@voltro/plugin-auth-auth0 0.11.2 → 0.11.3
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.
- package/CHANGELOG.md +55 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,61 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.11.3] — 2026-07-24
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- **@voltro/runtime** — `crud.*` secure-default CRUD handler helpers + `redactColumns` (A1 core). Each returns an executor you export as a `*.query.server.ts` / `*.mutation.server.ts` default — the descriptor (schemas + `guards`) stays hand-written and browser-safe:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// accounts.list.query.server.ts
|
|
50
|
+
import { crud } from '@voltro/runtime'
|
|
51
|
+
export default crud.list('accounts', { redact: ['apiSecret'] })
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
They bake in the invariants a hand-rolled CRUD generator kept getting wrong (the leak class was in the HANDLERS, not the schemas):
|
|
55
|
+
|
|
56
|
+
- **Tenant scope** — `list` / `getById` read through `ctx.store`, which auto-scopes a `tenant()` table; they never `.unscoped()`, so a cross-tenant read is impossible. - **Redaction** — `redact` columns are stripped from every returned row (a credential / secret / salary a read must never ship), on reads AND on the row a `create` / `update` echoes. `redactColumns(rows, cols)` is exported standalone for a hand-written handler that isn't plain CRUD. - **`getById` returns `null`, never throws** — a reactive getter that throws stalls its shared-WS siblings (pairs with the per-subscription error isolation).
|
|
57
|
+
|
|
58
|
+
What they deliberately DON'T do is authorize: a guard runs before the executor, so gating stays on the DESCRIPTOR (`guards: [...]`) — an executor can't gate itself. Keep write descriptors guarded.
|
|
59
|
+
|
|
60
|
+
Scope note: this is the browser-safe, codegen-free core. Deriving the descriptor SCHEMAS from a table (to drop the hand-written `Schema.Struct`) is structurally a codegen concern — a table VALUE can't be imported into a browser-loaded descriptor (it drags the store into the bundle; `rowSchema` is server-only for exactly this reason) — so full schema-derivation + a `.crud()` boot audit for the scope/gating discipline are a separate, planned pass. See `plans/framework-a1-defineCrud.md`.
|
|
61
|
+
- **@voltro/runtime** — `ctx.store.links(junctionTable, anchor)` — a diff-based writer for a many-to-many JUNCTION table (A2). It reconciles the links from one anchor row against a target-id list by writing only the DIFFERENCE:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
await ctx.store.links('post_tags', { postId: post.id }).set(tagIds) // add missing, remove surplus
|
|
65
|
+
await ctx.store.links('post_tags', { postId: post.id }).add([tagId]) // idempotent
|
|
66
|
+
await ctx.store.links('post_tags', { postId: post.id }).remove([tagId])
|
|
67
|
+
await ctx.store.links('post_tags', { postId: post.id }).list() // current target ids
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Why it belongs in the framework rather than every app: a drop-all-then-reinsert `setLinks` loses data when two writers overlap and makes a reactive subscription on the junction churn every row (flicker) even when nothing changed. `links().set()` touches only the rows that actually differ — the added are inserted, the removed deleted, the unchanged left in place — so a reactive consumer sees a change only for what changed, and `set()` returns `{ added, removed }`. `add`/`remove` are likewise idempotent (they read first and act only on the genuine delta).
|
|
71
|
+
|
|
72
|
+
`anchor` names the source column and its id (`{ postId: 'p1' }`); the target column is the junction's OTHER `reference()` column, auto-detected. A junction with anything but exactly two reference columns is refused with a message naming what it found — use plain `insertMany`/`deleteMany` for a non-standard junction. The writes go through the normal stamped/tenant-scoped store path, so tenant and audit columns are filled as usual. Additive: a new `links` method on `FluentStore` + the `JunctionLinks` interface.
|
|
73
|
+
- **@voltro/client, @voltro/web** — `useSubscription(..., { initialSnapshot })` — the last mile of "SSR-correct first paint, then live" (A5). Pass the value an SSR loader already fetched with `ctx.query` (read it in the component with `useLoaderData()`) and the subscription shows it at the first paint with `loading: false` — it IS real server data — then swaps to the live stream the instant its first snapshot arrives:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const seed = useLoaderData<Employee>()
|
|
77
|
+
const { data } = useSubscription('app', 'employees.me', {}, { initialSnapshot: seed })
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The SSR markup and the hydration render read the same loader value, so they match (no hydration flicker), and the app no longer hand-builds a seed store to bridge loader data into the first render. This is the difference from `fallback`, whose value never came from the server and so keeps `loading: true`; use exactly one of the two. Like `fallback`, `initialSnapshot` guarantees `data` is present, so the call gets the non-union result and needs no `loading` branch. Additive: a new `initialSnapshot` field on `SubscriptionOptions` + an overload; `@voltro/web` re-exports the client surface.
|
|
81
|
+
- **@voltro/cli** — `apis.<name>.authHeaders` in a web `app.config.ts` — a declarative per-reconnect auth-header resolver, so an authenticated split-origin web app no longer hand-mounts `VoltroRuntimeProvider` just to inject a rotating-token thunk (A4). The framework owns the client mount, the reconnect re-resolve, and the SSR-null case (the resolver runs browser-only — it never fires on the server):
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// app.config.ts
|
|
85
|
+
apis: {
|
|
86
|
+
api: {
|
|
87
|
+
package: '@app/api',
|
|
88
|
+
authHeaders: async () => ({ authorization: `Bearer ${await getToken()}` }),
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Because it's a FUNCTION, the codegen imports it from `app.config.ts` into the client bundle rather than serializing it — so a config that declares `authHeaders` must stay browser-safe (no `node:*` / server-only value imports; a pure env schema is fine, and tree-shakes out). It supersedes a static `headers` on the same api. The provider already resolved a `ResolvableHeaders` thunk fresh per connection generation; this just lets you declare it in config instead of hand-writing a `mount()` call.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
42
97
|
## [0.11.2] — 2026-07-24
|
|
43
98
|
|
|
44
99
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-auth-auth0",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "Auth0-backed AuthStrategy for the Voltro framework. Verifies Auth0-issued JWTs via the tenant's JWKS endpoint. Conforms to @voltro/protocol AuthStrategy so it composes with other IdP plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"node": ">=24.0.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@voltro/protocol": "0.11.
|
|
35
|
+
"@voltro/protocol": "0.11.3"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"effect": "^3.21.4"
|