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\AtomicIdempotencyMiddlewareBetterRoute\Middleware\Write\AtomicIdempotencyStoreInterfaceBetterRoute\Middleware\Write\AtomicIdempotencyRecordBetterRoute\Middleware\Write\ArrayAtomicIdempotencyStore(tests / non-production)BetterRoute\Middleware\Write\WpdbAtomicIdempotencyStore(production,INSERT IGNOREreservation, 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: truewhen the saved response is aResponse; - 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\CorsMiddlewareBetterRoute\Middleware\Cors\CorsPolicyRouter::options()and a public-by-default permission forOPTIONSroutes (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 anownerResolver(RequestContext): int|string|nullagainst the auth context (auth.userId/auth.subject) or current WP user. OptionalbypassCapabilityfor admin overrides.deniedStatusdefaults to404(recommended — does not leak existence) but can be403.BetterRoute\Resource\OwnedResourcePolicy::currentUserOwns()— Resource DSL preset that emitspermissionsforget/update/delete(configurable viaownedActions) plus an optionallistpermission for authenticated users only. Bypass capability defaults tomanage_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:
AuditMiddlewarenow readsRequestContext::$attributes['audit'](associative array) and merges it into the emitted event under the existing event schema.BetterRoute\Middleware\Audit\AuditEnricherMiddlewareis a thin enricher that adds:authProvider,authUserId,authSubjectfrom the auth attribute (only fields actually present);idempotencyKey(SHA-1 hashed — never stored raw);- optional
clientIpviaClientIpResolverwhenincludeClientIp: true; - any
staticFieldsyou 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::VERSIONreports0.5.0-devon the development branch and0.5.0on the tagged release.
Files added
src/Middleware/Write/AtomicIdempotencyMiddleware.phpsrc/Middleware/Write/AtomicIdempotencyStoreInterface.phpsrc/Middleware/Write/AtomicIdempotencyRecord.phpsrc/Middleware/Write/ArrayAtomicIdempotencyStore.phpsrc/Middleware/Write/WpdbAtomicIdempotencyStore.phpsrc/Middleware/Cors/CorsMiddleware.phpsrc/Middleware/Cors/CorsPolicy.phpsrc/Middleware/Auth/OwnershipGuardMiddleware.phpsrc/Middleware/Audit/AuditEnricherMiddleware.phpsrc/Resource/OwnedResourcePolicy.php
Files changed
src/Middleware/Audit/AuditMiddleware.php— merges safeauditattribute into eventssrc/Middleware/RateLimit/RateLimitMiddleware.php— wraps array responsessrc/Router/Router.php—options()route,OPTIONSpermission defaultsrc/OpenApi/OpenApiExporter.php— minor follow-ups for the new attributessrc/Support/Version.php—0.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 addsOwnedResourcePolicy::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
OPTIONSroute 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
Routerroutes 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.
| Change | Action |
|---|---|
| New middlewares are opt-in | Add them to a route or group when needed |
Router::options() is new | Existing code is unaffected |
AuditMiddleware reads audit attribute | If you were already writing to that key, the value is now merged into events — expected to be safe but worth noting |