Skip to content

Plan: build out the first-party middleware ecosystem #166

@medz

Description

@medz

Goal

After Spry 8.1, the next step should be filling out the first-party middleware ecosystem rather than continuing with scattered optimizations.

The aim is to complement Spry's existing request pipeline with a coherent middleware layer while keeping the framework core small and explicit.

Scope

  • Auth: Basic / Bearer
  • Combine: some / every / except (#179)
  • CORS
  • CSRF
  • ETag
  • Language
  • Powered-By
  • Pretty JSON
  • Request ID (#177)
  • Timing (#178)
  • Timeout
  • Cache-Control

Principles

  • Keep package:spry focused on routing, generation, and runtime behavior.
  • Prefer first-party middleware packages/helpers over expanding Spry core directly.
  • Do not grow Event with middleware-specific fields for this wave; prefer event.locals plus documented access patterns when request-scoped state is needed.
  • Keep middleware behavior explicit and inspectable.
  • Hold security-sensitive middleware (auth, csrf) to a higher bar than cosmetic/header helpers.

Proposed rollout

Wave 1

  • requestId(...) (#177)
  • timing(...) (#178)
  • poweredBy(...)
  • cacheControl(...)
  • prettyJson(...)
  • every([...]) (#179)
  • except(...) (#179)

Why first:

  • low-risk
  • immediately useful
  • no new control-flow contract required

Wave 2

  • cors(...)
  • etag(...)
  • language(...)
  • timeout(...)

Why second:

  • still broadly useful
  • needs more careful semantics around headers, negotiation, and response control

Wave 3

  • basicAuth(...)
  • bearerAuth(...)
  • csrf(...)
  • some(...) (#179)

Why last:

  • security-sensitive
  • likely to shape the eventual composition model

Design note on some(...)

some(...) ultimately landed as a general-purpose fallback combiner in #179, but with explicit semantics rather than a naive mirror of every(...).

Today, middleware is still:

typedef Middleware = FutureOr<Response> Function(Event event, Next next);

The some(...) helper resolves the missing generic "decline" state by treating:

  • a normal return as success
  • a thrown error as candidate failure
  • shared next() calls as a single downstream execution

When every candidate fails, some(...) throws a selected tracked error through SomeErrorThrower.

Expected outputs per middleware

Each middleware should eventually land with:

  • focused unit tests
  • route-level integration coverage
  • one minimal example
  • one short docs page or guide section

Non-goals for this wave

  • websocket-specific middleware APIs
  • websocket message-level middleware
  • full session/auth frameworks
  • full i18n frameworks
  • full caching frameworks

The first job is to complete the common HTTP middleware layer around Spry's existing request pipeline.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions