{"_id":"@de-otio/trellis-extension-testkit","_rev":"2-83359ce835063f3beee484ea0bb5e9f6","name":"@de-otio/trellis-extension-testkit","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@de-otio/trellis-extension-testkit","version":"0.1.0","author":{"name":"Trellis"},"license":"MIT","_id":"@de-otio/trellis-extension-testkit@0.1.0","maintainers":[{"name":"rkm1","email":"richard.myers@de-otio.org"}],"homepage":"https://github.com/de-otio/trellis#readme","bugs":{"url":"https://github.com/de-otio/trellis/issues"},"dist":{"shasum":"7926d5759020ff5e23c470cfe4dea2dba04233dd","tarball":"https://registry.npmjs.org/@de-otio/trellis-extension-testkit/-/trellis-extension-testkit-0.1.0.tgz","fileCount":43,"integrity":"sha512-zHQivrRXXG95rfPkPuY3fxlfGlBM+bbX2/Bh96fcSy+I8ZC9rmP2+Yr7U6mlc7A1igE5AWxN4a/NEFQBEYloyA==","signatures":[{"sig":"MEUCIQDwFYOKA3A2puJOKop30xp9c/4baOstFcdQbn7gYtClTQIgBDAiuUVTMszs71uuew81MyKMtUQEV0UiETZQvAcSd/0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":166571},"main":"./lib/index.js","type":"module","types":"./lib/index.d.ts","exports":{".":{"types":"./lib/index.d.ts","default":"./lib/index.js"},"./example":{"types":"./lib/example/index.d.ts","default":"./lib/example/index.js"},"./package.json":"./package.json","./docker-compose.yml":"./fixtures/docker-compose.yml"},"gitHead":"d6c6442f7877d4448be83e058ca69c19bf637d1c","scripts":{"dev":"tsc --watch","lint":"prettier --check .","test":"vitest run --config vitest.config.ts","build":"tsc --build tsconfig.json","format":"prettier -w .","prepare":"tsc --build tsconfig.json","test:types":"tsc --noEmit -p tsconfig.test.json"},"_npmUser":{"name":"rkm1","email":"richard.myers@de-otio.org"},"repository":{"url":"git+https://github.com/de-otio/trellis.git","type":"git","directory":"packages/extension-testkit"},"_npmVersion":"11.17.0","description":"Boot a real Trellis server against your extension, and check that it conforms","directories":{},"_nodeVersion":"24.19.0","dependencies":{"pg":"^8.23.0","zod":"^4.4.3","prisma":"^7.9.1","@aws-sdk/client-dynamodb":"^3.1106.0","@de-otio/trellis-extension-api":"^0.9.2"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.0","prettier":"^3.9.6","@types/pg":"^8.21.0","typescript":"^7.0.2","@types/node":"^26.2.0"},"peerDependencies":{"@de-otio/trellis":">=0.25.0-alpha.7"},"_npmOperationalInternal":{"tmp":"tmp/trellis-extension-testkit_0.1.0_1786882072746_0.9049001413964535","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@de-otio/trellis-extension-testkit","version":"0.1.1","description":"Boot a real Trellis server against your extension, and check that it conforms","type":"module","exports":{".":{"types":"./lib/index.d.ts","default":"./lib/index.js"},"./example":{"types":"./lib/example/index.d.ts","default":"./lib/example/index.js"},"./docker-compose.yml":"./fixtures/docker-compose.yml","./package.json":"./package.json"},"main":"./lib/index.js","types":"./lib/index.d.ts","scripts":{"build":"tsc --build tsconfig.json","dev":"tsc --watch","prepare":"tsc --build tsconfig.json","test":"vitest run --config vitest.config.ts","test:types":"tsc --noEmit -p tsconfig.test.json","lint":"prettier --check .","format":"prettier -w ."},"author":{"name":"Trellis"},"license":"MIT","dependencies":{"@aws-sdk/client-dynamodb":"^3.1106.0","@de-otio/trellis-extension-api":"^0.9.2","pg":"^8.23.0","prisma":"^7.9.1","zod":"^4.4.3"},"peerDependencies":{"@de-otio/trellis":">=0.25.0-alpha.8"},"devDependencies":{"@types/node":"^26.2.0","@types/pg":"^8.21.0","prettier":"^3.9.6","typescript":"^7.0.2","vitest":"^4.0.0"},"repository":{"type":"git","url":"git+https://github.com/de-otio/trellis.git","directory":"packages/extension-testkit"},"gitHead":"7efd7b292eb3a494049c893ad06e45627a718050","_id":"@de-otio/trellis-extension-testkit@0.1.1","bugs":{"url":"https://github.com/de-otio/trellis/issues"},"homepage":"https://github.com/de-otio/trellis#readme","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-6xfg+9EtE631kLA3q3PlekwGqqyrEJxYZieg789Kg3ow8BvNaT2rst0XVWg1Mmbo2RBkLR5ZiBlUEC5w1fPcUQ==","shasum":"e4d43dc8df35e07b1c335ea79460f3b0cc869528","tarball":"https://registry.npmjs.org/@de-otio/trellis-extension-testkit/-/trellis-extension-testkit-0.1.1.tgz","fileCount":43,"unpackedSize":167012,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@de-otio%2ftrellis-extension-testkit@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCM3uf+knXN2mfwAM8ViovM2kvHJOTEqPxHmgglaKr7aAIhAMfkaypXVoV2WFtqqr+rHSIfIUGFWgrW07CniH8ZSL/+"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:468c5d83-58ff-44dd-80df-b1fa480c06f6"}},"directories":{},"maintainers":[{"name":"rkm1","email":"richard.myers@de-otio.org"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/trellis-extension-testkit_0.1.1_1786884967110_0.8620212707759203"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-16T12:07:52.603Z","modified":"2026-08-16T12:56:07.593Z","0.1.0":"2026-08-16T12:07:52.896Z","0.1.1":"2026-08-16T12:56:07.253Z"},"bugs":{"url":"https://github.com/de-otio/trellis/issues"},"author":{"name":"Trellis"},"license":"MIT","homepage":"https://github.com/de-otio/trellis#readme","repository":{"type":"git","url":"git+https://github.com/de-otio/trellis.git","directory":"packages/extension-testkit"},"description":"Boot a real Trellis server against your extension, and check that it conforms","maintainers":[{"name":"rkm1","email":"richard.myers@de-otio.org"}],"readme":"# @de-otio/trellis-extension-testkit\n\nBoot a real Trellis server against your extension, and check that it conforms.\n\nUntil this package existed you could typecheck an extension and do nothing\nelse: the server boot, the docker stack and the migrations all lived in core's\ntest tree, which is excluded from the published tarball. So the one thing that\nwould let you — or your coding agent — verify your own work was the one thing\nnot shipped.\n\n```bash\nnpm i -D @de-otio/trellis-extension-testkit\n```\n\n`@de-otio/trellis` is a **peer** dependency: the testkit runs your extension\nagainst the core you already depend on, never against one of its own.\n\n## Boot a server\n\n```ts\n// test/setup.ts — your runner's setup file, NOT a test file. See below.\nimport { startStandaloneServer } from \"@de-otio/trellis-extension-testkit\";\nimport { myExtension } from \"../src/index.js\";\n\nconst server = await startStandaloneServer({\n  extensions: [myExtension],\n  extra: { MY_EXTENSION_API_KEY: \"test\" }, // whatever your configSchema requires\n});\n\nconsole.log(server.url); // http://localhost:3100\n// …run your tests…\nawait server.stop();\n```\n\nThat call applies the environment core needs, runs core's migrations, creates\nthe DynamoDB table, registers your extension, starts the server, waits for\n`/health`, enables the feature toggles core's own handlers gate on, and runs\nthe conformance checks. If any of it fails, it throws — which is the answer you\nwant from a test lane.\n\nYou need Postgres and DynamoDB-local. A compose file ships with the package:\n\n```bash\ndocker compose -f node_modules/@de-otio/trellis-extension-testkit/fixtures/docker-compose.yml up -d\n```\n\nAlready running a Trellis stack? Skip the file and pass `databaseUrl` /\n`dynamoEndpoint`. Two lanes can share one stack as long as they use a different\n`port` and `dynamoTable`.\n\n### Where to call it from\n\n**A setup file, not a test file.** Every mainstream runner — vitest, jest,\nnode:test — executes test files in workers with their own module graph, and\ncore's extension registry is in-process state. Boot in a worker and the tests\nin _other_ files talk to a server whose registry they cannot see; boot in a\nrunner-level `globalSetup` and the checks run in a process where nothing is\nregistered. The rule that covers both: **boot and check in the same process**,\nand drive HTTP from wherever you like.\n\n## Check conformance\n\n`startStandaloneServer` runs these at boot by default. Call them directly when\nyou want the findings rather than a throw:\n\n```ts\nimport { checkExtensionConformance } from \"@de-otio/trellis-extension-testkit\";\n\nconst result = await checkExtensionConformance({\n  extension: myExtension,\n  apiUrl: server.url,\n});\n// { ok: boolean, findings: [{ check, severity, message, fix }] }\n```\n\n| check               | what it catches                                                                  |\n| ------------------- | -------------------------------------------------------------------------------- |\n| `registration`      | registered under an id you did not expect, or two copies of the extension loaded |\n| `api-version`       | `extensionApiVersion` absent, unparseable, or from another compatibility window  |\n| `routes-mount`      | a declared `extensionRoutes` entry that answers 404                              |\n| `cross-tenant-read` | a `crossTenantRead` grant nothing you ship can reach                             |\n\nThese are **stricter than core on purpose.** Core validates what would make\n_core_ unsafe and is deliberately permissive about what merely makes an\nextension wrong — an undeclared `extensionApiVersion` is one line in a log\nnobody reads. Every defect found in the first real Trellis vertical was of that\nsecond kind, which is what this table is a list of.\n\nAdopting the testkit into a lane that already has findings:\n\n```ts\nawait startStandaloneServer({\n  extensions: [myExtension],\n  conformance: \"warn\", // report, do not fail\n  // or, per finding, once you have decided one is acceptable:\n  acceptConformance: [\"api-version\"],\n});\n```\n\n`accept` downgrades a finding to a warning and **keeps it in the report**.\nSilencing one entirely is how it stops being reconsidered.\n\n### What `cross-tenant-read` does not check\n\nWhether each declared model is actually _read_. An extension with routes that\ndeclares five models and reads one passes — and that is the exact shape of the\nover-broad declaration this suite was written after. Catching it needs core to\nrecord which models `discover()` touched during a run, and that instrumentation\ndoes not exist yet. Said plainly because a check that implied it covered this\nwould be worse than no check at all.\n\n## The reference extension\n\n```ts\nimport { exampleExtension, minimalExtension } from \"@de-otio/trellis-extension-testkit/example\";\n```\n\n`exampleExtension` populates every optional surface core actually dispatches\nand is the thing to copy. `minimalExtension` omits every optional field,\nincluding `extensionApiVersion` — it is the standing proof that the optional\nfields really are optional, so it fails conformance by design.\n\nBoth are core's own dummy target: core's contract tests and standalone lane\nimport these objects. A reference extension nothing exercises rots into a lie\nabout the contract.\n\n## Pieces, if you want them separately\n\n`startStandaloneServer` is the whole product; everything else is what it is\nmade of, exported because a lane that manages its own database will want the\ntoggles but not the migrations, or the reverse.\n\n- `standaloneEnv(opts)` — apply core's required environment. **Call before\n  importing `@de-otio/trellis`**: several core modules read `process.env` when\n  they load.\n- `applyCoreMigrations({ databaseUrl, schemaPath })` — `prisma migrate deploy`\n  against core's shipped migrations.\n- `coreSchemaPath()` — where core's `schema.prisma` is, resolved through the\n  installed package. Throws with instructions when core is a git checkout\n  rather than a tarball, because `prisma/` is assembled at pack time.\n- `seedGlobalFeatureToggles(keys)` — enable global toggles. Core's handlers\n  gate on these and they default to off, so skipping this produces 403s and\n  404s that look like bugs in your extension.\n- `ensureDynamoTable({ table, endpoint })`, `waitForHealth(url)`.\n\n## Versioning\n\nThe testkit is published from the same repository as core and\n`@de-otio/trellis-extension-api`, on its own tag series\n(`extension-testkit-v<version>`). Its dependency on the contract package is\nchecked in lockstep with core's in CI.\n\n## Licence\n\nMIT.\n","readmeFilename":"README.md"}