$npx -y skills add Lonsdale201/wp-agent-skills --skill br-install-and-migrateInstall better-route from Packagist or migrate a WordPress integration to better-route 1.1. Use when adding better-route/better-route, changing the Composer constraint to ^1.1, upgrading from 1.0 or pre-1.0 releases, diagnosing new 403 route responses, migrating atomic idempotenc
| 1 | # better-route: install and migrate to 1.1 |
| 2 | |
| 3 | ## Install |
| 4 | |
| 5 | Require the stable 1.1 line directly from Packagist: |
| 6 | |
| 7 | ```bash |
| 8 | composer require better-route/better-route:^1.1 |
| 9 | composer show better-route/better-route |
| 10 | ``` |
| 11 | |
| 12 | Use PHP 8.1 or newer. Do not add a VCS repository unless intentionally consuming a fork or unreleased branch. |
| 13 | |
| 14 | Register routes during `rest_api_init`: |
| 15 | |
| 16 | ```php |
| 17 | use BetterRoute\Router\Router; |
| 18 | |
| 19 | add_action('rest_api_init', static function (): void { |
| 20 | $router = Router::make('myapp', 'v1'); |
| 21 | $router->get('/ping', static fn (): array => ['pong' => true]) |
| 22 | ->publicRoute(); |
| 23 | $router->register(); |
| 24 | }); |
| 25 | ``` |
| 26 | |
| 27 | ## 1.1 migration checklist |
| 28 | |
| 29 | Treat these as consumer-visible changes when moving from `^1.0` to `^1.1`. |
| 30 | |
| 31 | ### Routing |
| 32 | |
| 33 | - Add `permission()`, `protectedByMiddleware()`, or `publicRoute()` to every raw Router route. In 1.1 omitted intent denies `GET` and `OPTIONS` too, not only writes. |
| 34 | - Register only during `rest_api_init`. Registration outside the hook or a `false` result from WordPress now throws clearly. |
| 35 | - Use WordPress route regex such as `/(?P<id>\d+)`, not `{id}`. |
| 36 | - Review one-parameter handlers: an untyped parameter receives the WP request; a `RequestContext`-compatible type receives the context. |
| 37 | - Keep route `args` validation cheap and side-effect free because WordPress performs it before `permission_callback`. |
| 38 | |
| 39 | ### Identity, cache, and throttling |
| 40 | |
| 41 | - Expect default cache, idempotency, and rate-limit keys to use a native logged-in WordPress user even when no auth middleware populated `attributes['auth']`. |
| 42 | - Expect structured, recursively canonicalized keys and fingerprints. Do not depend on old delimiter-concatenated key strings. |
| 43 | - Use `WpObjectCacheRateLimiter` only with a persistent external object cache that supports atomic `wp_cache_incr()`. |
| 44 | - Use the default `TransientRateLimiter` only where MySQL named locks are available; it now serializes the transient read/modify/write instead of racing. |
| 45 | - Read `Retry-After` as well as `X-RateLimit-*` on `429` responses. |
| 46 | |
| 47 | ### Idempotency and optimistic locking |
| 48 | |
| 49 | - Re-run `WpdbAtomicIdempotencyStore::installSchema()` during deployment/activation. The 1.1 schema adds `reservation_token` and migrates an existing table. |
| 50 | - Do not release an uncertain atomic reservation after a throwable unless duplicate execution is demonstrably safe. `releaseOnThrowable` now defaults to `false`. |
| 51 | - Keep idempotency keys at or below the configured `maxKeyLength` (default 200) and printable ASCII. |
| 52 | - Store data-only responses. Better Route serializes no arbitrary PHP classes; `WP_REST_Response` is converted to a safe Better Route response and returned `WP_Error` values are not stored. |
| 53 | - Understand that optimistic locking serializes cooperating Better Route writers with a MySQL advisory lock. External writers must use the same protocol or a storage-level conditional update. |
| 54 | |
| 55 | ### Resource DSL |
| 56 | |
| 57 | - Treat omitted `allow()` as full CRUD and explicit `allow([])` as no routes. Unsupported action names now throw. |
| 58 | - Never call both `sourceCpt()` and `sourceTable()` on one Resource; 1.1 rejects the combination. |
| 59 | - Review CPT exposure. Default reads only allow a publicly viewable post type and visible status; password/private read data fails closed. |
| 60 | - Avoid arbitrary per-item `cptVisibilityPolicy()` callbacks on large datasets. Accurate visible totals require scanning all matched pages; prefer a query-level repository condition. |
| 61 | - Keep `defaultPerPage <= maxPerPage`; validation is performed on final registration state, so fluent setter order no longer changes validity. |
| 62 | - Expect custom-table null payloads to become SQL `NULL`, default ordering to use the primary key, and non-primary sorts to add the primary key as a stable tie-breaker. |
| 63 | - Strict list parsers accept WordPress global REST parameters `_locale`, `_fields`, `_embed`, `_envelope`, and `_jsonp`; other unknown parameters still fail. |
| 64 | |
| 65 | ### CORS, ETag, errors, and telemetry |
| 66 | |
| 67 | - Attach `CorsMiddleware` to matched routes and explicitly mark any raw `OPTIONS` route public. The 1.1 WordPress bridge handles preflight before dispatch and replaces core CORS headers for those routes. |
| 68 | - Validate configured CORS origins/methods/header names; wildcard origin plus credentials remains invalid. |
| 69 | - Expect ETag matching to support weak validators, |