From a gap analysis against NestJS and AdonisJS.
Three application services both frameworks ship and this repo has no port for. Grouped into one issue because they are the same shape of work — a port plus one or two adapters — and none of them raises a design question the way #57 or #58 does.
- Cache. Nest ships
@nestjs/cache-manager; Adonis has one too. A Cache port with an in-memory adapter and a Redis one. The interesting part is not the interface, it is whether a cached value is scoped to the unit, the scope, or the process.
- Mailer. A
Mailer port. The testable-by-default version matters more than the transport: a recording adapter so a spec can assert what would have been sent, in the shape @btravstack/observability's Recorder already uses for log lines.
- File storage. Adonis calls it Drive. A
Storage port over local disk and S3-compatible object storage. Streaming and presigned URLs are where the abstraction usually leaks; worth deciding whether they are in scope before writing the port.
All three are plain ports, which is the point: they need no kernel change, no new runtime, and no exemption from any thesis. They are the boring middle, and each is independently useful.
YAGNI caveat, deliberately kept. None of these should be built ahead of an example that needs one. packages/di's own CLAUDE.md says it: "contributions that sharpen the design beat ones that grow it." Port.many was removed for having no consumer, and that reasoning was later judged wrong for a library — but the fix there was to restore a removed primitive, not to add speculative ones. Build each of these when an example workspace genuinely calls for it.
Acceptance
- Each port lands with at least one real adapter and one test-friendly adapter.
- Each is exercised by an example workspace, since
examples/ is part of the gate.
- Each states in its own
CLAUDE.md what it deliberately does not do, the way the transport starters do.
- Not built speculatively — an issue closing as "no consumer needed it" is a valid outcome.
From a gap analysis against NestJS and AdonisJS.
Three application services both frameworks ship and this repo has no port for. Grouped into one issue because they are the same shape of work — a port plus one or two adapters — and none of them raises a design question the way #57 or #58 does.
@nestjs/cache-manager; Adonis has one too. ACacheport with an in-memory adapter and a Redis one. The interesting part is not the interface, it is whether a cached value is scoped to the unit, the scope, or the process.Mailerport. The testable-by-default version matters more than the transport: a recording adapter so a spec can assert what would have been sent, in the shape@btravstack/observability'sRecorderalready uses for log lines.Storageport over local disk and S3-compatible object storage. Streaming and presigned URLs are where the abstraction usually leaks; worth deciding whether they are in scope before writing the port.All three are plain ports, which is the point: they need no kernel change, no new runtime, and no exemption from any thesis. They are the boring middle, and each is independently useful.
YAGNI caveat, deliberately kept. None of these should be built ahead of an example that needs one.
packages/di's ownCLAUDE.mdsays it: "contributions that sharpen the design beat ones that grow it."Port.manywas removed for having no consumer, and that reasoning was later judged wrong for a library — but the fix there was to restore a removed primitive, not to add speculative ones. Build each of these when an example workspace genuinely calls for it.Acceptance
examples/is part of the gate.CLAUDE.mdwhat it deliberately does not do, the way the transport starters do.