@stratal/framework 0.0.27 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +596 -0
  2. package/README.md +166 -24
  3. package/dist/access-control/index.d.mts +43 -16
  4. package/dist/access-control/index.d.mts.map +1 -1
  5. package/dist/access-control/index.mjs +5 -5
  6. package/dist/access-control/index.mjs.map +1 -1
  7. package/dist/{access.service-BmDhE-re.mjs → access.service-BjmnWBEo.mjs} +35 -17
  8. package/dist/access.service-BjmnWBEo.mjs.map +1 -0
  9. package/dist/auth/index.d.mts +105 -103
  10. package/dist/auth/index.d.mts.map +1 -1
  11. package/dist/auth/index.mjs +123 -24
  12. package/dist/auth/index.mjs.map +1 -1
  13. package/dist/{auth-context-CGVbiSX3.d.mts → auth-context-C1om3Zsr.d.mts} +1 -2
  14. package/dist/auth-context-C1om3Zsr.d.mts.map +1 -0
  15. package/dist/{auth-context-C8NBfiMa.mjs → auth-context-cNSS1rmh.mjs} +2 -2
  16. package/dist/{auth-context-C8NBfiMa.mjs.map → auth-context-cNSS1rmh.mjs.map} +1 -1
  17. package/dist/auth.service-Onf3JkyL.d.mts +44 -0
  18. package/dist/auth.service-Onf3JkyL.d.mts.map +1 -0
  19. package/dist/context/index.d.mts +4 -5
  20. package/dist/context/index.d.mts.map +1 -1
  21. package/dist/context/index.mjs +1 -1
  22. package/dist/database/index.d.mts +3 -3
  23. package/dist/database/index.mjs +413 -34
  24. package/dist/database/index.mjs.map +1 -1
  25. package/dist/{decorate-B7nr7eBl.mjs → decorate-RQD1h28J.mjs} +1 -1
  26. package/dist/{decorateParam-DwV9LSPl.mjs → decorateParam-xwTkq9gO.mjs} +2 -2
  27. package/dist/{decorateParam-DwV9LSPl.mjs.map → decorateParam-xwTkq9gO.mjs.map} +1 -1
  28. package/dist/factory/index.d.mts +3 -5
  29. package/dist/factory/index.d.mts.map +1 -1
  30. package/dist/factory/index.mjs.map +1 -1
  31. package/dist/guards/index.d.mts +3 -4
  32. package/dist/guards/index.d.mts.map +1 -1
  33. package/dist/guards/index.mjs +4 -4
  34. package/dist/guards/index.mjs.map +1 -1
  35. package/dist/index-e_u1SRyd.d.mts +921 -0
  36. package/dist/index-e_u1SRyd.d.mts.map +1 -0
  37. package/dist/index.d.mts +1 -1
  38. package/dist/{types-CWZ9q74G.d.mts → types-B35g-lXi.d.mts} +18 -4
  39. package/dist/types-B35g-lXi.d.mts.map +1 -0
  40. package/package.json +31 -27
  41. package/dist/access.service-BmDhE-re.mjs.map +0 -1
  42. package/dist/auth-context-CGVbiSX3.d.mts.map +0 -1
  43. package/dist/index-Dt0YUA7r.d.mts +0 -446
  44. package/dist/index-Dt0YUA7r.d.mts.map +0 -1
  45. package/dist/types-CWZ9q74G.d.mts.map +0 -1
  46. package/dist/types-DabF8LGz.d.mts +0 -11
  47. package/dist/types-DabF8LGz.d.mts.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,596 @@
1
+ # @stratal/framework
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a753e55: Add cursor pagination, share permissions with the client for Inertia access control, and add a Workers-safe database pool factory.
8
+
9
+ ### Cursor pagination
10
+
11
+ Add `db.$cursor` for reading a list one page at a time, positioned by an opaque cursor rather than an offset.
12
+
13
+ ```typescript
14
+ const page = await db.$cursor.thread.findMany({
15
+ cursor: ctx.query("cursor"),
16
+ take: 20,
17
+ orderBy: [{ updatedAt: "desc" }, { id: "desc" }],
18
+ where: { userId },
19
+ });
20
+ // → { data, perPage, cursorName, cursor, nextCursor, prevCursor }
21
+ ```
22
+
23
+ - Rows added, removed or updated around the reader do not shift the page, so walking a list neither skips a row nor repeats one.
24
+ - `orderBy` is required and must end in a unique column, so tied rows do not share a position. `where`, `select`, `include` and `omit` work as on the model's own `findMany`, and `select` narrows the result type.
25
+ - Pass the result to `@stratal/inertia`'s `ctx.scroll()` as it is, or return it from a JSON route. Cursors are opaque — pass back the one a result gave you.
26
+ - A transaction client carries the same reader, and `db.$cursor.$from({ findMany }, …)` pages a `UNION` or a raw statement.
27
+ - Distinct from ZenStack's own `cursor` argument, which is offset-based and correct only while the list is unchanged.
28
+
29
+ ### Access control and auth
30
+ - Share the current user's permissions and roles automatically once `accessControl` is configured, so the client can gate on them. This backs the `<Can>`, `<Cannot>`, `<HasRole>` and `<HasNoRole>` components and the `useCan`, `useRole` and `useAccess` hooks in `@stratal/inertia`, with permission strings and role names type-checked against a generated registry.
31
+ - Add `AUTH_GATEWAY_PRIMERS`, exported from `@stratal/framework/auth`, so guarded and per-tenant routes can use `@Cacheable({ partitionBy: [...] })`. The response-cache gateway resolves partitions outside the app's middleware chain, so a resolver calling `ctx.user()` would otherwise throw on every request:
32
+
33
+ ```typescript
34
+ ResponseCacheModule.forRoot({
35
+ gateway: { entrypoint: "Cached" },
36
+ primers: AUTH_GATEWAY_PRIMERS,
37
+ partitions: { user: (ctx) => ctx.user().id },
38
+ });
39
+ ```
40
+
41
+ - Carry the cookies a session read issues through to the response. The `Set-Cookie` Better Auth writes while reading a session was previously discarded, so under `session.cookieCache` the cached-session cookie was minted on every request and reached the browser on none, and a session passing `session.updateAge` never delivered its extended expiry — a browser's copy expired on the schedule it was first given rather than sliding. Cookie names the handler has already written are left alone, so sign-out still clears them.
42
+ - Fix role reads and writes failing for any app whose ZenStack user model is not named exactly `User`. Setting a user's role, reading another user's roles, checking a permission and listing a user's permissions all threw when the model resolved to a different accessor, such as a pluralized `Users`. Changing a role now also refreshes that user's sessions, so it takes effect immediately.
43
+ - Adapt the Better Auth rate-limit bridge to the new atomic `consume` storage. `createBetterAuthRateLimitStorage()` now returns `{ consume }`, and records expire after the rule's own window instead of a fixed day, so stale counters no longer linger in KV. Accuracy follows the configured store, exactly as Stratal's own throttling does: exact in memory, best-effort on KV, where concurrent writes from different edge locations may undercount. **If you pass your own `rateLimit.customStorage`, it must now implement `consume`** — Better Auth no longer accepts `get`/`set`.
44
+
45
+ ### Database
46
+ - Add `createPoolFactory(env, makePool)` to `@stratal/framework/database`, which chooses connection topology from the environment instead of hard-coding it. Write `const pool = createPoolFactory(env, () => new Pool(config))`, then `dialect: () => new PostgresDialect({ pool })`. By default it returns a fresh pool per resolution, which is mandatory on the Workers runtime, where a pool opened in one request's I/O context cannot be reused by a later one without the runtime cancelling the cross-request I/O and hanging the request. The pool is created lazily on first query, so nothing opens a socket at module scope. In production Hyperdrive fronts these pools, so they never accumulate.
47
+ - Resolve a connection's database client once per request instead of once per injection. The client was transient, so a request resolving a controller, a guard and four services built six clients over six pools for work that shares a single I/O context. Every entrypoint already runs inside a request scope, so no caller changes. Sharing one client also makes the reentrant-`$transaction` guard effective across services, where separate clients could previously deadlock on a small pool.
48
+ - Await the configuration factory in `DatabaseModule.forRootAsync`. A factory that actually returned a promise handed initialization a `Promise` and it walked `undefined` connections. An asynchronous factory now works as documented, which is what lets a consumer put a generated schema behind an `import()` rather than evaluating a large schema module while the isolate starts.
49
+ - Make disposing a shared test-harness database connection idempotent, so shutdown no longer logs "Called end on pool more than once". Fresh-per-resolution pools used in dev, staging and production are unchanged.
50
+
51
+ ### Breaking Changes
52
+ - **The validation API is `zod/mini`.** The `z` re-export is gone from the validation surface this package re-exports. Import schema builders directly from `zod/mini` using named imports and replace classic chaining with the functional API: `z.string().min(1).optional()` becomes `optional(string().check(minLength(1)))`. Use `describe()` and `named()` from `stratal/validation` for descriptions and OpenAPI component ids.
53
+ - **OpenAPI documents are generated lazily**, on the first request to the docs endpoint. `OpenAPIService.getSpec()` becomes `getSpec(container)` and is async, and `routeFilter` is now a metadata predicate `(route: RouteSchemaMeta) => boolean` instead of `(path, pathItem)`.
54
+ - **Guards now deny when `canActivate` returns `false`**, with `GuardRejectedError` (403), instead of the return value being ignored. Audit your `canActivate` implementations before upgrading — requests that previously reached the handler now 403. `GuardRejectedError` is re-exported from `@stratal/framework/guards`, so apps that standardise on that path can `instanceof` it without a second import.
55
+ - **A custom Better Auth `rateLimit.customStorage` must implement `consume`**, replacing the previous `get`/`set` pair.
56
+
57
+ ### Patch Changes
58
+
59
+ - Updated dependencies [a753e55]
60
+ - stratal@0.1.0
61
+
62
+ ## 0.0.27
63
+
64
+ ### Patch Changes
65
+
66
+ - Updated dependencies [41a9140]
67
+ - stratal@0.0.27
68
+
69
+ ## 0.0.26
70
+
71
+ ### Patch Changes
72
+
73
+ - ab95f52: Close database connections on application shutdown
74
+
75
+ ### Details
76
+ - Database clients now disconnect their underlying pools when the application shuts down (including dev-server hot reloads), instead of leaking connections until the process exits
77
+ - Database clients also implement the async-disposal contract (`Symbol.asyncDispose`), so they participate in container disposal
78
+
79
+ - Updated dependencies [ab95f52]
80
+ - Updated dependencies [bb6d3b9]
81
+ - stratal@0.0.26
82
+
83
+ ## 0.0.25
84
+
85
+ ### Patch Changes
86
+
87
+ - e93db60: Emit entity mutation events with full entity snapshots from the database layer
88
+
89
+ ### Details
90
+ - New typed events: `entity.{Model}.created` (`{ after }`), `entity.{Model}.updated` (`{ before, after }`), and `entity.{Model}.deleted` (`{ before }`), plus wildcard subscriptions (`entity.{Model}`, `entity.{verb}`, `entity`).
91
+ - Unlike the existing `before.*`/`after.*` events (raw query args/result), entity events carry full entity snapshots, with the pre-mutation snapshot loaded inside the mutation's transaction.
92
+ - Listener-driven cost: snapshots are only loaded when a matching subscription exists, so models nobody observes pay nothing. A global `entity` wildcard makes every model pay the pre-read — subscribe per model when cost matters.
93
+
94
+ - Updated dependencies [e93db60]
95
+ - stratal@0.0.25
96
+
97
+ ## 0.0.24
98
+
99
+ ### Patch Changes
100
+
101
+ - stratal@0.0.24
102
+
103
+ ## 0.0.23
104
+
105
+ ### Patch Changes
106
+
107
+ - 13b0e8d: Add `@stratal/feature-flags` — Cloudflare Flagship feature flags via the native Worker binding API.
108
+ - `FeatureFlagModule.forRoot({ apps: [{ binding, flags }], default, context })` with a declare-once flag manifest, manifest defaults, a per-request evaluation-context resolver, and multi-app support via `FeatureFlagService.use(binding)`.
109
+ - `FeatureFlagShareMiddleware` shares evaluated flags to Inertia pages as the `featureFlags` prop; register it yourself (scoped to page controllers via `router.middleware(...)` or app-wide via `router.use(...)`) so a stalled Flagship binding can't block unrelated routes. Typed `useFlag` / `useFeatureFlags` hooks on `@stratal/feature-flags/react`. No runtime dependency on `@stratal/inertia`.
110
+ - `@stratal/inertia`: expose a generic `ctx.share(key, value)` macro on `RouterContext` so middleware and packages can contribute per-request shared props.
111
+ - `@stratal/framework`: add a `ctx.user()` macro on `RouterContext` (shorthand for `AuthContext.requireUser()`).
112
+
113
+ - 13b0e8d: Fix correctness and security issues found in review.
114
+
115
+ Queue:
116
+ - Retry the correct binding: dispatch stamps the producer binding into message metadata and failed jobs record it, so `queue:retry` re-enqueues through the Cloudflare binding instead of the queue name (which is not a valid binding key and broke retry whenever the two differed). A message with no binding metadata is logged and acked rather than stored as an unretryable job.
117
+ - Honor the documented retry budget: `maxRetries` now counts retries correctly against Cloudflare's 1-based `message.attempts` (previously gave one fewer retry than configured).
118
+ - Derive idempotency keys from an order-stable serialization of `type` + `payload`, so payloads that differ only in key order dedupe correctly.
119
+ - `queue:retry --all` / `queue:purge --all --queue` collect matching keys before deleting, so cursor pagination no longer skips jobs; `queue:failed --queue --limit` now counts matching jobs rather than scanned keys.
120
+ - Documented that delivery is at-least-once with best-effort de-duplication (not exactly-once), since the processed marker is written only after a handler succeeds and KV is eventually consistent — handlers must be idempotent.
121
+
122
+ Email (SMTP):
123
+ - Upgrade STARTTLS onto the socket `startTls()` returns: the original socket is closed by the runtime, so the post-upgrade reader/writer are re-derived from the new secure socket and any pre-handshake bytes are discarded (fixes a broken `smtp://` STARTTLS path on real Workers and closes the STARTTLS plaintext-injection vector).
124
+ - Refuse to send credentials over an unencrypted connection: an `smtp://` server that doesn't offer STARTTLS now fails loudly instead of leaking the password (blocks STARTTLS-stripping downgrades). Credential-free connections (e.g. local Mailpit) are unaffected.
125
+ - AUTH is gated on the server's advertised mechanisms and supports both `PLAIN` and `LOGIN`; usernames are percent-decoded like passwords.
126
+ - Add a response timeout so a hung SMTP server can't wedge the worker; QUIT/socket close are now best-effort and never mask a successful send.
127
+ - MIME builder strips CR/LF from headers, escapes/RFC 2231-encodes attachment filenames (prevents header injection), base64-encodes message bodies (fixes long-line corruption), and rejects envelope addresses containing whitespace or angle brackets (prevents `MAIL FROM`/`RCPT TO` desync).
128
+
129
+ Inertia SEO:
130
+ - `titleTemplate` substitutes every `%s` and treats `$`-sequences in the title literally.
131
+ - Inject head/body content via function replacements, so SEO/page content containing `$`-sequences (`$$`, `$&`, `` $` ``, `$'`) is no longer corrupted or able to splice a template placeholder back into the output.
132
+ - Drop unsafe attribute names — including inline event handlers (`on*`) — from custom `meta`/`link` entries (prevents tag breakout server-side, `setAttribute` errors during client head-sync, and developer-supplied event-handler attributes).
133
+
134
+ Feature flags:
135
+ - `FeatureFlagService.use()` binds the target app exactly once.
136
+
137
+ Database (framework):
138
+ - The reentrant `$transaction` proxy forwards the receiver for non-transaction property access.
139
+
140
+ Testing:
141
+ - `TestingModule.close()` drops the isolated per-file database even if shutdown throws; the stale-database sweep escapes LIKE metacharacters so a prefix containing `_` can't over-match.
142
+
143
+ DI:
144
+ - Construct singletons against the root container so they can never capture a request-scoped dependency (which would leak one request's state across every later request); an illegal singleton→request dependency now throws loudly.
145
+ - Detect circular dependencies and throw a clear error naming the cycle instead of overflowing the stack.
146
+ - `tryResolve` only swallows "no provider"; a registered provider that throws while constructing now surfaces the real error instead of injecting `undefined`.
147
+ - Request-cache invalidation tracks transitive constructor dependencies, so re-registering a value rebuilds cached services that depend on it through a transient intermediary.
148
+
149
+ Quarry dev runtime:
150
+ - Persist every durable plugin (KV, D1, R2, Durable Objects, cache) under `.wrangler/state/v3`, matching `wrangler dev` (previously only R2 was persisted); load `.env.local` / `.env.<env>.local` into `process.env` for full parity.
151
+ - The `cloudflare:sockets` STARTTLS shim re-attaches the stream error handler to the upgraded socket, so post-upgrade connection errors still surface.
152
+
153
+ - 13b0e8d: Make database transactions reentrant and remove `AuthContextMiddleware`
154
+ - Nested `$transaction` calls now reuse the active transaction instead of acquiring a second connection, fixing deadlocks on single-connection pools (e.g. Hyperdrive with `max: 1`) when libraries such as Better Auth run nested transactions.
155
+
156
+ ### Breaking Changes
157
+ - **`AuthContextMiddleware` is removed.** Auth context is now registered automatically per request. If you registered this middleware explicitly, remove the registration — `SessionVerificationMiddleware` is sufficient.
158
+
159
+ - Updated dependencies [13b0e8d]
160
+ - Updated dependencies [13b0e8d]
161
+ - Updated dependencies [13b0e8d]
162
+ - Updated dependencies [13b0e8d]
163
+ - Updated dependencies [13b0e8d]
164
+ - Updated dependencies [13b0e8d]
165
+ - Updated dependencies [13b0e8d]
166
+ - Updated dependencies [13b0e8d]
167
+ - Updated dependencies [13b0e8d]
168
+ - Updated dependencies [be813bc]
169
+ - stratal@0.0.23
170
+
171
+ ## 0.0.22
172
+
173
+ ### Patch Changes
174
+
175
+ - 1658945: Migrate all error classes to `HttpException`, move heavy dependencies to peer dependencies
176
+
177
+ ### Breaking Changes
178
+ - **Error classes migrated** — All framework error classes (`InsufficientPermissionsError`, auth errors, database errors, context errors) now extend `HttpException` instead of `ApplicationError`. Constructor signatures are simplified — remove `i18nKey` and `code` arguments.
179
+ - **`@better-auth/core`, `@zenstackhq/orm`, `@zenstackhq/schema`, and `better-auth` moved to peer dependencies** — Install them directly in your application if not already present.
180
+ - **Database error mapping simplified** — `fromZenStackError()` no longer maps to typed error code objects. It returns plain `HttpException` instances with descriptive messages.
181
+
182
+ - 4b273ea: Adapt to the new built-in DI container from `stratal`, removing all `tsyringe` and `reflect-metadata` usage
183
+ - All request-scoped services now use the `@Request` decorator instead of `@Transient`.
184
+ - `DatabaseModule` uses `lazy()` for dynamic connection registration instead of tsyringe's `delay()`.
185
+ - `reflect-metadata` is no longer required as a peer dependency.
186
+
187
+ - Updated dependencies [1658945]
188
+ - Updated dependencies [4b273ea]
189
+ - stratal@0.0.22
190
+
191
+ ## 0.0.21
192
+
193
+ ### Patch Changes
194
+
195
+ - 3489cfd: Require `name` on `AuthUser`
196
+
197
+ `AuthUser` now extends Better Auth's `BaseUser` directly, so `name` is required again (it was temporarily made optional in `0.0.20`). Apps whose schema stores `firstName`/`lastName` instead of a `name` column should expose `name` through a [ZenStack result extension](https://zenstack.dev/docs/orm/plugins/extending-orm-client#adding-fields-to-query-results) so reads return a populated `name` for free, rather than relying on `name` being absent.
198
+
199
+ - Updated dependencies [3489cfd]
200
+ - Updated dependencies [3489cfd]
201
+ - stratal@0.0.21
202
+
203
+ ## 0.0.20
204
+
205
+ ### Patch Changes
206
+
207
+ - f8c61e1: Auto-wire Better Auth's rate limiting through Stratal's `RateLimiterModule`
208
+
209
+ When `RateLimiterModule` is imported alongside `AuthModule`, Better Auth's `rateLimit` block is configured automatically:
210
+ - `customStorage` is backed by Stratal's shared `IRateLimiterStore`, so HTTP throttling and Better Auth share one store.
211
+ - `customRules` is populated from a new `RateLimiterRegistry.forPath(path, resolver)` API, letting apps declare path-keyed limits (e.g. `/sign-in/email`, `/two-factor/*`) using the same `Limit` builder used elsewhere.
212
+ - User-supplied `rateLimit.customStorage` and `rateLimit.customRules` keys take precedence on a per-key basis.
213
+
214
+ ```ts
215
+ limiter.forPath("/sign-in/email", () => Limit.perSeconds(10, 3));
216
+ limiter.forPath("/forget-password", () => Limit.none()); // disabled
217
+ ```
218
+
219
+ Path-keyed entries are scoped per-IP+path by Better Auth (`Limit.by(...)` is ignored), and multiple `Limit`s reduce to the most restrictive (smallest `max / windowSeconds`).
220
+
221
+ - f8c61e1: Store the full authenticated user on `AuthContext`
222
+
223
+ `AuthContext` now holds the full user record returned by Better Auth's `getSession()` instead of just `userId`/`role`, so controllers and services can read profile fields without re-querying the database.
224
+
225
+ ### Breaking Changes
226
+ - `AuthInfo` shape changed from `{ userId?, role? }` to `{ user: AuthUser }`. `setAuthContext({ userId, role })` callers must pass `setAuthContext({ user })` instead.
227
+ - `getAuthContext()` was renamed to `getAuthInfo()` and now returns `{ user }`.
228
+ - `AuthContext.getRole()` reads from `user.role`. Apps that use roles should augment the new `AuthUser` interface with `role: string` (or your app's role field) so it stays typed.
229
+
230
+ ### New API
231
+ - `AuthUser` interface (extends Better Auth's `BaseUser` with optional `name`) is augmentable via `declare module '@stratal/framework/context'` for app-specific fields.
232
+ - `AuthContext.getUser()` returns the user or `undefined`.
233
+ - `AuthContext.requireUser()` returns the user or throws `UserNotAuthenticatedError`.
234
+
235
+ ### Migration
236
+
237
+ ```ts
238
+ // Before
239
+ const userId = authContext.getAuthContext().userId;
240
+ authContext.setAuthContext({
241
+ userId: session.user.id,
242
+ role: session.user.role,
243
+ });
244
+
245
+ // After
246
+ const user = authContext.requireUser();
247
+ authContext.setAuthContext({ user: session.user });
248
+ ```
249
+
250
+ - f8c61e1: Add `better-call@1.3.5` as a direct dependency
251
+
252
+ `@better-auth/core@1.6.9` declares `better-call` as a peer dependency but does not install it itself, so the framework — its direct consumer — is responsible for providing it. Without it, stricter resolvers (e.g. Cloudflare's workerd vitest pool) fail to resolve `better-call/error` from `@better-auth/core`.
253
+
254
+ - f8c61e1: Support `@computed` fields in `DatabaseModule` connection config
255
+
256
+ `DatabaseConnectionConfig` accepts a new optional `computedFields` map that is forwarded to the underlying ZenStack client. ZenStack 3+ requires this whenever the schema declares any `@computed` fields; previously the connection failed to construct.
257
+
258
+ - Updated dependencies [f8c61e1]
259
+ - Updated dependencies [f8c61e1]
260
+ - Updated dependencies [f8c61e1]
261
+ - Updated dependencies [f8c61e1]
262
+ - Updated dependencies [f8c61e1]
263
+ - stratal@0.0.20
264
+
265
+ ## 0.0.19
266
+
267
+ ### Patch Changes
268
+
269
+ - 3b16f5b: Replace Casbin-based RBAC module with Better Auth access control module
270
+
271
+ ### Breaking Changes
272
+ - The `RbacModule`, `CasbinService`, `CasbinEnforcerService`, and all Casbin-related exports under `@stratal/framework/rbac` have been removed.
273
+ - Use the new `@stratal/framework/access-control` module instead, which integrates with Better Auth's built-in access control system.
274
+ - `AuthGuard` now uses `AccessService` instead of `CasbinService` for permission checks.
275
+
276
+ ### Migration
277
+ 1. Replace `RbacModule` imports with the new access control setup via `createAccessControl()`.
278
+ 2. Define resources and roles using `createAccessControl({ resources, roles })` and pass the result to `AuthModule.forRootAsync()`.
279
+ 3. Replace `CasbinService` usage with `AccessService` from `@stratal/framework/access-control`.
280
+
281
+ - 3b16f5b: Add organization-related error handling and internationalization support for auth module
282
+ - Add structured error codes and i18n messages for organization operations (not found, member not found, invitation errors, limit reached).
283
+ - Enhance Better Auth error handler to map organization-specific errors to appropriate HTTP responses.
284
+
285
+ - 5d26c24: Rearchitect i18n module augmentation to a per-module keyed registry (breaking change)
286
+
287
+ **Why:** Multiple modules augmenting `AppMessages` with a shared top-level parent (e.g., `errors.auth`, `errors.uploads`, `errors.branding`) collided with TypeScript error **TS2717** ("Subsequent property declarations must have the same type"). Interface merging adds new properties across declarations but requires same-named properties to have structurally identical types — it does not deep-merge nested shapes.
288
+
289
+ **What changed:**
290
+ - Replaced the single augmentable `AppMessages` interface with an `AppMessageNamespaces` keyed registry. Each module declares its own distinct top-level key (Laravel-style package namespacing). Because each declaration adds a different property, interface merging accepts them all.
291
+ - `AppMessages` is now derived: `{ [K in keyof AppMessageNamespaces]: AppMessageNamespaces[K] }`.
292
+ - Access keys are unchanged dot-notation — `i18n.t('auth.errors.invalidCredentials')` — so no custom resolver is needed.
293
+
294
+ **Migration:**
295
+
296
+ Before:
297
+
298
+ ```ts
299
+ declare module "stratal/i18n" {
300
+ interface AppMessages {
301
+ errors: { uploads: { notFound: string } };
302
+ }
303
+ }
304
+ ```
305
+
306
+ After:
307
+
308
+ ```ts
309
+ declare module "stratal/i18n" {
310
+ interface AppMessageNamespaces {
311
+ uploads: { errors: { notFound: string } };
312
+ }
313
+ }
314
+ ```
315
+
316
+ **Framework package moves:**
317
+ - All `errors.auth.*` keys (previously split between `stratal` core and `@stratal/framework`) now live in the auth module as `auth.errors.*`. `errors.auth.org.*` → `auth.org.*`. The `errors.auth.*` namespace has been removed from `stratal`'s core messages.
318
+ - `@stratal/framework`'s `DatabaseModule` now registers its `database.*` validation messages via `I18nModule.registerMessages` (previously the messages file existed but was never wired up).
319
+ - `@stratal/inertia-modal`'s `errors.modal.*` key moved to `modal.errors.*`.
320
+
321
+ **Callsite updates required in downstream apps:**
322
+
323
+ ```ts
324
+ // Before
325
+ new ApplicationError('errors.auth.invalidCredentials', ...)
326
+ i18n.t('errors.auth.org.organizationNotFound')
327
+
328
+ // After
329
+ new ApplicationError('auth.errors.invalidCredentials', ...)
330
+ i18n.t('auth.org.organizationNotFound')
331
+ ```
332
+
333
+ No runtime API change: `I18nModule.registerMessages(messages)` keeps its existing signature, and deep-merge behavior is unchanged. Locale-only contributions that override core's built-in `errors.*` / `common.*` / etc. continue to work.
334
+
335
+ - Updated dependencies [3b16f5b]
336
+ - Updated dependencies [5d26c24]
337
+ - Updated dependencies [3b16f5b]
338
+ - Updated dependencies [3b16f5b]
339
+ - Updated dependencies [5d26c24]
340
+ - Updated dependencies [5d26c24]
341
+ - Updated dependencies [3b16f5b]
342
+ - stratal@0.0.19
343
+
344
+ ## 0.0.18
345
+
346
+ ### Patch Changes
347
+
348
+ - c9176ea: Migrate auth middleware to router-scoped configuration and improve error resilience
349
+
350
+ ### Details
351
+ - Migrate `AuthModule` from `MiddlewareConfigurable` to `RouteConfigurable` interface
352
+ - Add graceful error handling in session verification to prevent invalidated sessions from blocking requests
353
+ - Expand Better Auth error mapping for token expiry, signup, and session creation failures
354
+ - Use duck-typing for Better Auth `APIError` detection to handle bundler environments
355
+
356
+ - Updated dependencies [fcb71c4]
357
+ - Updated dependencies [17f8675]
358
+ - Updated dependencies [c9176ea]
359
+ - Updated dependencies [c9176ea]
360
+ - stratal@0.0.18
361
+
362
+ ## 0.0.17
363
+
364
+ ### Patch Changes
365
+
366
+ - [`cbfce8b`](https://github.com/strataljs/stratal/commit/cbfce8b3a3517b60d94f500c5dc1ef68d8ee76f4) Thanks [@adesege](https://github.com/adesege)! - Export database CLI commands from `@stratal/framework/database`
367
+
368
+ ### Details
369
+ - Export `ZenStackCommand`, `DbGenerateCommand`, `DbPullCommand`, `DbPushCommand`, `MigrateDeployCommand`, `MigrateDevCommand`, `MigrateResetCommand`, and `MigrateStatusCommand`
370
+
371
+ - [#147](https://github.com/strataljs/stratal/pull/147) [`7f2772b`](https://github.com/strataljs/stratal/commit/7f2772ba90a9b6a91603f79293d384e972864125) Thanks [@adesege](https://github.com/adesege)! - Fix database event types to correctly resolve models and operation args across multiple schema connections using distributive conditional types
372
+
373
+ - Updated dependencies [[`7f2772b`](https://github.com/strataljs/stratal/commit/7f2772ba90a9b6a91603f79293d384e972864125), [`6cccfef`](https://github.com/strataljs/stratal/commit/6cccfefdde703c5c6eaba199d05307ab9fe36085), [`79e05de`](https://github.com/strataljs/stratal/commit/79e05de7482c925323a2f37a00e47929133a979f), [`3c89c14`](https://github.com/strataljs/stratal/commit/3c89c147fca366382c0771bb442f29a6fc73601e), [`916fd90`](https://github.com/strataljs/stratal/commit/916fd90727a06b5ce7c0397467fe9dc1f859f841), [`cbfce8b`](https://github.com/strataljs/stratal/commit/cbfce8b3a3517b60d94f500c5dc1ef68d8ee76f4)]:
374
+ - stratal@0.0.17
375
+
376
+ ## 0.0.16
377
+
378
+ ### Patch Changes
379
+
380
+ - [#142](https://github.com/strataljs/stratal/pull/142) [`4b958e2`](https://github.com/strataljs/stratal/commit/4b958e250c99681a99a34a398fbf706546f556cc) Thanks [@adesege](https://github.com/adesege)! - Move auth, database, RBAC, and factory dependencies from optional peer dependencies to hard dependencies
381
+
382
+ ### Details
383
+ - `@better-auth/core`, `better-auth`, `@faker-js/faker`, `@zenstackhq/cli`, `@zenstackhq/orm`, and `casbin` are now direct dependencies
384
+ - Remove `peerDependenciesMeta` optional markers for these packages
385
+
386
+ - Updated dependencies [[`3dd0bc8`](https://github.com/strataljs/stratal/commit/3dd0bc84c8638db30db7b70f3532a44aa187ace8), [`4b958e2`](https://github.com/strataljs/stratal/commit/4b958e250c99681a99a34a398fbf706546f556cc)]:
387
+ - stratal@0.0.16
388
+
389
+ ## 0.0.15
390
+
391
+ ### Patch Changes
392
+
393
+ - Updated dependencies [[`0731e99`](https://github.com/strataljs/stratal/commit/0731e99c3e0c96f988387611f0ef8559b63d7bd8), [`52f1daa`](https://github.com/strataljs/stratal/commit/52f1daa981f5a38b983bb3c14abfefb663eb6941)]:
394
+ - stratal@0.0.15
395
+
396
+ ## 0.0.14
397
+
398
+ ### Patch Changes
399
+
400
+ - [#124](https://github.com/strataljs/stratal/pull/124) [`59251d3`](https://github.com/strataljs/stratal/commit/59251d32743cbd461f952985f192a68cb7ccdb91) Thanks [@adesege](https://github.com/adesege)! - Remove unused `custom-pg-types` re-export from database module
401
+
402
+ - [#122](https://github.com/strataljs/stratal/pull/122) [`47530bd`](https://github.com/strataljs/stratal/commit/47530bd31bc91329788b4ba7b03a389f0e722f46) Thanks [@adesege](https://github.com/adesege)! - Migrate build system from tsc to tsdown for faster builds and code-splitting support
403
+
404
+ - Updated dependencies [[`59251d3`](https://github.com/strataljs/stratal/commit/59251d32743cbd461f952985f192a68cb7ccdb91), [`47530bd`](https://github.com/strataljs/stratal/commit/47530bd31bc91329788b4ba7b03a389f0e722f46)]:
405
+ - stratal@0.0.14
406
+
407
+ ## 0.0.13
408
+
409
+ ### Patch Changes
410
+
411
+ - Updated dependencies [[`8d0df50`](https://github.com/strataljs/stratal/commit/8d0df506411bc725ef4e4eaf4efdb314b3384d98), [`527f675`](https://github.com/strataljs/stratal/commit/527f675ea3b4cdb98165cbe1f81e820fa9e79490), [`bb99119`](https://github.com/strataljs/stratal/commit/bb991196dbcc55963d16ee1a6f5db580c18c796a), [`957de6e`](https://github.com/strataljs/stratal/commit/957de6e88684344bf26e95d03187345bf77f4f52), [`0ade941`](https://github.com/strataljs/stratal/commit/0ade94162f9058e9230039fa72efbbf3e57cf572)]:
412
+ - stratal@0.0.13
413
+
414
+ ## 0.0.12
415
+
416
+ ### Patch Changes
417
+
418
+ - [#97](https://github.com/strataljs/stratal/pull/97) [`d58b878`](https://github.com/strataljs/stratal/commit/d58b8782848562a50b79cd558eaf01978aa77f26) Thanks [@adesege](https://github.com/adesege)! - Add `stratalTest()` vitest plugin and migrate fetch mocking from Cloudflare's undici-based `fetchMock` to MSW
419
+
420
+ ### Details
421
+ - **@stratal/testing**
422
+ - Add `@stratal/testing/vitest-plugin` sub-export with `stratalTest()` — wraps `cloudflareTest` with Stratal defaults (tslib alias, ZenStack mocks, SSR externals)
423
+ - Replace `FetchMock`/`createFetchMock` with `MockFetch`/`createMockFetch` backed by MSW (`setupServer`)
424
+ - Re-export `http` and `HttpResponse` from `msw` for convenience
425
+ - Update `cloudflare:test` imports to `cloudflare:workers`
426
+ - Bump vitest peer dependency from `^3.2.0` to `^4.1.0`
427
+
428
+ - **stratal**
429
+ - Update test mocks to use class syntax for Vitest 4 compatibility
430
+ - Bump dependencies: `@intlify/*`, `@scalar/hono-api-reference`, `hono`, `@aws-sdk/*`, `vitest`
431
+
432
+ - **@stratal/framework**
433
+ - Refactor vitest config to use `stratalTest()` plugin, removing manual pool/alias config
434
+ - Bump dependencies: `better-auth`, `@zenstackhq/*`, `wrangler`, `vitest`
435
+
436
+ ### Breaking Changes
437
+ - **@stratal/testing**: `FetchMock` and `createFetchMock` are removed. Use `MockFetch`/`createMockFetch` instead. The new API uses MSW lifecycle methods (`listen`/`reset`/`close`) instead of `activate`/`disableNetConnect`/`deactivate`.
438
+ - **@stratal/testing**: Vitest peer dependency is now `^4.1.0` (was `^3.2.0`).
439
+
440
+ - Updated dependencies [[`11b0da9`](https://github.com/strataljs/stratal/commit/11b0da97ef436bffef592fbc34685bbcc85d7ef7), [`e1a2ba2`](https://github.com/strataljs/stratal/commit/e1a2ba2da883481d192a15b8015456705982d683), [`d58b878`](https://github.com/strataljs/stratal/commit/d58b8782848562a50b79cd558eaf01978aa77f26)]:
441
+ - stratal@0.0.12
442
+
443
+ ## 0.0.11
444
+
445
+ ### Patch Changes
446
+
447
+ - [#90](https://github.com/strataljs/stratal/pull/90) [`87581af`](https://github.com/strataljs/stratal/commit/87581af263eb74c059966650fbd5c1b849d36dfc) Thanks [@adesege](https://github.com/adesege)! - Refactor database module to use per-connection schemas instead of a shared schema with slicing
448
+
449
+ ### Breaking Changes
450
+
451
+ **@stratal/framework**
452
+ - `DatabaseModuleConfig` no longer accepts a top-level `schema` property. Each connection in `connections` now requires its own `schema` property.
453
+ - `DatabaseConnectionConfig` no longer accepts `slicing`. Each connection defines its own schema, making slicing unnecessary.
454
+ - `StratalDatabase` augmentation interface changed: replace `schema` and `slicing` with `schemas` (a map of connection name to schema type).
455
+
456
+ ```typescript
457
+ // Before
458
+ interface StratalDatabase {
459
+ schema: SchemaType;
460
+ defaultConnection: "main";
461
+ slicing: {
462
+ main: { includedModels: readonly ["User", "Post"] };
463
+ analytics: { includedModels: readonly ["AnalyticsEvent"] };
464
+ };
465
+ }
466
+
467
+ // After
468
+ interface StratalDatabase {
469
+ schemas: {
470
+ main: MainSchemaType;
471
+ analytics: AnalyticsSchemaType;
472
+ };
473
+ defaultConnection: "main";
474
+ }
475
+ ```
476
+
477
+ - Removed type exports: `InferDatabaseSchema`, `InferConnectionSlicing`
478
+ - Added type exports: `InferConnectionSchema<K>`, `InferAnySchema`
479
+ - Removed `@stratal/zenstack-plugin` package (no longer needed with per-connection schemas)
480
+ - Removed `slicing` support from database connections. Slicing will be re-added in a future release.
481
+
482
+ - [#92](https://github.com/strataljs/stratal/pull/92) [`bae01ef`](https://github.com/strataljs/stratal/commit/bae01eff7cb7f520ad00206377d9f5f4968076b6) Thanks [@adesege](https://github.com/adesege)! - Update symbol tokens to use 'stratal' namespace for consistency across modules
483
+
484
+ - Updated dependencies [[`bae01ef`](https://github.com/strataljs/stratal/commit/bae01eff7cb7f520ad00206377d9f5f4968076b6)]:
485
+ - stratal@0.0.11
486
+
487
+ ## 0.0.10
488
+
489
+ ### Patch Changes
490
+
491
+ - Updated dependencies [[`3329d20`](https://github.com/strataljs/stratal/commit/3329d20658ea6a6f7cadbbb3efb7630b1cca9ad2)]:
492
+ - stratal@0.0.10
493
+
494
+ ## 0.0.9
495
+
496
+ ### Patch Changes
497
+
498
+ - Updated dependencies [[`c0d9313`](https://github.com/strataljs/stratal/commit/c0d9313b30272eece8a4596718b7d4c1b442c221)]:
499
+ - stratal@0.0.9
500
+
501
+ ## 0.0.8
502
+
503
+ ### Patch Changes
504
+
505
+ - [#84](https://github.com/strataljs/stratal/pull/84) [`3b38b81`](https://github.com/strataljs/stratal/commit/3b38b8184428dc0f79ffbe9dc55ba782d46dea03) Thanks [@adesege](https://github.com/adesege)! - Rename `AuthModule.withRootAsync` to `AuthModule.forRootAsync` for consistency with core framework naming conventions
506
+
507
+ ### Breaking Changes
508
+ - **@stratal/framework**: `AuthModule.withRootAsync()` has been renamed to `AuthModule.forRootAsync()`. Update all usages:
509
+ ```diff
510
+ - AuthModule.withRootAsync({ ... })
511
+ + AuthModule.forRootAsync({ ... })
512
+ ```
513
+
514
+ - Updated dependencies []:
515
+ - stratal@0.0.8
516
+
517
+ ## 0.0.7
518
+
519
+ ### Patch Changes
520
+
521
+ - [#81](https://github.com/strataljs/stratal/pull/81) [`89e06b5`](https://github.com/strataljs/stratal/commit/89e06b57f8aca553f60cbefab8f931cf5554f1b3) Thanks [@adesege](https://github.com/adesege)! - Rearchitect core internals: replace AsyncLocalStorage-based request context with explicit container passing, and replace RouterService with HonoApp
522
+
523
+ ### Breaking Changes
524
+
525
+ **`stratal` (core)**
526
+ - **Removed `RequestContextStore`** — The `AsyncLocalStorage`-based request context propagation is eliminated. This removes the dependency on the `nodejs_als` compatibility flag in Cloudflare Workers.
527
+ - **Removed `RouterService` and `RequestScopeService`** — Replaced by `HonoApp`, a subclass of `OpenAPIHono` that directly integrates request scoping, middleware class support, and global error handling.
528
+ - **Removed `RouterAlreadyConfiguredError` and `RouterNotConfiguredError`** — Replaced by `HonoAppAlreadyConfiguredError`.
529
+ - **`Container` no longer accepts `env` or `ctx` in options** — These are now registered as values in the container by `Application` directly.
530
+ - **`runInRequestScope()` callback signature changed** — The callback now receives `(requestContainer: Container) => T | Promise<T>` instead of `() => T | Promise<T>`. Callers must use the passed container for resolution.
531
+ - **`Stratal` initialization changed from lazy to eager** — `Stratal` now eagerly bootstraps by dynamically importing `cloudflare:workers` for `env` and `waitUntil`, instead of lazily initializing on first request.
532
+ - **`queue()` and `scheduled()` no longer accept `env` and `ctx` parameters** — These are obtained from `cloudflare:workers` during eager init.
533
+ - **New `StratalExecutionContext` interface** — A minimal abstraction over Cloudflare's `ExecutionContext` with only `waitUntil()`.
534
+ - **New `HonoApp` class** — Extends `OpenAPIHono` with Stratal concerns; supports `Constructor<Middleware>` in `use()` via module augmentation.
535
+
536
+ **`@stratal/framework`**
537
+ - **`DatabaseConnectionConfig.dialect` changed from `Dialect` to `() => Dialect`** — Database connections now take a factory function for lazy dialect/pool creation.
538
+ - **Caching strategy changed from `instancePerContainerCachingFactory` to `instanceCachingFactory`**.
539
+
540
+ **`@stratal/testing`**
541
+ - **`TestingModule.runInRequestScope()` callback now receives a `container` parameter** — Update all callbacks to use the passed container for service resolution.
542
+ - **`TestingModule.fetch()` now routes through `HonoApp`** instead of `RouterService`.
543
+ - **`TestingModuleBuilder.compile()` now applies overrides before `initialize()`** — Fixes issue where overrides were applied after initialization.
544
+
545
+ ### Minor Changes
546
+
547
+ **`@stratal/seeders`**
548
+ - Updated `executeSeeder()` to use the explicit `requestContainer` parameter from `runInRequestScope()`.
549
+
550
+ - [#83](https://github.com/strataljs/stratal/pull/83) [`bcb3556`](https://github.com/strataljs/stratal/commit/bcb3556a6e1f185e088286f202c605c73799e63f) Thanks [@adesege](https://github.com/adesege)! - Introduce @stratal/zenstack-plugin and rearchitect database module to use shared schema with per-connection slicing
551
+
552
+ ### New Package
553
+
554
+ **`@stratal/zenstack-plugin`**
555
+ - ZenStack plugin for multi-connection database support with schema slicing
556
+ - Generates connection-specific schema types and `StratalDatabase` augmentation
557
+ - CLI commands: `stratal-db migrate` and `stratal-db push` for per-connection database management
558
+ - Plugin model (`plugin.zmodel`) for ZenStack integration
559
+
560
+ ### Breaking Changes
561
+
562
+ **`stratal` (core)**
563
+ - Re-exports `delay` from tsyringe via `stratal/di`
564
+
565
+ **`@stratal/framework`**
566
+ - **Replaced `DatabaseSchemaRegistry` and `DefaultDatabaseConnection` with unified `StratalDatabase` interface** — Consumers must update their type augmentations to use the new single interface with `schema`, `defaultConnection`, and `slicing` properties.
567
+ - **`schema` moved from `DatabaseConnectionConfig` to `DatabaseModuleConfig`** — All connections now share a single schema; per-connection schema is no longer supported.
568
+ - **Added `slicing` option to `DatabaseConnectionConfig`** — Connections can narrow available models via ZenStack slicing options.
569
+ - **Database services are now lazily created** — Dialect factories are not called during module initialization; services are instantiated on first resolve within a request scope.
570
+ - **Database services are now request-scoped** — Each request gets its own database client instance via tsyringe's `delay()` + `Scope.Request`.
571
+ - **`DatabaseEvents` type no longer parameterized by connection** — Event types (`ModelName`, `DatabaseEventName`, `GetData`, `GetResult`, etc.) derive from the shared schema instead of per-connection schemas.
572
+ - **Removed `InferConnectionSchema` type** — Replaced by `InferDatabaseSchema` (shared) and `InferConnectionSlicing` (per-connection slicing).
573
+
574
+ **`@stratal/testing`**
575
+ - **`TestingModule.getDb()` is now synchronous** — Returns `DatabaseService` directly instead of `Promise<DatabaseService>`.
576
+ - **`TestingModule` creates a single request-scoped container at construction** — `container` property now returns the request-scoped container. The `runInRequestScope` pattern is removed.
577
+ - **`TestingModule.close()` now disposes the request container** before shutting down the application.
578
+
579
+ - Updated dependencies [[`89e06b5`](https://github.com/strataljs/stratal/commit/89e06b57f8aca553f60cbefab8f931cf5554f1b3), [`bcb3556`](https://github.com/strataljs/stratal/commit/bcb3556a6e1f185e088286f202c605c73799e63f)]:
580
+ - stratal@0.0.7
581
+
582
+ ## 0.0.6
583
+
584
+ ### Patch Changes
585
+
586
+ - Updated dependencies [[`6542f78`](https://github.com/strataljs/stratal/commit/6542f78fda2bf851df7ee5d88d6f7c7d04ea6388)]:
587
+ - stratal@0.0.6
588
+
589
+ ## 0.0.5
590
+
591
+ ### Patch Changes
592
+
593
+ - [#66](https://github.com/strataljs/stratal/pull/66) [`c8ea964`](https://github.com/strataljs/stratal/commit/c8ea964e272b09ebc6619843e77d2b51178f9423) Thanks [@adesege](https://github.com/adesege)! - Add AuthModule (Better Auth integration), DatabaseModule (ZenStack ORM with named connections and plugins), RbacModule (Casbin RBAC), AuthGuard factory, AuthContext, Factory base class, and database event types. Includes E2E test suite with Docker Postgres.
594
+
595
+ - Updated dependencies [[`c8ea964`](https://github.com/strataljs/stratal/commit/c8ea964e272b09ebc6619843e77d2b51178f9423)]:
596
+ - stratal@0.0.5