Uh oh!
There was an error while loading. Please reload this page.
Conversation
Set up the repo shell following @btravstack/entity's conventions: pnpm workspace with the shared @btravstack/* catalog packages (tsconfig, oxlint, lefthook, commitlint), turbo pipeline, changesets, knip, and CI. No package yet — packages/* and examples/* are declared as workspace globs for the di package and its future examples.
Ports declare what an application needs, providers bind a port to a concrete construction (value, factory, class, or an acquire/release resource), and modules group providers behind explicit imports/exports. Every wiring mistake the type system can catch — a missing dependency, an internal port leaking out of a module, a re-export of something never imported — is a compile error; a cycle or a duplicate provider is caught before any factory runs, as a defect via unthrown. Ported from the reviewed @saas/platform-di implementation (93 tests), adapted to this repo's layout: runtime tests renamed *.test.ts -> *.spec.ts per @btravstack/entity's convention, relative imports moved to .js extensions to match the shared tsconfig's NodeNext resolution, and interface declarations converted to type aliases per the shared oxlint config's consistent-type-definitions rule.
Root README pitches the package and links to the package docs, in entity's style (badges, one-paragraph pitch, worked example). The package README preserves the platform-di walkthrough — ports, providers, modules, the construction family — with package names updated for the published @btravstack/di surface.
Ports named by the application, a private connection pool behind a public repository, and one application module composed against a production adapter and an in-memory one — the core story for @btravstack/di, built as a real workspace package rather than just a README walkthrough. Includes the compile-time guarantees (port privacy, the Module.build/Module.scoped gate) pinned in a dedicated index.test-d.ts, mirroring packages/di's own example.test-d.ts split.
Lifetime management: a connection pool acquired once under Module.scoped, and a per-request transaction layered over the already-built parent with Module.forkScope. The spec proves — not just type-checks — that each request's resource releases before the next begins, that the parent outlives every fork, and that a fork resolves its dependency from the parent context rather than a copy of its own.
Multi-binding: a Port.many health-check registry fed independently by two modules via Provider.member, collected and run together at the composition root. The spec proves contributions accumulate across module boundaries, a failing check comes back as reported data rather than aborting the run, and the registry is purely additive when a third module joins.
Indexes the three examples workspace packages, in the style of btravstack/entity's examples/README.md. Also picks up pnpm-lock.yaml's update from installing the three new example packages' devDependencies.
…larations
A consumer that exports a port it declares could not emit declarations:
export class OrderRepository extends Port("OrderRepository")<Shape> {}
emits as `declare const OrderRepository_base: <the heritage expression's
type>`, and the emitter can only write that type using names the consumer can
reach. `PortClass`/`ManyPortClass` were not exported from the package index, so
it had none: it expanded the heritage expression down to `PortInstance`'s
`[ID]`/`[SERVICE]`/`[MANY]` keys — module-private `unique symbol`s — and
reported TS4020, "has or is using private name 'ID'". That is the pattern
`packages/di/README.md` teaches on its first page, so it affected essentially
every real consumer.
Exporting the two class *types* is the fix that costs least. The emitter now
stops at `PortClass<"OrderRepository">` (2,683 bytes of consumer declarations
across the reproduction, against 3,545 when only the instance types are
nameable and the construct signature has to be written out), and `port.ts` is
untouched.
The brand symbols stay unexported deliberately. Exporting them also fixes emit,
but a consumer who can name `ID`/`SERVICE` can hand-write
`{ [ID]: "Logger", [SERVICE]: Shape }` and pass it off as a `Logger` — measured,
it type-checks. Naming the class types grants no such thing: the brand keys stay
unreachable, so port identity stays nominal and a port instance stays
unforgeable. As an ambient `export declare const` they have no runtime binding
and rolldown rejects the index re-export outright (MISSING_EXPORT); making them
real `Symbol()` values would add runtime surface to phantom tokens that are
never constructed.
The three example packages carried `declaration: false` in their own tsconfigs
to dodge this. That is legitimate only because they are private, and it is what
kept the repo green while no consumer could build, so it goes with the fix.`packages/di`'s own checks never emitted a consumer's declarations, so TS4020 could not be seen from inside the repo — the three example packages had turned `declaration` off, and the library's own `tsc --noEmit` only ever compiles code that can name `port.ts`'s brand symbols directly. `examples/hexagonal-order-api/src/emit-guards.ts` is that missing consumer: a file imported by nothing, which exists to be *compiled*. It names a plain port, a `Port.many` set port, a port reaching through another port's `ServiceOf`, the providers and module built on them, and the two factories whose return type is a port class rather than an instance — the shapes that fail through different brands, so a fix naming only one of the class types leaves the other broken. `tsconfig.emit.json` turns `noEmit` back off (TS4020 is raised by the declaration *emitter*, so a `--noEmit` pass cannot be the whole gate) and the package's `typecheck` script runs it under both 7.0.2 and a stable-line 5.9.3, then feeds the emitted `.d.ts` back through the compiler — a dangling reference in the output is not an emit-time diagnostic and would otherwise ship. `emit-guards.d.ts` is named explicitly in that last step because nothing imports it, so it would go unchecked on `index.d.ts` alone. The `@ts-expect-error` directives in the fixture are the other half: they assert that `ID`/`SERVICE`/`MANY` are still unreachable and that two structurally identical ports with different ids still do not unify. An unused directive there is a failure, not noise — it is the signal that someone bought declaration emit by widening the export surface far enough to forge a port. Verified with teeth: reverting the index re-export puts eleven TS4020/TS4023 errors back through this fixture. `knip.jsonc` names it an entry, without which knip reports it as an unused file.
fix(di): let consumers emit declarations for their own ports
The beet is not totally formed — dome, eyes and smile only, coalescing at the needle end, one drop already out. Mid-formation is temporal-contract's own grammar, so the two logos rhyme: the container constructs the dependency, then injects it. Replaces the socket mark, which the history keeps.
Dependabot groups verbatim (weekly npm + actions); release rides btravstack/config's reusable workflow after a green CI on main, exactly as the sibling repos do.
docs: guidance for future Claude Code instances, as CLAUDE.md
docs: the documentation site — vitepress + typedoc, on the entity template
chore: make 0.1.0 the first published version
…go pair Three things the landing surfaced when di joined it as the fifth panel (btravstack/btravstack.github.io#43). Closes#6. Closes#7. **The accent.** `--accent` was `#2A62B8`, the logo's deep blue. The landing paints each package name in the raw accent against a `#100F12` card, and that hex measures 3.22 there — the other four packages sit at 5.00–7.25. It is now `#3E7FD4`, the logo's *lighter* blue: 4.72 on the dark card, 7.10 as darkened text on white. The original reasoning is untouched and still holds — blue is plumbing, and the hex is one the mark itself already paints (barrel rim, plunger, needle hub), so chrome and artwork stay one color. **The logo pair.** di shipped only `logo.svg`, and it was the one package without a `logo-{light,dark}` pair. Three of its values read against the canvas rather than against the artwork, so on a light background the near-white barrel washed out and the mark read as a floating beet with a blue cap. `logo-dark` is the existing file unchanged; `logo-light` inverts exactly those three — barrel `#EAF2FB`→`#2A2730`, plunger `#2A62B8`→`#3E7FD4`, needle `#9FB2C8`→`#5A6675`. The ticks stay at 50% opacity: 1.89 against the dark barrel versus 1.81 against the light one, so they read equally faint either way. The hero and the nav logo now take the pair; `logo.svg` stays the favicon and the JSON-LD mark. **The theme.** Catalog moves 1.7.0 → 2.0.0, which is where `--pkg-di` lives. The major is the removal of `--pkg-demesne` / `--pkg-start`; neither is referenced here, so nothing breaks. pnpm recorded the new version as a pinned `minimumReleaseAgeExclude` entry — replaced with the unversioned name and the rationale comment the other repos use, so the next release needs no entry. Docs build clean: `--accent: #3E7FD4` in the emitted CSS, no `2a62b8` left, all five `--pkg-*` tokens present and both retired ones gone, and both logo variants verified in both themes.
`pnpm format --check` failed CI on this file. oxfmt normalises CSS hex values
to lowercase, which is why every sibling site's `--accent` is lowercase and why
this file's own `#2a62b8` was before it changed. The comment's hex follows, so
the file does not mix cases with its own declaration.
Note this is the opposite of the markdown convention: prose in changesets and
`design.md` writes hexes uppercase. Each file type is internally consistent.
Root cause of the miss: the shared pre-commit hook in @btravstack/lefthook
globs `*.{ts,tsx,js,jsx,json,yaml,yml,md}`, which has no `css`, so this file was
never formatted locally — the hook reported three files (config.ts, index.md,
pnpm-workspace.yaml) and skipped this one. CI runs `oxfmt .` over everything.feat(docs): theme 2.0.0, the lighter blue accent, and a light/dark logo pair
The subtree's packages/di, three examples and docs workspace move to their start-side homes; di's root CLAUDE.md becomes packages/di's; the repo-generic CONTRIBUTING/SECURITY and the deploy-docs/release workflows come along; duplicated root scaffolding is dropped. Intermediate state: the catalog entries the moved workspaces need land in the next commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The catalog gains the docs toolchain (@btravstack/theme, typedoc + its own named catalog, vitepress, the typescript-consumer alias) and the vite override; the docs workspace joins the workspace list; knip.json becomes knip.jsonc carrying di's entries (and drops this repo's dead workspaces block — knip's default entry patterns already cover src/main.ts); the changeset config adopts onlyUpdatePeerDependentsWhenOutOfRange, which matters now that the start packages peer on an in-repo di. @btravstack/di moves from catalog: to workspace ranges everywhere; its published peer range stays ^0.1.0 and its manifest now names this repo. di's code passes this repo's stricter oxlint set (no-throw, no-get-or-throw, prefer-ensure) unchanged, and its suite is green under unthrown 5.5.0 (was 5.1.0 in its own repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR merges the standalone btravstack/di repository into this monorepo as packages/di, updating workspace wiring so the start kernel and its runtimes consume the in-repo DI package while keeping @btravstack/di published separately (still as a peer). It also brings over di’s consumer examples and documentation site, plus supporting release/docs workflows and repo metadata.
Changes:
- Add the
packages/diworkspace (source, tests, type-level tests, package metadata) and switch existing workspaces to depend on it viaworkspace:*/workspace:^. - Add di’s documentation site as a
docs/workspace (TypeDoc + VitePress), with Turbo wiring and deploy workflow. - Consolidate repo tooling/config: knip config to
.jsonc, changesets config tweak, new contributing/security docs, and supporting GitHub workflows/actions.
Reviewed changes
Copilot reviewed 109 out of 115 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| turbo.json | Adds a dedicated Turbo build task for the di docs site output. |
| SECURITY.md | Adds repository security policy for vulnerability reporting. |
| README.md | Updates documentation section to include packages/di and the docs site. |
| pnpm-workspace.yaml | Adds docs as a workspace and updates the root catalog/overrides for docs toolchain. |
| packages/start/package.json | Switches @btravstack/di devDependency from catalog: to workspace:^. |
| packages/start-temporal/package.json | Switches @btravstack/di devDependency from catalog: to workspace:^. |
| packages/start-http/package.json | Switches @btravstack/di devDependency from catalog: to workspace:^. |
| packages/start-amqp/package.json | Switches @btravstack/di devDependency from catalog: to workspace:^. |
| packages/di/vitest.config.ts | Adds Vitest config for the di package. |
| packages/di/tsconfig.test-d.json | Adds a dedicated tsconfig for *.test-d.ts type-level tests. |
| packages/di/tsconfig.json | Adds di package TypeScript build config aligned to repo base config. |
| packages/di/src/type-assert.ts | Adds shared Equal<A,B> type helper for type-level tests. |
| packages/di/src/scoped.test-d.ts | Adds type-level tests for Module.scoped / Scope gating semantics. |
| packages/di/src/scope.ts | Adds the di scope/finaliser implementation (LIFO, swallow/report failures). |
| packages/di/src/provider.spec.ts | Adds runtime tests for provider construction arms and defect handling. |
| packages/di/src/port.test-d.ts | Adds type-level tests for nominal Port identity and ServiceOf. |
| packages/di/src/port.spec.ts | Adds runtime tests for port id and duplicate-id warning behavior. |
| packages/di/src/many.test-d.ts | Adds type-level tests for Port.many and Provider.member typing. |
| packages/di/src/many.spec.ts | Adds runtime tests for set-port accumulation and wiring-defect behavior. |
| packages/di/src/index.ts | Defines di’s public API surface and type-only export decisions. |
| packages/di/src/fork.test-d.ts | Adds type-level tests for Module.forkScope typing and gating. |
| packages/di/src/fork.spec.ts | Adds runtime tests for forked scope teardown isolation vs parent scope. |
| packages/di/src/example.test-d.ts | Adds type-level example asserting module privacy at compile time. |
| packages/di/src/context.test-d.ts | Adds type-level tests for Context variance and access gating. |
| packages/di/src/context.spec.ts | Adds runtime tests for context immutability and unsafeAdd behavior. |
| packages/di/src/build.test-d.ts | Adds type-level tests for Module.build return typing and arity gate. |
| packages/di/package.json | Adds the published di package manifest and scripts. |
| packages/di/LICENSE | Adds di’s MIT license file. |
| packages/di/CLAUDE.md | Adds di-specific spec/guidance file for contributors/agents. |
| packages/di/CHANGELOG.md | Adds di changelog (0.1.0 initial release). |
| knip.jsonc | Adopts JSONC, adds di/docs-related ignores and a di example entrypoint override. |
| knip.json | Removes old knip config (replaced by knip.jsonc). |
| examples/request-scope/vitest.config.ts | Adds Vitest config for the request-scope example. |
| examples/request-scope/tsconfig.json | Adds TS config for the request-scope example. |
| examples/request-scope/src/index.ts | Adds the request-scope example implementation using Module.scoped + forkScope. |
| examples/request-scope/src/index.spec.ts | Adds runtime tests proving lifecycle/release ordering and parent seeding. |
| examples/request-scope/README.md | Adds documentation for the request-scope example. |
| examples/request-scope/package.json | Adds the request-scope example workspace manifest. |
| examples/README.md | Extends examples index to include the di example family. |
| examples/plugin-registry/vitest.config.ts | Adds Vitest config for the plugin-registry example. |
| examples/plugin-registry/tsconfig.json | Adds TS config for the plugin-registry example. |
| examples/plugin-registry/src/index.ts | Adds plugin-registry example showcasing Port.many + Provider.member. |
| examples/plugin-registry/src/index.spec.ts | Adds runtime tests for contribution accumulation and failure folding. |
| examples/plugin-registry/README.md | Adds documentation for the plugin-registry example. |
| examples/plugin-registry/package.json | Adds the plugin-registry example workspace manifest. |
| examples/order-worker/package.json | Switches @btravstack/di dependency to workspace:*. |
| examples/order-temporal/package.json | Switches @btravstack/di dependency to workspace:*. |
| examples/order-infrastructure/package.json | Switches @btravstack/di dependency to workspace:*. |
| examples/order-application/package.json | Switches @btravstack/di dependency to workspace:*. |
| examples/order-api/package.json | Switches @btravstack/di dependency to workspace:*. |
| examples/order-amqp/package.json | Switches @btravstack/di dependency to workspace:*. |
| examples/hexagonal-order-api/vitest.config.ts | Adds Vitest config for the hexagonal-order-api example. |
| examples/hexagonal-order-api/tsconfig.test-d.json | Adds dedicated type-test tsconfig for the hexagonal example. |
| examples/hexagonal-order-api/tsconfig.json | Adds TS config for the hexagonal example (excludes *.test-d.ts). |
| examples/hexagonal-order-api/tsconfig.emit.json | Adds declaration-emit fixture config used to gate TS4020-class issues. |
| examples/hexagonal-order-api/src/index.ts | Adds the hexagonal architecture example implementation. |
| examples/hexagonal-order-api/src/index.test-d.ts | Adds compile-time tests for privacy + Scope gating in the example. |
| examples/hexagonal-order-api/src/index.spec.ts | Adds runtime tests for both production and in-memory compositions. |
| examples/hexagonal-order-api/README.md | Adds documentation for the hexagonal example. |
| examples/hexagonal-order-api/package.json | Adds the hexagonal example workspace manifest and emit/typecheck scripts. |
| docs/typedoc.json | Adds TypeDoc config to generate di API docs from source. |
| docs/reference/wiring-defects.md | Adds reference docs for pre-construction wiring defects and messages. |
| docs/reference/providers.md | Adds reference docs for Provider construction arms and semantics. |
| docs/reference/ports.md | Adds reference docs for ports, set ports, ServiceOf, and type-only Scope. |
| docs/reference/modules.md | Adds reference docs for module structure, channels, and variance rule. |
| docs/reference/entry-points.md | Adds reference docs for build/scoped/forkScope and the arity gate. |
| docs/public/logo.svg | Adds docs site logo asset. |
| docs/public/logo-light.svg | Adds light-mode logo asset variant. |
| docs/public/logo-dark.svg | Adds dark-mode logo asset variant. |
| docs/package.json | Adds docs workspace manifest and build/dev scripts. |
| docs/index.md | Adds VitePress home page for di docs. |
| docs/how-to/swap-an-adapter.md | Adds how-to guide for adapter swapping via module seams. |
| docs/how-to/request-scope.md | Adds how-to guide for per-request scopes via forkScope. |
| docs/how-to/private-ports.md | Adds how-to guide for module privacy via exports. |
| docs/how-to/plugin-registry.md | Adds how-to guide for plugin registries via set ports. |
| docs/how-to/manage-a-resource.md | Adds how-to guide for resource lifetime management via scoped. |
| docs/explanation/why-di.md | Adds explanation of di’s design motivations and constraints. |
| docs/explanation/scopes-and-resources.md | Adds explanation of Scope as a phantom debt and teardown guarantees. |
| docs/explanation/peer-dependencies.md | Adds explanation for unthrown being a peer dependency. |
| docs/explanation/modules-and-privacy.md | Adds explanation of type-level privacy vs flat runtime map. |
| docs/explanation/failures-vs-defects.md | Adds explanation of modeled failures vs defects and channel separation. |
| docs/explanation/compile-time-wiring.md | Adds explanation of Needs propagation and compile-time arity gate. |
| docs/examples/request-scope.md | Adds docs page for the request-scope example. |
| docs/examples/plugin-registry.md | Adds docs page for the plugin-registry example. |
| docs/examples/index.md | Adds docs page indexing di examples and explaining why they’re “tests”. |
| docs/examples/hexagonal-order-api.md | Adds docs page for the hexagonal-order-api example. |
| docs/api/index.md | Adds API reference landing page (TypeDoc output linked beneath). |
| docs/.vitepress/theme/index.ts | Adds VitePress theme wiring for docs site. |
| docs/.vitepress/theme/custom.css | Adds di-specific accent/theming tokens for docs site. |
| docs/.vitepress/config.ts | Adds VitePress site config (nav, sidebar, edit links, social/meta). |
| CONTRIBUTING.md | Adds contribution guidelines and the repo “gate” commands. |
| CLAUDE.md | Updates repo-level authoritative spec to reflect di merge and repo structure. |
| .gitignore | Ignores VitePress cache and generated TypeDoc output directory. |
| .github/workflows/release.yml | Adds release workflow chaining off CI success on main. |
| .github/workflows/deploy-docs.yml | Adds GitHub Pages deploy workflow for docs (TypeDoc + VitePress). |
| .github/actions/setup/action.yml | Adds composite action for Node+pnpm setup and turbo cache. |
| .changeset/no-release-first-publish.md | Adds changeset marker for release plumbing without a bump. |
| .changeset/config.json | Enables onlyUpdatePeerDependentsWhenOutOfRange experimental option. |
Suppressed comments (3)
docs/.vitepress/config.ts:176
- The docs site social GitHub link still points at btravstack/di. After moving di into this monorepo, it should link to the new location to avoid a stale/archived repo.
socialLinks: [
{ icon: "github", link: "https://github.com/btravstack/di" },
{ icon: "npm", link: "https://www.npmjs.com/package/@btravstack/di" },
],
docs/.vitepress/config.ts:194
- The editLink pattern still targets the old btravstack/di repository, so “Edit this page on GitHub” will be broken/misleading after the merge. It should point at this repo’s docs path.
editLink: {
pattern: "https://github.com/btravstack/di/edit/main/docs/:path",
text: "Edit this page on GitHub",
},
docs/.vitepress/config.ts:304
- The Organization JSON-LD
sameAsstill references the old btravstack/di repo. After the move, it should reference the new canonical repository URL.
"@type": "ImageObject",
url: `${SITE_URL}logo.svg`,
},
sameAs: ["https://github.com/btravstack/di"],
}),
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| - **GitHub Security Advisories** — [open a private report](https://github.com/btravstack/di/security/advisories/new) | ||
| (preferred; keeps the discussion and fix coordination in one place). |
| - theme: alt | ||
| text: GitHub | ||
| link: https://github.com/btravstack/di |
| { | ||
| text: "Changelog", | ||
| link: "https://github.com/btravstack/di/releases", | ||
| }, |
| Three small packages under | ||
| [`examples/`](https://github.com/btravstack/di/tree/main/examples), each | ||
| showing a different job `@btravstack/di` does — and, at the same time, | ||
| exercising the library end to end from a consumer's own workspace. |
| ```sh | ||
| git clone https://github.com/btravstack/di.git && cd di | ||
| pnpm install |
| **Source:** | ||
| [`examples/hexagonal-order-api`](https://github.com/btravstack/di/tree/main/examples/hexagonal-order-api) | ||
| **Source:** | ||
| [`examples/request-scope`](https://github.com/btravstack/di/tree/main/examples/request-scope) | ||
| **Source:** | ||
| [`examples/plugin-registry`](https://github.com/btravstack/di/tree/main/examples/plugin-registry) | ||
| sentence, and hovering a large module shows real channel unions. The library | ||
| is also deliberately small — one construction family, one module algebra, | ||
| three entry points, and [one name per concept](https://github.com/btravstack/di/blob/main/CONTRIBUTING.md). | ||
| If you want conditional registration DSLs, interceptors, or property |
btravers
commented
Aug 13, 2026
Closing: decision reversed — di stays in its own repo for now. The merge work is preserved on a local branch and can be revived if the co-design cost ever justifies it. |
Merges
btravstack/diinto this repo aspackages/di, per the decision that start is the stack's aggregator and di is its closest-coupled sibling (mutual 0.x, peer^0.1.0, effectively one audience).What moved
packages/di— the package, with its full git history (subtree merge; 27 commits reachable through the merge commit). Manifest now names this repo; still published as@btravstack/di, still a peer of the four start packages.examples/hexagonal-order-api/request-scope/plugin-registry— di's consumer examples, indexed inexamples/README.md.docs/— di's VitePress + TypeDoc site, with its toolchain (theme, typedoc + named catalog, vitepress, thetypescript-consumeralias for the dual declaration-emit pass, the vite security override) folded into the root catalog.CONTRIBUTING.md/SECURITY.md, thedeploy-docs.yml+release.ymlworkflows and the composite setup action.packages/di/CLAUDE.md(sixth per-package spec file).Wiring changes
@btravstack/dimoves fromcatalog:toworkspace:^/workspace:*across all workspaces; the catalog entry is gone.knip.json→knip.jsonc, carrying di's entries and dropping this repo's deadworkspacesblock (one checkbox of Mechanical consistency sweep across the packages #27).onlyUpdatePeerDependentsWhenOutOfRange— needed now that start packages peer on an in-repo di.Gate
format ✓ · lint ✓ · typecheck ✓ (26 tasks, incl. di's dual-TS emit pass and the docs build) · knip ✓ · test: 24/25 workspaces green — the one failure is the pre-existing local-only port-9000 probe conflict, unrelated to this PR · build ✓ (6 tasks).
After this lands (separate steps, not in this PR)
deploy-docs.yml(di's docs URL moves).btravstack/di(e.g. di#9) and archive that repo with a pointer here.🤖 Generated with Claude Code