@stratal/framework 0.0.0-canary-a753e55 → 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 (2) hide show
  1. package/CHANGELOG.md +61 -2
  2. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,6 +1,65 @@
1
1
  # @stratal/framework
2
2
 
3
- ## 0.0.0-canary-a753e55
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
4
63
 
5
64
  ### Minor Changes
6
65
 
@@ -57,7 +116,7 @@
57
116
  ### Patch Changes
58
117
 
59
118
  - Updated dependencies [a753e55]
60
- - stratal@0.0.0-canary-a753e55
119
+ - stratal@0.1.0
61
120
 
62
121
  ## 0.0.27
63
122
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratal/framework",
3
- "version": "0.0.0-canary-a753e55",
3
+ "version": "0.0.0-canary-e5681b8",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -80,13 +80,13 @@
80
80
  "@zenstackhq/schema": ">=3.6",
81
81
  "better-auth": ">=1.6",
82
82
  "pg": "^8.0.0",
83
- "stratal": "0.0.0-canary-a753e55"
83
+ "stratal": "0.0.0-canary-e5681b8"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@better-auth/core": "^1.7.5",
87
87
  "@cloudflare/vitest-plugin": "^1.1.13",
88
88
  "@cloudflare/workers-types": "5.20260919.1",
89
- "@stratal/testing": "^0.0.0-canary-a753e55",
89
+ "@stratal/testing": "^0.0.0-canary-e5681b8",
90
90
  "@types/node": "^26.6.2",
91
91
  "@types/pg": "^8.23.1",
92
92
  "@vitest/coverage-istanbul": "~4.1.11",
@@ -100,7 +100,7 @@
100
100
  "better-call": "1.4.0",
101
101
  "dotenv-cli": "^11.0.0",
102
102
  "pg": "^8.23.0",
103
- "stratal": "0.0.0-canary-a753e55",
103
+ "stratal": "0.0.0-canary-e5681b8",
104
104
  "tsdown": "^0.23.0",
105
105
  "typescript": "^7.0.2",
106
106
  "vitest": "~4.1.11",