Skip to content

Application-service ports: cache, mailer, file storage #62

Description

@btravers

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions