@stratal/framework 0.0.0-canary-ccb3f17 → 0.0.0-canary-e5681b8

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 (36) hide show
  1. package/CHANGELOG.md +655 -0
  2. package/README.md +166 -24
  3. package/dist/access-control/index.d.mts +8 -8
  4. package/dist/access-control/index.d.mts.map +1 -1
  5. package/dist/access-control/index.mjs +2 -2
  6. package/dist/{access.service-rsEreT-4.mjs → access.service-BjmnWBEo.mjs} +3 -3
  7. package/dist/{access.service-rsEreT-4.mjs.map → access.service-BjmnWBEo.mjs.map} +1 -1
  8. package/dist/auth/index.d.mts +49 -48
  9. package/dist/auth/index.d.mts.map +1 -1
  10. package/dist/auth/index.mjs +86 -17
  11. package/dist/auth/index.mjs.map +1 -1
  12. package/dist/{auth-context-CE_-TV27.mjs → auth-context-cNSS1rmh.mjs} +2 -2
  13. package/dist/{auth-context-CE_-TV27.mjs.map → auth-context-cNSS1rmh.mjs.map} +1 -1
  14. package/dist/context/index.d.mts +3 -3
  15. package/dist/context/index.d.mts.map +1 -1
  16. package/dist/context/index.mjs +1 -1
  17. package/dist/database/index.d.mts +3 -3
  18. package/dist/database/index.mjs +288 -9
  19. package/dist/database/index.mjs.map +1 -1
  20. package/dist/{decorate-ZDdbRmsv.mjs → decorate-RQD1h28J.mjs} +1 -1
  21. package/dist/{decorateParam-DQ95ttaa.mjs → decorateParam-xwTkq9gO.mjs} +2 -2
  22. package/dist/{decorateParam-DQ95ttaa.mjs.map → decorateParam-xwTkq9gO.mjs.map} +1 -1
  23. package/dist/factory/index.d.mts +3 -4
  24. package/dist/factory/index.d.mts.map +1 -1
  25. package/dist/guards/index.d.mts +3 -3
  26. package/dist/guards/index.d.mts.map +1 -1
  27. package/dist/guards/index.mjs +4 -4
  28. package/dist/index-e_u1SRyd.d.mts +921 -0
  29. package/dist/index-e_u1SRyd.d.mts.map +1 -0
  30. package/dist/index.d.mts +1 -1
  31. package/dist/{types-DjLXYLlD.d.mts → types-B35g-lXi.d.mts} +18 -3
  32. package/dist/types-B35g-lXi.d.mts.map +1 -0
  33. package/package.json +29 -25
  34. package/dist/index-B8EVn7T5.d.mts +0 -481
  35. package/dist/index-B8EVn7T5.d.mts.map +0 -1
  36. package/dist/types-DjLXYLlD.d.mts.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,655 @@
1
+ # @stratal/framework
2
+
3
+ ## 0.0.0-canary-e5681b8
4
+
5
+ ### Minor Changes
6
+
7
+ - e5681b8: 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 [e5681b8]
60
+ - stratal@0.0.0-canary-e5681b8
61
+
62
+ ## 0.1.0
63
+
64
+ ### Minor Changes
65
+
66
+ - a753e55: Add cursor pagination, share permissions with the client for Inertia access control, and add a Workers-safe database pool factory.
67
+
68
+ ### Cursor pagination
69
+
70
+ Add `db.$cursor` for reading a list one page at a time, positioned by an opaque cursor rather than an offset.
71
+
72
+ ```typescript
73
+ const page = await db.$cursor.thread.findMany({
74
+ cursor: ctx.query("cursor"),
75
+ take: 20,
76
+ orderBy: [{ updatedAt: "desc" }, { id: "desc" }],
77
+ where: { userId },
78
+ });
79
+ // → { data, perPage, cursorName, cursor, nextCursor, prevCursor }
80
+ ```
81
+
82
+ - Rows added, removed or updated around the reader do not shift the page, so walking a list neither skips a row nor repeats one.
83
+ - `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.
84
+ - 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.
85
+ - A transaction client carries the same reader, and `db.$cursor.$from({ findMany }, …)` pages a `UNION` or a raw statement.
86
+ - Distinct from ZenStack's own `cursor` argument, which is offset-based and correct only while the list is unchanged.
87
+
88
+ ### Access control and auth
89
+ - 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.
90
+ - 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:
91
+
92
+ ```typescript
93
+ ResponseCacheModule.forRoot({
94
+ gateway: { entrypoint: "Cached" },
95
+ primers: AUTH_GATEWAY_PRIMERS,
96
+ partitions: { user: (ctx) => ctx.user().id },
97
+ });
98
+ ```
99
+
100
+ - 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.
101
+ - 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.
102
+ - 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`.
103
+
104
+ ### Database
105
+ - 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.
106
+ - 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.
107
+ - 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.
108
+ - 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.
109
+
110
+ ### Breaking Changes
111
+ - **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.
112
+ - **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)`.
113
+ - **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.
114
+ - **A custom Better Auth `rateLimit.customStorage` must implement `consume`**, replacing the previous `get`/`set` pair.
115
+
116
+ ### Patch Changes
117
+
118
+ - Updated dependencies [a753e55]
119
+ - stratal@0.1.0
120
+
121
+ ## 0.0.27
122
+
123
+ ### Patch Changes
124
+
125
+ - Updated dependencies [41a9140]
126
+ - stratal@0.0.27
127
+
128
+ ## 0.0.26
129
+
130
+ ### Patch Changes
131
+
132
+ - ab95f52: Close database connections on application shutdown
133
+
134
+ ### Details
135
+ - 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
136
+ - Database clients also implement the async-disposal contract (`Symbol.asyncDispose`), so they participate in container disposal
137
+
138
+ - Updated dependencies [ab95f52]
139
+ - Updated dependencies [bb6d3b9]
140
+ - stratal@0.0.26
141
+
142
+ ## 0.0.25
143
+
144
+ ### Patch Changes
145
+
146
+ - e93db60: Emit entity mutation events with full entity snapshots from the database layer
147
+
148
+ ### Details
149
+ - 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`).
150
+ - 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.
151
+ - 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.
152
+
153
+ - Updated dependencies [e93db60]
154
+ - stratal@0.0.25
155
+
156
+ ## 0.0.24
157
+
158
+ ### Patch Changes
159
+
160
+ - stratal@0.0.24
161
+
162
+ ## 0.0.23
163
+
164
+ ### Patch Changes
165
+
166
+ - 13b0e8d: Add `@stratal/feature-flags` — Cloudflare Flagship feature flags via the native Worker binding API.
167
+ - `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)`.
168
+ - `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`.
169
+ - `@stratal/inertia`: expose a generic `ctx.share(key, value)` macro on `RouterContext` so middleware and packages can contribute per-request shared props.
170
+ - `@stratal/framework`: add a `ctx.user()` macro on `RouterContext` (shorthand for `AuthContext.requireUser()`).
171
+
172
+ - 13b0e8d: Fix correctness and security issues found in review.
173
+
174
+ Queue:
175
+ - 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.
176
+ - Honor the documented retry budget: `maxRetries` now counts retries correctly against Cloudflare's 1-based `message.attempts` (previously gave one fewer retry than configured).
177
+ - Derive idempotency keys from an order-stable serialization of `type` + `payload`, so payloads that differ only in key order dedupe correctly.
178
+ - `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.
179
+ - 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.
180
+
181
+ Email (SMTP):
182
+ - 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).
183
+ - 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.
184
+ - AUTH is gated on the server's advertised mechanisms and supports both `PLAIN` and `LOGIN`; usernames are percent-decoded like passwords.
185
+ - 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.
186
+ - 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).
187
+
188
+ Inertia SEO:
189
+ - `titleTemplate` substitutes every `%s` and treats `$`-sequences in the title literally.
190
+ - 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.
191
+ - 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).
192
+
193
+ Feature flags:
194
+ - `FeatureFlagService.use()` binds the target app exactly once.
195
+
196
+ Database (framework):
197
+ - The reentrant `$transaction` proxy forwards the receiver for non-transaction property access.
198
+
199
+ Testing:
200
+ - `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.
201
+
202
+ DI:
203
+ - 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.
204
+ - Detect circular dependencies and throw a clear error naming the cycle instead of overflowing the stack.
205
+ - `tryResolve` only swallows "no provider"; a registered provider that throws while constructing now surfaces the real error instead of injecting `undefined`.
206
+ - Request-cache invalidation tracks transitive constructor dependencies, so re-registering a value rebuilds cached services that depend on it through a transient intermediary.
207
+
208
+ Quarry dev runtime:
209
+ - 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.
210
+ - The `cloudflare:sockets` STARTTLS shim re-attaches the stream error handler to the upgraded socket, so post-upgrade connection errors still surface.
211
+
212
+ - 13b0e8d: Make database transactions reentrant and remove `AuthContextMiddleware`
213
+ - 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.
214
+
215
+ ### Breaking Changes
216
+ - **`AuthContextMiddleware` is removed.** Auth context is now registered automatically per request. If you registered this middleware explicitly, remove the registration — `SessionVerificationMiddleware` is sufficient.
217
+
218
+ - Updated dependencies [13b0e8d]
219
+ - Updated dependencies [13b0e8d]
220
+ - Updated dependencies [13b0e8d]
221
+ - Updated dependencies [13b0e8d]
222
+ - Updated dependencies [13b0e8d]
223
+ - Updated dependencies [13b0e8d]
224
+ - Updated dependencies [13b0e8d]
225
+ - Updated dependencies [13b0e8d]
226
+ - Updated dependencies [13b0e8d]
227
+ - Updated dependencies [be813bc]
228
+ - stratal@0.0.23
229
+
230
+ ## 0.0.22
231
+
232
+ ### Patch Changes
233
+
234
+ - 1658945: Migrate all error classes to `HttpException`, move heavy dependencies to peer dependencies
235
+
236
+ ### Breaking Changes
237
+ - **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.
238
+ - **`@better-auth/core`, `@zenstackhq/orm`, `@zenstackhq/schema`, and `better-auth` moved to peer dependencies** — Install them directly in your application if not already present.
239
+ - **Database error mapping simplified** — `fromZenStackError()` no longer maps to typed error code objects. It returns plain `HttpException` instances with descriptive messages.
240
+
241
+ - 4b273ea: Adapt to the new built-in DI container from `stratal`, removing all `tsyringe` and `reflect-metadata` usage
242
+ - All request-scoped services now use the `@Request` decorator instead of `@Transient`.
243
+ - `DatabaseModule` uses `lazy()` for dynamic connection registration instead of tsyringe's `delay()`.
244
+ - `reflect-metadata` is no longer required as a peer dependency.
245
+
246
+ - Updated dependencies [1658945]
247
+ - Updated dependencies [4b273ea]
248
+ - stratal@0.0.22
249
+
250
+ ## 0.0.21
251
+
252
+ ### Patch Changes
253
+
254
+ - 3489cfd: Require `name` on `AuthUser`
255
+
256
+ `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.
257
+
258
+ - Updated dependencies [3489cfd]
259
+ - Updated dependencies [3489cfd]
260
+ - stratal@0.0.21
261
+
262
+ ## 0.0.20
263
+
264
+ ### Patch Changes
265
+
266
+ - f8c61e1: Auto-wire Better Auth's rate limiting through Stratal's `RateLimiterModule`
267
+
268
+ When `RateLimiterModule` is imported alongside `AuthModule`, Better Auth's `rateLimit` block is configured automatically:
269
+ - `customStorage` is backed by Stratal's shared `IRateLimiterStore`, so HTTP throttling and Better Auth share one store.
270
+ - `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.
271
+ - User-supplied `rateLimit.customStorage` and `rateLimit.customRules` keys take precedence on a per-key basis.
272
+
273
+ ```ts
274
+ limiter.forPath("/sign-in/email", () => Limit.perSeconds(10, 3));
275
+ limiter.forPath("/forget-password", () => Limit.none()); // disabled
276
+ ```
277
+
278
+ 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`).
279
+
280
+ - f8c61e1: Store the full authenticated user on `AuthContext`
281
+
282
+ `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.
283
+
284
+ ### Breaking Changes
285
+ - `AuthInfo` shape changed from `{ userId?, role? }` to `{ user: AuthUser }`. `setAuthContext({ userId, role })` callers must pass `setAuthContext({ user })` instead.
286
+ - `getAuthContext()` was renamed to `getAuthInfo()` and now returns `{ user }`.
287
+ - `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.
288
+
289
+ ### New API
290
+ - `AuthUser` interface (extends Better Auth's `BaseUser` with optional `name`) is augmentable via `declare module '@stratal/framework/context'` for app-specific fields.
291
+ - `AuthContext.getUser()` returns the user or `undefined`.
292
+ - `AuthContext.requireUser()` returns the user or throws `UserNotAuthenticatedError`.
293
+
294
+ ### Migration
295
+
296
+ ```ts
297
+ // Before
298
+ const userId = authContext.getAuthContext().userId;
299
+ authContext.setAuthContext({
300
+ userId: session.user.id,
301
+ role: session.user.role,
302
+ });
303
+
304
+ // After
305
+ const user = authContext.requireUser();
306
+ authContext.setAuthContext({ user: session.user });
307
+ ```
308
+
309
+ - f8c61e1: Add `better-call@1.3.5` as a direct dependency
310
+
311
+ `@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`.
312
+
313
+ - f8c61e1: Support `@computed` fields in `DatabaseModule` connection config
314
+
315
+ `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.
316
+
317
+ - Updated dependencies [f8c61e1]
318
+ - Updated dependencies [f8c61e1]
319
+ - Updated dependencies [f8c61e1]
320
+ - Updated dependencies [f8c61e1]
321
+ - Updated dependencies [f8c61e1]
322
+ - stratal@0.0.20
323
+
324
+ ## 0.0.19
325
+
326
+ ### Patch Changes
327
+
328
+ - 3b16f5b: Replace Casbin-based RBAC module with Better Auth access control module
329
+
330
+ ### Breaking Changes
331
+ - The `RbacModule`, `CasbinService`, `CasbinEnforcerService`, and all Casbin-related exports under `@stratal/framework/rbac` have been removed.
332
+ - Use the new `@stratal/framework/access-control` module instead, which integrates with Better Auth's built-in access control system.
333
+ - `AuthGuard` now uses `AccessService` instead of `CasbinService` for permission checks.
334
+
335
+ ### Migration
336
+ 1. Replace `RbacModule` imports with the new access control setup via `createAccessControl()`.
337
+ 2. Define resources and roles using `createAccessControl({ resources, roles })` and pass the result to `AuthModule.forRootAsync()`.
338
+ 3. Replace `CasbinService` usage with `AccessService` from `@stratal/framework/access-control`.
339
+
340
+ - 3b16f5b: Add organization-related error handling and internationalization support for auth module
341
+ - Add structured error codes and i18n messages for organization operations (not found, member not found, invitation errors, limit reached).
342
+ - Enhance Better Auth error handler to map organization-specific errors to appropriate HTTP responses.
343
+
344
+ - 5d26c24: Rearchitect i18n module augmentation to a per-module keyed registry (breaking change)
345
+
346
+ **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.
347
+
348
+ **What changed:**
349
+ - 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.
350
+ - `AppMessages` is now derived: `{ [K in keyof AppMessageNamespaces]: AppMessageNamespaces[K] }`.
351
+ - Access keys are unchanged dot-notation — `i18n.t('auth.errors.invalidCredentials')` — so no custom resolver is needed.
352
+
353
+ **Migration:**
354
+
355
+ Before:
356
+
357
+ ```ts
358
+ declare module "stratal/i18n" {
359
+ interface AppMessages {
360
+ errors: { uploads: { notFound: string } };
361
+ }
362
+ }
363
+ ```
364
+
365
+ After:
366
+
367
+ ```ts
368
+ declare module "stratal/i18n" {
369
+ interface AppMessageNamespaces {
370
+ uploads: { errors: { notFound: string } };
371
+ }
372
+ }
373
+ ```
374
+
375
+ **Framework package moves:**
376
+ - 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.
377
+ - `@stratal/framework`'s `DatabaseModule` now registers its `database.*` validation messages via `I18nModule.registerMessages` (previously the messages file existed but was never wired up).
378
+ - `@stratal/inertia-modal`'s `errors.modal.*` key moved to `modal.errors.*`.
379
+
380
+ **Callsite updates required in downstream apps:**
381
+
382
+ ```ts
383
+ // Before
384
+ new ApplicationError('errors.auth.invalidCredentials', ...)
385
+ i18n.t('errors.auth.org.organizationNotFound')
386
+
387
+ // After
388
+ new ApplicationError('auth.errors.invalidCredentials', ...)
389
+ i18n.t('auth.org.organizationNotFound')
390
+ ```
391
+
392
+ 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.
393
+
394
+ - Updated dependencies [3b16f5b]
395
+ - Updated dependencies [5d26c24]
396
+ - Updated dependencies [3b16f5b]
397
+ - Updated dependencies [3b16f5b]
398
+ - Updated dependencies [5d26c24]
399
+ - Updated dependencies [5d26c24]
400
+ - Updated dependencies [3b16f5b]
401
+ - stratal@0.0.19
402
+
403
+ ## 0.0.18
404
+
405
+ ### Patch Changes
406
+
407
+ - c9176ea: Migrate auth middleware to router-scoped configuration and improve error resilience
408
+
409
+ ### Details
410
+ - Migrate `AuthModule` from `MiddlewareConfigurable` to `RouteConfigurable` interface
411
+ - Add graceful error handling in session verification to prevent invalidated sessions from blocking requests
412
+ - Expand Better Auth error mapping for token expiry, signup, and session creation failures
413
+ - Use duck-typing for Better Auth `APIError` detection to handle bundler environments
414
+
415
+ - Updated dependencies [fcb71c4]
416
+ - Updated dependencies [17f8675]
417
+ - Updated dependencies [c9176ea]
418
+ - Updated dependencies [c9176ea]
419
+ - stratal@0.0.18
420
+
421
+ ## 0.0.17
422
+
423
+ ### Patch Changes
424
+
425
+ - [`cbfce8b`](https://github.com/strataljs/stratal/commit/cbfce8b3a3517b60d94f500c5dc1ef68d8ee76f4) Thanks [@adesege](https://github.com/adesege)! - Export database CLI commands from `@stratal/framework/database`
426
+
427
+ ### Details
428
+ - Export `ZenStackCommand`, `DbGenerateCommand`, `DbPullCommand`, `DbPushCommand`, `MigrateDeployCommand`, `MigrateDevCommand`, `MigrateResetCommand`, and `MigrateStatusCommand`
429
+
430
+ - [#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
431
+
432
+ - 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)]:
433
+ - stratal@0.0.17
434
+
435
+ ## 0.0.16
436
+
437
+ ### Patch Changes
438
+
439
+ - [#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
440
+
441
+ ### Details
442
+ - `@better-auth/core`, `better-auth`, `@faker-js/faker`, `@zenstackhq/cli`, `@zenstackhq/orm`, and `casbin` are now direct dependencies
443
+ - Remove `peerDependenciesMeta` optional markers for these packages
444
+
445
+ - Updated dependencies [[`3dd0bc8`](https://github.com/strataljs/stratal/commit/3dd0bc84c8638db30db7b70f3532a44aa187ace8), [`4b958e2`](https://github.com/strataljs/stratal/commit/4b958e250c99681a99a34a398fbf706546f556cc)]:
446
+ - stratal@0.0.16
447
+
448
+ ## 0.0.15
449
+
450
+ ### Patch Changes
451
+
452
+ - Updated dependencies [[`0731e99`](https://github.com/strataljs/stratal/commit/0731e99c3e0c96f988387611f0ef8559b63d7bd8), [`52f1daa`](https://github.com/strataljs/stratal/commit/52f1daa981f5a38b983bb3c14abfefb663eb6941)]:
453
+ - stratal@0.0.15
454
+
455
+ ## 0.0.14
456
+
457
+ ### Patch Changes
458
+
459
+ - [#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
460
+
461
+ - [#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
462
+
463
+ - Updated dependencies [[`59251d3`](https://github.com/strataljs/stratal/commit/59251d32743cbd461f952985f192a68cb7ccdb91), [`47530bd`](https://github.com/strataljs/stratal/commit/47530bd31bc91329788b4ba7b03a389f0e722f46)]:
464
+ - stratal@0.0.14
465
+
466
+ ## 0.0.13
467
+
468
+ ### Patch Changes
469
+
470
+ - 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)]:
471
+ - stratal@0.0.13
472
+
473
+ ## 0.0.12
474
+
475
+ ### Patch Changes
476
+
477
+ - [#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
478
+
479
+ ### Details
480
+ - **@stratal/testing**
481
+ - Add `@stratal/testing/vitest-plugin` sub-export with `stratalTest()` — wraps `cloudflareTest` with Stratal defaults (tslib alias, ZenStack mocks, SSR externals)
482
+ - Replace `FetchMock`/`createFetchMock` with `MockFetch`/`createMockFetch` backed by MSW (`setupServer`)
483
+ - Re-export `http` and `HttpResponse` from `msw` for convenience
484
+ - Update `cloudflare:test` imports to `cloudflare:workers`
485
+ - Bump vitest peer dependency from `^3.2.0` to `^4.1.0`
486
+
487
+ - **stratal**
488
+ - Update test mocks to use class syntax for Vitest 4 compatibility
489
+ - Bump dependencies: `@intlify/*`, `@scalar/hono-api-reference`, `hono`, `@aws-sdk/*`, `vitest`
490
+
491
+ - **@stratal/framework**
492
+ - Refactor vitest config to use `stratalTest()` plugin, removing manual pool/alias config
493
+ - Bump dependencies: `better-auth`, `@zenstackhq/*`, `wrangler`, `vitest`
494
+
495
+ ### Breaking Changes
496
+ - **@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`.
497
+ - **@stratal/testing**: Vitest peer dependency is now `^4.1.0` (was `^3.2.0`).
498
+
499
+ - 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)]:
500
+ - stratal@0.0.12
501
+
502
+ ## 0.0.11
503
+
504
+ ### Patch Changes
505
+
506
+ - [#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
507
+
508
+ ### Breaking Changes
509
+
510
+ **@stratal/framework**
511
+ - `DatabaseModuleConfig` no longer accepts a top-level `schema` property. Each connection in `connections` now requires its own `schema` property.
512
+ - `DatabaseConnectionConfig` no longer accepts `slicing`. Each connection defines its own schema, making slicing unnecessary.
513
+ - `StratalDatabase` augmentation interface changed: replace `schema` and `slicing` with `schemas` (a map of connection name to schema type).
514
+
515
+ ```typescript
516
+ // Before
517
+ interface StratalDatabase {
518
+ schema: SchemaType;
519
+ defaultConnection: "main";
520
+ slicing: {
521
+ main: { includedModels: readonly ["User", "Post"] };
522
+ analytics: { includedModels: readonly ["AnalyticsEvent"] };
523
+ };
524
+ }
525
+
526
+ // After
527
+ interface StratalDatabase {
528
+ schemas: {
529
+ main: MainSchemaType;
530
+ analytics: AnalyticsSchemaType;
531
+ };
532
+ defaultConnection: "main";
533
+ }
534
+ ```
535
+
536
+ - Removed type exports: `InferDatabaseSchema`, `InferConnectionSlicing`
537
+ - Added type exports: `InferConnectionSchema<K>`, `InferAnySchema`
538
+ - Removed `@stratal/zenstack-plugin` package (no longer needed with per-connection schemas)
539
+ - Removed `slicing` support from database connections. Slicing will be re-added in a future release.
540
+
541
+ - [#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
542
+
543
+ - Updated dependencies [[`bae01ef`](https://github.com/strataljs/stratal/commit/bae01eff7cb7f520ad00206377d9f5f4968076b6)]:
544
+ - stratal@0.0.11
545
+
546
+ ## 0.0.10
547
+
548
+ ### Patch Changes
549
+
550
+ - Updated dependencies [[`3329d20`](https://github.com/strataljs/stratal/commit/3329d20658ea6a6f7cadbbb3efb7630b1cca9ad2)]:
551
+ - stratal@0.0.10
552
+
553
+ ## 0.0.9
554
+
555
+ ### Patch Changes
556
+
557
+ - Updated dependencies [[`c0d9313`](https://github.com/strataljs/stratal/commit/c0d9313b30272eece8a4596718b7d4c1b442c221)]:
558
+ - stratal@0.0.9
559
+
560
+ ## 0.0.8
561
+
562
+ ### Patch Changes
563
+
564
+ - [#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
565
+
566
+ ### Breaking Changes
567
+ - **@stratal/framework**: `AuthModule.withRootAsync()` has been renamed to `AuthModule.forRootAsync()`. Update all usages:
568
+ ```diff
569
+ - AuthModule.withRootAsync({ ... })
570
+ + AuthModule.forRootAsync({ ... })
571
+ ```
572
+
573
+ - Updated dependencies []:
574
+ - stratal@0.0.8
575
+
576
+ ## 0.0.7
577
+
578
+ ### Patch Changes
579
+
580
+ - [#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
581
+
582
+ ### Breaking Changes
583
+
584
+ **`stratal` (core)**
585
+ - **Removed `RequestContextStore`** — The `AsyncLocalStorage`-based request context propagation is eliminated. This removes the dependency on the `nodejs_als` compatibility flag in Cloudflare Workers.
586
+ - **Removed `RouterService` and `RequestScopeService`** — Replaced by `HonoApp`, a subclass of `OpenAPIHono` that directly integrates request scoping, middleware class support, and global error handling.
587
+ - **Removed `RouterAlreadyConfiguredError` and `RouterNotConfiguredError`** — Replaced by `HonoAppAlreadyConfiguredError`.
588
+ - **`Container` no longer accepts `env` or `ctx` in options** — These are now registered as values in the container by `Application` directly.
589
+ - **`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.
590
+ - **`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.
591
+ - **`queue()` and `scheduled()` no longer accept `env` and `ctx` parameters** — These are obtained from `cloudflare:workers` during eager init.
592
+ - **New `StratalExecutionContext` interface** — A minimal abstraction over Cloudflare's `ExecutionContext` with only `waitUntil()`.
593
+ - **New `HonoApp` class** — Extends `OpenAPIHono` with Stratal concerns; supports `Constructor<Middleware>` in `use()` via module augmentation.
594
+
595
+ **`@stratal/framework`**
596
+ - **`DatabaseConnectionConfig.dialect` changed from `Dialect` to `() => Dialect`** — Database connections now take a factory function for lazy dialect/pool creation.
597
+ - **Caching strategy changed from `instancePerContainerCachingFactory` to `instanceCachingFactory`**.
598
+
599
+ **`@stratal/testing`**
600
+ - **`TestingModule.runInRequestScope()` callback now receives a `container` parameter** — Update all callbacks to use the passed container for service resolution.
601
+ - **`TestingModule.fetch()` now routes through `HonoApp`** instead of `RouterService`.
602
+ - **`TestingModuleBuilder.compile()` now applies overrides before `initialize()`** — Fixes issue where overrides were applied after initialization.
603
+
604
+ ### Minor Changes
605
+
606
+ **`@stratal/seeders`**
607
+ - Updated `executeSeeder()` to use the explicit `requestContainer` parameter from `runInRequestScope()`.
608
+
609
+ - [#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
610
+
611
+ ### New Package
612
+
613
+ **`@stratal/zenstack-plugin`**
614
+ - ZenStack plugin for multi-connection database support with schema slicing
615
+ - Generates connection-specific schema types and `StratalDatabase` augmentation
616
+ - CLI commands: `stratal-db migrate` and `stratal-db push` for per-connection database management
617
+ - Plugin model (`plugin.zmodel`) for ZenStack integration
618
+
619
+ ### Breaking Changes
620
+
621
+ **`stratal` (core)**
622
+ - Re-exports `delay` from tsyringe via `stratal/di`
623
+
624
+ **`@stratal/framework`**
625
+ - **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.
626
+ - **`schema` moved from `DatabaseConnectionConfig` to `DatabaseModuleConfig`** — All connections now share a single schema; per-connection schema is no longer supported.
627
+ - **Added `slicing` option to `DatabaseConnectionConfig`** — Connections can narrow available models via ZenStack slicing options.
628
+ - **Database services are now lazily created** — Dialect factories are not called during module initialization; services are instantiated on first resolve within a request scope.
629
+ - **Database services are now request-scoped** — Each request gets its own database client instance via tsyringe's `delay()` + `Scope.Request`.
630
+ - **`DatabaseEvents` type no longer parameterized by connection** — Event types (`ModelName`, `DatabaseEventName`, `GetData`, `GetResult`, etc.) derive from the shared schema instead of per-connection schemas.
631
+ - **Removed `InferConnectionSchema` type** — Replaced by `InferDatabaseSchema` (shared) and `InferConnectionSlicing` (per-connection slicing).
632
+
633
+ **`@stratal/testing`**
634
+ - **`TestingModule.getDb()` is now synchronous** — Returns `DatabaseService` directly instead of `Promise<DatabaseService>`.
635
+ - **`TestingModule` creates a single request-scoped container at construction** — `container` property now returns the request-scoped container. The `runInRequestScope` pattern is removed.
636
+ - **`TestingModule.close()` now disposes the request container** before shutting down the application.
637
+
638
+ - Updated dependencies [[`89e06b5`](https://github.com/strataljs/stratal/commit/89e06b57f8aca553f60cbefab8f931cf5554f1b3), [`bcb3556`](https://github.com/strataljs/stratal/commit/bcb3556a6e1f185e088286f202c605c73799e63f)]:
639
+ - stratal@0.0.7
640
+
641
+ ## 0.0.6
642
+
643
+ ### Patch Changes
644
+
645
+ - Updated dependencies [[`6542f78`](https://github.com/strataljs/stratal/commit/6542f78fda2bf851df7ee5d88d6f7c7d04ea6388)]:
646
+ - stratal@0.0.6
647
+
648
+ ## 0.0.5
649
+
650
+ ### Patch Changes
651
+
652
+ - [#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.
653
+
654
+ - Updated dependencies [[`c8ea964`](https://github.com/strataljs/stratal/commit/c8ea964e272b09ebc6619843e77d2b51178f9423)]:
655
+ - stratal@0.0.5