Skip to main content

v0.5.0

Released on the main branch as Composer tag v0.5.0.

Upgrade summary

{
"require": {
"better-route/better-route": "^0.5.0"
}
}

0.5.0 is additive — there are no breaking changes for existing 0.4.0 callers. The release adds neutral primitives for public-client and account-style APIs where a route can trigger expensive or externally visible side effects (charges, notifications, customer-owned mutations). The new pieces are not tied to WooCommerce, but they target the same surface where retries, mobile clients, customer-owned resources, CORS, and audit trails have to be explicit.

Public-client / account API hardening

Atomic idempotency for side-effectful writes

IdempotencyMiddleware (since 0.3.0) is a response replay cache — the store write happens after handler completion. That is fine for "make the second call return the same answer," but it does not stop two concurrent retries from both executing the handler.

0.5.0 adds AtomicIdempotencyMiddleware together with an atomic store contract:

  • BetterRoute\Middleware\Write\AtomicIdempotencyMiddleware
  • BetterRoute\Middleware\Write\AtomicIdempotencyStoreInterface
  • BetterRoute\Middleware\Write\AtomicIdempotencyRecord
  • BetterRoute\Middleware\Write\ArrayAtomicIdempotencyStore (tests / non-production)
  • BetterRoute\Middleware\Write\WpdbAtomicIdempotencyStore (production, INSERT IGNORE reservation, dedicated installable schema)

Behavior:

  • the first matching request reserves the idempotency key before the handler runs;
  • an identical request while the first is still running returns 409 idempotency_in_progress;
  • an identical request after completion replays the saved response and adds Idempotency-Replayed: true when the saved response is a Response;
  • same key with a different fingerprint returns 409 idempotency_conflict;
  • by default, thrown exceptions release the reservation so the client can retry (releaseOnThrowable: true).

See Atomic idempotency.

CORS / preflight middleware

Public clients (browsers, mobile webviews) often need a deliberate REST CORS contract instead of relying on default WordPress behavior. 0.5.0 adds:

  • BetterRoute\Middleware\Cors\CorsMiddleware
  • BetterRoute\Middleware\Cors\CorsPolicy
  • Router::options() and a public-by-default permission for OPTIONS routes (so explicit preflight endpoints can be registered without ->publicRoute())

Default allowed headers include Authorization, Content-Type, Idempotency-Key, If-Match, If-None-Match, X-Request-ID, X-WP-Nonce. Default exposed headers include ETag, Idempotency-Replayed, X-RateLimit-*, X-Request-ID. Preflight OPTIONS requests short-circuit with 204 and the negotiated headers. Disallowed origins fail with 403 cors_origin_denied unless rejectDisallowedOrigins: false is passed.

See CORS / preflight.

Ownership guards

0.5.0 adds reusable helpers for routes/resources where the authenticated user may only access their own object.

  • BetterRoute\Middleware\Auth\OwnershipGuardMiddleware — route-level guard. Runs an ownerResolver(RequestContext): int|string|null against the auth context (auth.userId/auth.subject) or current WP user. Optional bypassCapability for admin overrides. deniedStatus defaults to 404 (recommended — does not leak existence) but can be 403.
  • BetterRoute\Resource\OwnedResourcePolicy::currentUserOwns() — Resource DSL preset that emits permissions for get/update/delete (configurable via ownedActions) plus an optional list permission for authenticated users only. Bypass capability defaults to manage_options.

Difference from 0.4.0: ResourcePolicy had generic capability/callback presets only. 0.5.0 introduces a named ownership pattern for customer-owned or user-owned resources.

See Ownership guards.

Audit enrichment

AuditMiddleware (since 0.3.0) emitted route/method/status/duration/requestId. 0.5.0 lets callers attach domain-safe metadata without changing handlers:

  • AuditMiddleware now reads RequestContext::$attributes['audit'] (associative array) and merges it into the emitted event under the existing event schema.
  • BetterRoute\Middleware\Audit\AuditEnricherMiddleware is a thin enricher that adds:
    • authProvider, authUserId, authSubject from the auth attribute (only fields actually present);
    • idempotencyKey (SHA-1 hashed — never stored raw);
    • optional clientIp via ClientIpResolver when includeClientIp: true;
    • any staticFields you pass (e.g. ['resource' => 'account']).

Order it before AuditMiddleware (and after auth middleware that populates auth).

See Audit.

Response-header consistency

RateLimitMiddleware now wraps array handler responses into Response so X-RateLimit-Limit/Remaining/Reset headers survive even when the handler returns plain data. Existing Response and WP_REST_Response-style responses are unchanged.

Version marker

  • BetterRoute\Support\Version::VERSION reports 0.5.0-dev on the development branch and 0.5.0 on the tagged release.

Files added

  • src/Middleware/Write/AtomicIdempotencyMiddleware.php
  • src/Middleware/Write/AtomicIdempotencyStoreInterface.php
  • src/Middleware/Write/AtomicIdempotencyRecord.php
  • src/Middleware/Write/ArrayAtomicIdempotencyStore.php
  • src/Middleware/Write/WpdbAtomicIdempotencyStore.php
  • src/Middleware/Cors/CorsMiddleware.php
  • src/Middleware/Cors/CorsPolicy.php
  • src/Middleware/Auth/OwnershipGuardMiddleware.php
  • src/Middleware/Audit/AuditEnricherMiddleware.php
  • src/Resource/OwnedResourcePolicy.php

Files changed

  • src/Middleware/Audit/AuditMiddleware.php — merges safe audit attribute into events
  • src/Middleware/RateLimit/RateLimitMiddleware.php — wraps array responses
  • src/Router/Router.phpoptions() route, OPTIONS permission default
  • src/OpenApi/OpenApiExporter.php — minor follow-ups for the new attributes
  • src/Support/Version.php0.5.0-dev

Compared to 0.4.0

  • IdempotencyMiddleware (0.3.0) writes the store after handler execution. AtomicIdempotencyMiddleware (0.5.0) reserves before handler execution — pick it when concurrent duplicate execution must not happen (charges, external calls, sends).
  • ResourcePolicy (0.3.0) presets covered admin-only / public-read / capability / callback. 0.5.0 adds OwnedResourcePolicy::currentUserOwns() for the explicit "user owns this row" pattern.
  • 0.4.0 made raw Router writes deny-by-default. 0.5.0 keeps that behavior and adds explicit OPTIONS route handling for preflight.
  • 0.5.0 does not replace token issuance, refresh-token rotation, or login flows. Auth middlewares still verify credentials/tokens supplied by another layer.
  • Woo routes remain admin/integration CRUD routes. Customer-owned APIs should use raw Router routes plus auth, ownership guard, atomic idempotency, optimistic lock, audit enrichment, and explicit CORS policy as needed.

Breaking change checklist

None. 0.5.0 is purely additive.

ChangeAction
New middlewares are opt-inAdd them to a route or group when needed
Router::options() is newExisting code is unaffected
AuditMiddleware reads audit attributeIf you were already writing to that key, the value is now merged into events — expected to be safe but worth noting