This batch comes from dogfooding: the Playstack dashboard and API were built by composing the packages, and every place that needed an app-level patch became a package fix instead.
Split-origin sessions
@playstack/auth-react now documents and supports the bearer contract end to
end. The transport accepts a plain-http loopback baseUrl for local
development, useAuth() exposes refresh(), and the split-origin guidance no
longer expects a CSRF header: a cross-origin page cannot read a cookie set on
the API origin, so the API checks Origin and Sec-Fetch-Site instead.
@playstack/accounts-react accepts a sessionParser on the provider and any
callback as onSessionChange, so an account switch with the bearer transport
is one line: pass refresh and the transport adopts the rotated refresh
cookie.
Smaller edges
@playstack/auth reads users without opening a transaction. The WebCrypto
interfaces of @playstack/crypto and @playstack/api-keys are satisfied by
globalThis.crypto everywhere without casts. CaptureMailProvider in
@playstack/mail/testing gains rawMessages() and templateMessages().
@playstack/nest-feedback takes a clientAddress policy for hosts behind a
proxy. Revocation in @playstack/api-keys is documented as idempotent, and
@playstack/events and @playstack/audit document when an outbox row is
written and how audit actions are named.
Feedback from the CLI
playstack feedback sends a bug report, idea, or question, and after a
failed command playstack feedback --last attaches the redacted failure
record. @playstack/feedback owns the report contract and durable intake;
@playstack/nest-feedback serves it at POST /v1/feedback.
Registry access and licensing
Free packages are MIT and install from public npm. Paid packages are served from the Playstack registry under the Playstack Pro License, with account registry tokens issued from the dashboard, and access to releases published while a subscription was active is retained after it ends.
One shared memory transaction
@playstack/core/testing now owns the in-memory unit of work every package's
memory persistence adapter joins. A transaction opened through one package's
adapter can be passed to another package's adapter and both commit or roll
back together, so composed flows can be tested without a database. Every
memory adapter exposes state(transaction?).
Registered devices by injection
@playstack/devices now satisfies the DeviceSessionValidator contract of
@playstack/auth directly through assertActive, so deviceValidator: devices needs no adapter. @playstack/nest-auth accepts the service through
Nest injection: provide it under NEST_DEVICE_SESSION_VALIDATOR or as the
devices option, and pass sessions as a factory that receives it. The auth
guard answers a validation failure thrown by the strategy, such as a revoked
device, with 401, and resolves its Reflector explicitly so builds without
emitted decorator metadata keep working.
Bearer clients on split-origin routes
@playstack/nest-auth gains PlaystackOriginGuard, the defence for the
split-origin profile where no CSRF cookie can be read: unsafe browser
requests must present an allowed Origin and non-cross-site Fetch Metadata,
while a request with no Origin, no Fetch Metadata and no cookie carries no
ambient credential and passes. CLIs, mobile apps and servers can therefore
call the same identity, account and member routes with a bearer token, and
isBrowserRequest lets a host choose a transport per client, for example
returning the whole token pair in the body to a client that cannot hold a
cookie. The four other Nest bindings that read route metadata now inject
Reflector explicitly, so they work in builds without decorator metadata.
Guards composed by the application
@playstack/nest-auth adds PlaystackGuardStack: name a default
composition per transport profile (guards: 'cookie' or 'bearer') or list
your own ordering of package guards, other Playstack bindings and
application guards, then place the stack on a controller or install it
globally with globalGuards: true and @Public() opt-outs. Each guard is
resolved once from the application's providers, evaluated in order, and the
first denial ends the request; a bad composition fails at bootstrap. The
individual guards stay exported, so hand-placed layering keeps working and
no package installs a guard on its own.