@voltro/protocol 0.2.1 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -39,6 +39,42 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.3.0] — 2026-07-18
43
+
44
+ ### ⚠ BREAKING
45
+
46
+ - **@voltro/cli** — `voltro build` precompiles the whole serve path (framework + effect + `@voltro` inlined, app modules as lazy chunks) into a single **serve bundle**, and `voltro serve` boots from it in-process — cutting `serve: ready` from ~1000 ms to ~180 ms (5–6×; the win is larger on a cold scale-to-zero container). The app's declared SQL driver is inlined (only the native leaf like `pg` stays external, resolved at runtime so it survives the deploy relocation). **Production now serves ONLY from the bundle and NEVER transpiles on demand:** the bundle build externalises unresolvable optional peers (e.g. `@react-email/render` behind `@voltro/plugin-mail`) so it always builds; a bundle-build failure is **fatal** (`voltro build` exits non-zero); and an unbuilt production `voltro serve` fails loud instead of falling back to tsx. The build toolchain (`tsx`, `esbuild`, `vite`, `@vitejs/plugin-react`, `@tailwindcss/vite` + their native tree: rolldown/lightningcss/postcss/jiti) moves to **`optionalDependencies`** of `@voltro/cli`, so `pnpm --prod --no-optional deploy` yields a serve image with none of it — a prod API image's `node_modules` drops ~305 MB → ~131 MB, structurally, with no fragile prune list. `voltro dev` and a non-production local `voltro serve` are unchanged (still tsx). **Migration:** in production (`NODE_ENV=production`) run `voltro build` before `voltro serve`. The generated Dockerfiles already do; a custom Dockerfile / start script adds a `voltro build .` step before `voltro serve .` (`voltro update` prints this — see the 0.3.0 codemod note).
47
+
48
+ ### Added
49
+
50
+ - **@voltro/protocol, @voltro/runtime, @voltro/plugin-rbac** — Declarative authorization `guards:` on `defineMutation` / `defineQuery` / `defineAction`. The framework enforces the declared scope(s) in the dispatch spine BEFORE the executor (for a mutation, before the transaction opens), fails with a typed `ScopeError`, and auto-merges `ScopeError` into the wire error union so the client decodes the denial typed. Guards are browser-safe DATA (scope strings + a pure `resource: (input) => id` extractor). Checks run against the caller's EFFECTIVE scope set — raw subject scopes ∪ `@voltro/plugin-rbac` role-derived scopes — via a new canonical effective-scope seam in `@voltro/protocol` (`effectiveScopes` / `setEffectiveScopes` / `checkGuards`), which rbac now publishes to (so a role-granted scope satisfies a `guards:` entry and the in-handler `permission()` identically). Adds `ctx.access` (`has` / `hasAny` / `require` / `scopes`) — the cast-free typed authorization slice on every handler context. Enforcement is single-sourced in the shared serve pipeline, so `voltro dev` and `voltro serve` can't drift.
51
+ - **@voltro/protocol, @voltro/client** — Nested / path-targeted auto-optimistic. A mutation `target` can now patch a nested array INSIDE a query's value — a JSON array column (`snapshot.projects`) or a computed/shaped result — at item granularity, via `path` (dot-path to the array), `by` (item key, default `id`), and `match` (a pure predicate that scopes the patch to the entries whose current value satisfies it, preventing a patch bleeding across sibling subscriptions that share a source table). Previously auto-optimistic only patched the flat top-level row array keyed by `id`; nested values needed a hand-written `.withOptimistic` reducer. `path`/`by`/`match` are browser-safe descriptor data (a dot-path string + pure predicate), same discipline as `identify`/`shape`. A path insert is applied even on a computed entry (it targets a known document, not a blind top-level add).
52
+ - **@voltro/runtime** — `ctx.store.applyDefined(input, keys)` (and a standalone `applyDefined` export from `@voltro/runtime`) — builds a partial-update patch keeping only the listed keys whose value the caller actually provided (`!== undefined`; a defined falsy value like `0`/`''`/`false` is kept). Collapses the per-field `if (input.x !== undefined) patch.x = input.x` idiom every partial-update mutation hand-writes.
53
+ - **@voltro/database** — `.uniqueActive([cols], opts?)` on the table builder — a portable partial-UNIQUE constraint that holds only among the rows matching a predicate (default `"deletedAt" IS NULL`, pairing with `.softDelete()`). Emits `CREATE UNIQUE INDEX … WHERE` on postgres / sqlite / mssql, so a soft-deleted row leaves the active set and a NEW row with the same key inserts cleanly — no hand-written `generatedAs("CASE WHEN …")` column and no resurrection footgun. On mysql / mariadb (no partial-index support) it FAILS LOUDLY at migrate time rather than silently emitting a full unique index that would forbid re-creating a soft-deleted key — the generated-STORED-column lowering for those dialects is a follow-up. Kept out of the declarative index snapshot (the incremental planner is predicate-blind and would misclassify a unique+partial index as a full constraint), so the fresh-schema DDL path is its sole emitter and there is no re-diff churn. Live-verified against postgres.
54
+ - **@voltro/cli** — `voltro update` upgrades an app to the latest framework: it bumps every `@voltro/*` dependency, installs with the detected package manager, and runs the codemods shipped with the target version. Codemods are authored with `defineCodemod` + an import-scoped ts-morph helper toolkit (`renameImport`, `renameModuleSpecifier`, `renameJsxProp`, `renameObjectKey`, `add`/`removeImport`, structural `changeCallArgs`/`wrapCall`, `annotate`) and run against the app source; a `manual` kind surfaces written steps for changes that can't be automated. Breaking public-API changes now ship a codemod (or an explicit `codemod: none`), enforced by the changelog gate. Framework-owned `_voltro_*` table changes continue to ride the declarative differ on `voltro db apply` / `voltro dev` boot — `update` does not touch the database.
55
+
56
+ ### Fixed
57
+
58
+ - **@voltro/database** — The core-table registry (`registerCoreTables` / `requireActors` / `requireTenants`) now stores its state on a process-global `Symbol.for` singleton, the same mechanism the main table registry already uses — instead of module-local `let` bindings. Module-local state splits when the `@voltro/database` module is duplicated in a process (e.g. resolved through both the `.` and `./sql` entry points, or a bundled framework copy alongside an externally-resolved one): one instance's `registerCoreTables` becomes invisible to the instance that reads it, surfacing as a spurious `core 'actors' table not registered` at store construction. Pinning it to `globalThis` makes every copy share one store, matching the table registry's already-global behaviour.
59
+
60
+ ---
61
+
62
+ ## [0.2.2] — 2026-07-17
63
+
64
+ ### Added
65
+
66
+ - **@voltro/cli** — `voltro build` now precompiles the whole serve path (framework + effect + `@voltro` inlined, app modules as lazy chunks) into a single **serve bundle** (`.framework/dist-api/serveBundle/serveEntry.js`), and `voltro serve` boots from it in-process — no child `node --import tsx`, no CLI command graph, no per-module resolution of the ~2700-module framework graph. This cuts `serve: ready` from ~1000 ms to ~180 ms (~5–6×) on both driverless (memory) and driver-backed (postgres) apps; the win is larger on a cold scale-to-zero container where module resolution dominates. The app's declared SQL driver is inlined into the bundle so it shares the framework's single effect instance (only the native leaf like `pg` stays external, resolved at runtime so it survives the deploy relocation). Fully fallback-safe: a missing, stale, or corrupt bundle degrades to the standard tsx serve path, so it can never stop `voltro serve` from booting. Nothing to configure — building an API app produces the bundle and serving prefers it automatically.
67
+
68
+ ### Fixed
69
+
70
+ - **@voltro/database** — The core-table registry (`registerCoreTables` / `requireActors` / `requireTenants`) now stores its state on a process-global `Symbol.for` singleton, the same mechanism the main table registry already uses — instead of module-local `let` bindings. Module-local state splits when the `@voltro/database` module is duplicated in a process (e.g. resolved through both the `.` and `./sql` entry points, or a bundled framework copy alongside an externally-resolved one): one instance's `registerCoreTables` becomes invisible to the instance that reads it, surfacing as a spurious `core 'actors' table not registered` at store construction. Pinning it to `globalThis` makes every copy share one store, matching the table registry's already-global behaviour.
71
+
72
+ ### Internal (no consumer-facing effect)
73
+
74
+ - **@voltro/cli** — `appModuleLoader` now accepts lazy `() => import()` loaders alongside eager module namespaces (the eager path — today's `apiEntry.js` bundle — is unchanged). Groundwork for the serve bundle: app modules registered as lazy loaders evaluate on first `importAppModule` (during `runServe`, after `registerCoreTables`) rather than eagerly at bundle-import time. No consumer-facing effect on its own.
75
+
76
+ ---
77
+
42
78
  ## [0.2.1] — 2026-07-17
43
79
 
44
80
  ### Fixed
package/dist/index.d.ts CHANGED
@@ -14,6 +14,9 @@ export declare interface ActionProcedureDescriptor<Name extends string, Input ex
14
14
  readonly input: Input;
15
15
  readonly output: Output;
16
16
  readonly error: Error;
17
+ /** Declarative authorization guard(s) — enforced before the executor runs,
18
+ * failing with a typed `ScopeError`. Absent → no framework-level authz. */
19
+ readonly guards: Guards | undefined;
17
20
  /** Opt this action into a public REST endpoint (innovation/11). */
18
21
  readonly publicApi: PublicApiSpec | undefined;
19
22
  /** Opt this action into the auto-synthesized agent toolset (innovation/07). */
@@ -160,6 +163,16 @@ export declare const checkFrameworkCompat: (pluginName: string, range: string |
160
163
  reason: string;
161
164
  };
162
165
 
166
+ /**
167
+ * Check a descriptor's declared `guards:` against a subject's EFFECTIVE scopes.
168
+ * Returns a typed `ScopeError` naming the first unmet scope, or `null` when
169
+ * every guard is satisfied (or there are no guards). Pure + synchronous — the
170
+ * runtime calls it in the dispatch spine BEFORE the executor (and, for
171
+ * mutations, before the transaction opens). The `admin:full` bypass passes
172
+ * everything.
173
+ */
174
+ export declare const checkGuards: (subject: Subject, guards: ReadonlyArray<GuardCheckSpec> | undefined) => ScopeError | null;
175
+
163
176
  export declare interface ClientDescriptor {
164
177
  readonly kind: 'query' | 'mutation' | 'action' | 'stream' | 'workflow';
165
178
  /** Query only: table(s) the cache should match this subscription against. */
@@ -190,6 +203,13 @@ export declare interface ClientTarget {
190
203
  readonly order?: 'prepend' | 'append' | undefined;
191
204
  readonly shape?: ((input: Record<string, unknown>, optimisticIdOrCurrent?: unknown) => Record<string, unknown>) | undefined;
192
205
  readonly identify?: ((input: Record<string, unknown>) => string) | undefined;
206
+ /** Nested/path-targeted optimistic (see `NestedTargetFields`). Carried to the
207
+ * client so item-level patches into a JSON array / computed value happen
208
+ * automatically. Functions survive because the codegen references the live
209
+ * descriptor by value. */
210
+ readonly path?: string | undefined;
211
+ readonly by?: string | undefined;
212
+ readonly match?: ((value: unknown, input: Record<string, unknown>) => boolean) | undefined;
193
213
  }
194
214
 
195
215
  /** Compose multiple strategies into a single resolver function. The
@@ -287,6 +307,10 @@ export declare const defineAction: <const Name extends string, Input extends Sch
287
307
  readonly input: Input;
288
308
  readonly output: Output;
289
309
  readonly error?: Error;
310
+ /** Declarative authorization guard(s) — the caller must hold the named
311
+ * scope(s) or the action fails with a typed `ScopeError` before the executor
312
+ * runs. `ScopeError` is auto-merged into the wire error union. */
313
+ readonly guards?: Guards<Schema.Schema.Type<Input>>;
290
314
  /** Project this action as a public REST endpoint (innovation/11). */
291
315
  readonly publicApi?: PublicApiSpec;
292
316
  /** Expose this action as an agent tool (innovation/07). */
@@ -300,6 +324,10 @@ export declare const defineMutation: <const Name extends string, Input extends S
300
324
  readonly error?: Error;
301
325
  /** Optional declarative target(s) — drives auto-optimistic. */
302
326
  readonly target?: Target<Schema.Schema.Type<Input>, Schema.Schema.Type<Output>>;
327
+ /** Declarative authorization guard(s) — the caller must hold the named
328
+ * scope(s) or the mutation fails with a typed `ScopeError` BEFORE the
329
+ * transaction opens. `ScopeError` is auto-merged into the wire error union. */
330
+ readonly guards?: Guards<Schema.Schema.Type<Input>>;
303
331
  /** Project this mutation as a public REST endpoint (innovation/11). */
304
332
  readonly publicApi?: PublicApiSpec;
305
333
  /** Expose this mutation as an agent tool (innovation/07). */
@@ -364,6 +392,10 @@ export declare const defineQuery: <const Name extends string, Input extends Sche
364
392
  readonly source?: string | ReadonlyArray<string>;
365
393
  /** Opt into server-side snapshot caching with auto-invalidation. */
366
394
  readonly cache?: QueryCacheConfig;
395
+ /** Declarative authorization guard(s) — the caller must hold the named
396
+ * scope(s) or the query fails with a typed `ScopeError` before the executor
397
+ * runs. `ScopeError` is auto-merged into the wire error union. */
398
+ readonly guards?: Guards<Schema.Schema.Type<Input>>;
367
399
  /** Project this query as a public REST endpoint (innovation/11). */
368
400
  readonly publicApi?: PublicApiSpec;
369
401
  /** Expose this query as an agent tool (innovation/07). */
@@ -382,7 +414,7 @@ export declare const defineStream: <const Name extends string, Input extends Sch
382
414
  readonly error?: Error;
383
415
  }) => StreamProcedureDescriptor<Name, Input, Element, Error>;
384
416
 
385
- export declare interface DeleteTarget<Input = unknown> {
417
+ export declare interface DeleteTarget<Input = unknown> extends NestedTargetFields<Input> {
386
418
  readonly table: string;
387
419
  readonly op: 'delete';
388
420
  /** Identify the row to remove. Default: `input.id`. */
@@ -406,6 +438,13 @@ export declare interface DeleteTarget<Input = unknown> {
406
438
  */
407
439
  export declare const diffRows: (prev: ReadonlyArray<PatchRow>, next: ReadonlyArray<PatchRow>) => RowPatch;
408
440
 
441
+ /**
442
+ * The caller's effective scope set — the merged set published by rbac if
443
+ * present, else the raw `subject.scopes`. This is what declarative guards and
444
+ * `ctx.access` check against.
445
+ */
446
+ export declare const effectiveScopes: (subject: Subject) => ReadonlyArray<string>;
447
+
409
448
  /** Normalize the `exposeAsTool` shorthand. `true` is only valid when the
410
449
  * descriptor carries a top-level `description`; callers pass that in. */
411
450
  export declare type ExposeAsTool = boolean | ExposeAsToolSpec;
@@ -438,9 +477,43 @@ export declare const failIdempotent: (store: IdempotencyStore, scope: string, ke
438
477
  /** Record a completed response for replay. */
439
478
  export declare const finishIdempotent: (store: IdempotencyStore, scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
440
479
 
480
+ export declare interface GuardCheckSpec {
481
+ readonly scope: string | ReadonlyArray<string>;
482
+ readonly mode?: 'all' | 'any';
483
+ }
484
+
485
+ /** One or more declarative guards on a descriptor. All must pass (AND across
486
+ * entries; `mode` controls AND/OR WITHIN one entry's scope array). */
487
+ export declare type Guards<Input = unknown> = ReadonlyArray<GuardSpec<Input>>;
488
+
489
+ export declare interface GuardSpec<Input = unknown> {
490
+ /** Required permission scope(s). A single string, or an array combined by
491
+ * `mode`. Scope strings are the same values `hasScope` / rbac roles use. */
492
+ readonly scope: string | ReadonlyArray<string>;
493
+ /** How an array of scopes combines. `'all'` (default) = AND (hold every
494
+ * scope); `'any'` = OR (hold at least one). Ignored for a single scope. */
495
+ readonly mode?: 'all' | 'any';
496
+ /**
497
+ * PURE `input → resource id` extractor for a row/resource-scoped guard.
498
+ * Browser-safe (no DB, no server import) — exactly like `target.identify`.
499
+ * The framework passes the extracted id to a resource-aware scope resolver
500
+ * (a future ReBAC / `accessPolicy()` resolver) so the check can be scoped to
501
+ * THAT resource. With only the default (subject-global) resolver installed
502
+ * the id is advisory and the guard checks the subject's global scopes. Omit
503
+ * for a plain subject-scope guard.
504
+ */
505
+ readonly resource?: (input: Input) => string | undefined;
506
+ }
507
+
441
508
  export declare const hasCallbackRoutes: (s: AuthStrategy) => s is AuthStrategyWithCallback;
442
509
 
443
- /** True if the subject holds `scope` (or the `admin:full` bypass). */
510
+ /** True if the subject holds `scope` (or the `admin:full` bypass), checking
511
+ * the EFFECTIVE set (role-derived scopes included). Prefer this over
512
+ * `hasScope` anywhere rbac roles should count. */
513
+ export declare const hasEffectiveScope: (subject: Subject, scope: string) => boolean;
514
+
515
+ /** True if the subject holds `scope` (or the `admin:full` bypass). Checks only
516
+ * the RAW subject scopes — use `hasEffectiveScope` to include rbac roles. */
444
517
  export declare const hasScope: (subject: Subject, scope: string) => boolean;
445
518
 
446
519
  /**
@@ -544,7 +617,7 @@ export declare interface IdempotencyStore {
544
617
  * corrupt the pointer. */
545
618
  export declare const idToPath: (id: RowId) => string;
546
619
 
547
- export declare interface InsertTarget<Input = unknown, Row = unknown> {
620
+ export declare interface InsertTarget<Input = unknown, Row = unknown> extends NestedTargetFields<Input> {
548
621
  readonly table: string;
549
622
  readonly op: 'insert';
550
623
  readonly order?: 'prepend' | 'append' | undefined;
@@ -589,6 +662,10 @@ export declare interface MutationProcedureDescriptor<Name extends string, Input
589
662
  * query-invalidation analytics). Mutations without a target run
590
663
  * normally but skip auto-optimistic. */
591
664
  readonly target: Target | undefined;
665
+ /** Declarative authorization guard(s) — enforced before the transaction
666
+ * opens, failing with a typed `ScopeError`. Absent → no framework-level
667
+ * authz (author gates in-handler, or the mutation is unguarded). */
668
+ readonly guards: Guards | undefined;
592
669
  /** Opt this mutation into a public REST endpoint (innovation/11). */
593
670
  readonly publicApi: PublicApiSpec | undefined;
594
671
  /** Opt this mutation into the auto-synthesized agent toolset (innovation/07). */
@@ -597,6 +674,21 @@ export declare interface MutationProcedureDescriptor<Name extends string, Input
597
674
 
598
675
  export declare const mutationToRpc: <Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: MutationProcedureDescriptor<Name, Input, Output, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Output, Schema.Schema.All, never>;
599
676
 
677
+ export declare interface NestedTargetFields<Input = unknown> {
678
+ /** Dot-path to the nested array within the query VALUE to patch (e.g.
679
+ * `'snapshot.projects'`). Absent → patch the top-level row array (default). */
680
+ readonly path?: string | undefined;
681
+ /** Item key within the nested array (default `'id'`). Only meaningful with
682
+ * `path`. */
683
+ readonly by?: string | undefined;
684
+ /** Guard WHICH cached query entries this target patches: only entries whose
685
+ * CURRENT value satisfies the predicate. Pure + browser-safe. Prevents a
686
+ * patch from bleeding across sibling subscriptions that share a source table
687
+ * (the guard AWB hand-writes as `roadmap.id === input.roadmapId`). Absent →
688
+ * every entry matching the target `table` is patched. */
689
+ readonly match?: ((value: unknown, input: Input) => boolean) | undefined;
690
+ }
691
+
600
692
  /**
601
693
  * Project a runtime descriptor down to the subset the client needs.
602
694
  * Functions (`shape`, `identify`) are passed through by reference —
@@ -1289,6 +1381,9 @@ export declare interface QueryProcedureDescriptor<Name extends string, Input ext
1289
1381
  /** Server-side snapshot cache config. Absent → never cached (the
1290
1382
  * default; the dispatcher already keeps live subscriptions fresh). */
1291
1383
  readonly cache: QueryCacheConfig | undefined;
1384
+ /** Declarative authorization guard(s) — enforced before the executor runs,
1385
+ * failing with a typed `ScopeError`. Absent → no framework-level authz. */
1386
+ readonly guards: Guards | undefined;
1292
1387
  /** Opt this query into a public REST endpoint (innovation/11). */
1293
1388
  readonly publicApi: PublicApiSpec | undefined;
1294
1389
  /** Opt this query into the auto-synthesized agent toolset (innovation/07). */
@@ -1341,7 +1436,8 @@ export declare const queryToRpc: <Name extends string, Input extends Schema.Sche
1341
1436
  revision: Schema.optional<typeof Schema.Number>;
1342
1437
  }>]>, Schema.Schema.All>, typeof Schema.Never, never>;
1343
1438
 
1344
- /** Gate a handler on a scope; fails with a typed `ScopeError` if missing. */
1439
+ /** Gate a handler on a scope; fails with a typed `ScopeError` if missing.
1440
+ * Checks the EFFECTIVE set so role-derived scopes count. */
1345
1441
  export declare const requireScope: (subject: Subject, scope: string) => Effect.Effect<void, ScopeError>;
1346
1442
 
1347
1443
  /** The id of a row, addressed in op paths as `/<id>`. */
@@ -1577,6 +1673,13 @@ declare type ServerErrorListener = (event: ServerErrorEvent) => void;
1577
1673
 
1578
1674
  export declare type ServerErrorSource = 'rest' | 'aggregate' | 'subscriber' | 'reaction' | 'schedule' | 'workflow' | 'webhook' | 'startup';
1579
1675
 
1676
+ /**
1677
+ * Publish the caller's fully-resolved scope set for this request (raw subject
1678
+ * scopes ∪ role-derived scopes ∪ any extra grants). Called by the rbac
1679
+ * interceptor; safe to call more than once (last write wins).
1680
+ */
1681
+ export declare const setEffectiveScopes: (subject: Subject, scopes: ReadonlyArray<string>) => void;
1682
+
1580
1683
  /** A strategy's verdict on a request.
1581
1684
  * - `matched`: this strategy claims the request; here's the Subject.
1582
1685
  * - `skip`: not my request (e.g. cookie absent); try next strategy.
@@ -1925,7 +2028,7 @@ export declare const undoRedoDescriptor: MutationProcedureDescriptor<"__voltro.u
1925
2028
  ok: typeof Schema.Boolean;
1926
2029
  }>, Schema.Union<[typeof UndoNotFound, typeof UndoForbidden, typeof UndoConflict]>>;
1927
2030
 
1928
- export declare interface UpdateTarget<Input = unknown, Row = unknown> {
2031
+ export declare interface UpdateTarget<Input = unknown, Row = unknown> extends NestedTargetFields<Input> {
1929
2032
  readonly table: string;
1930
2033
  readonly op: 'update';
1931
2034
  /** Identify the row to patch. Default: `input.id`. */
package/dist/index.js CHANGED
@@ -1,30 +1,30 @@
1
- import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as ee, r as te, s, t as ne, u as re } from "./auth-DCE6m7Bo.js";
2
- import { a as ie, c as ae, d as oe, f as se, i as ce, l as le, n as ue, o as de, r as fe, s as pe, t as me, u as he } from "./serverErrorBus-C1KVL0dr.js";
3
- import { Context as ge, Layer as _e, Schema as c } from "effect";
4
- import { Rpc as l } from "@effect/rpc";
1
+ import { a as e, c as t, d as n, f as r, g as i, h as a, i as o, l as s, m as ee, n as te, o as ne, p as re, r as ie, s as ae, t as oe, u as se } from "./serverErrorBus-BrqfEV84.js";
2
+ import { a as ce, c as le, d as ue, f as de, i as fe, l as pe, n as me, o as he, r as ge, s as c, t as _e, u as ve } from "./auth-DCE6m7Bo.js";
3
+ import { Context as ye, Layer as be, Schema as l } from "effect";
4
+ import { Rpc as u } from "@effect/rpc";
5
5
  //#region src/rowPatch.ts
6
- var ve = c.Union(c.String, c.Number), u = c.Record({
7
- key: c.String,
8
- value: c.Unknown
9
- }).pipe(c.filter((e) => "id" in e, { message: () => "patch row must carry an id" })), d = c.Union(c.Struct({
10
- op: c.Literal("add"),
11
- path: c.String,
12
- value: u
13
- }), c.Struct({
14
- op: c.Literal("replace"),
15
- path: c.String,
16
- value: u
17
- }), c.Struct({
18
- op: c.Literal("remove"),
19
- path: c.String
20
- })), f = c.Struct({
21
- ops: c.Array(d),
22
- order: c.Array(ve)
23
- }), p = (e) => `/${String(e).replace(/~/g, "~0").replace(/\//g, "~1")}`, ye = (e) => (e.startsWith("/") ? e.slice(1) : e).replace(/~1/g, "/").replace(/~0/g, "~"), m = (e) => {
6
+ var xe = l.Union(l.String, l.Number), d = l.Record({
7
+ key: l.String,
8
+ value: l.Unknown
9
+ }).pipe(l.filter((e) => "id" in e, { message: () => "patch row must carry an id" })), f = l.Union(l.Struct({
10
+ op: l.Literal("add"),
11
+ path: l.String,
12
+ value: d
13
+ }), l.Struct({
14
+ op: l.Literal("replace"),
15
+ path: l.String,
16
+ value: d
17
+ }), l.Struct({
18
+ op: l.Literal("remove"),
19
+ path: l.String
20
+ })), p = l.Struct({
21
+ ops: l.Array(f),
22
+ order: l.Array(xe)
23
+ }), m = (e) => `/${String(e).replace(/~/g, "~0").replace(/\//g, "~1")}`, Se = (e) => (e.startsWith("/") ? e.slice(1) : e).replace(/~1/g, "/").replace(/~0/g, "~"), h = (e) => {
24
24
  let t = Object.keys(e).sort(), n = {};
25
25
  for (let r of t) n[r] = e[r];
26
26
  return JSON.stringify(n);
27
- }, be = (e, t) => m(e) === m(t), xe = (e, t) => {
27
+ }, Ce = (e, t) => h(e) === h(t), g = (e, t) => {
28
28
  let n = /* @__PURE__ */ new Map();
29
29
  for (let t of e) n.set(t.id, t);
30
30
  let r = [], i = [], a = /* @__PURE__ */ new Set();
@@ -33,26 +33,26 @@ var ve = c.Union(c.String, c.Number), u = c.Record({
33
33
  let t = n.get(e.id);
34
34
  t === void 0 ? r.push({
35
35
  op: "add",
36
- path: p(e.id),
36
+ path: m(e.id),
37
37
  value: e
38
- }) : be(t, e) || r.push({
38
+ }) : Ce(t, e) || r.push({
39
39
  op: "replace",
40
- path: p(e.id),
40
+ path: m(e.id),
41
41
  value: e
42
42
  });
43
43
  }
44
44
  for (let t of e) a.has(t.id) || r.push({
45
45
  op: "remove",
46
- path: p(t.id)
46
+ path: m(t.id)
47
47
  });
48
48
  return {
49
49
  ops: r,
50
50
  order: i
51
51
  };
52
- }, Se = (e) => e.every((e) => {
52
+ }, we = (e) => e.every((e) => {
53
53
  let t = e.id;
54
54
  return typeof t == "string" || typeof t == "number";
55
- }), Ce = (e, t) => {
55
+ }), Te = (e, t) => {
56
56
  let n = /* @__PURE__ */ new Map();
57
57
  for (let t of e) n.set(t.id, t);
58
58
  for (let e of t.ops) (e.op === "add" || e.op === "replace") && n.set(e.value.id, e.value);
@@ -62,79 +62,82 @@ var ve = c.Union(c.String, c.Number), u = c.Record({
62
62
  t !== void 0 && r.push(t);
63
63
  }
64
64
  return r;
65
- }, h = (e) => c.Union(c.Struct({
66
- _tag: c.Literal("snapshot"),
67
- revision: c.Number,
65
+ }, _ = (e) => l.Union(l.Struct({
66
+ _tag: l.Literal("snapshot"),
67
+ revision: l.Number,
68
68
  data: e,
69
- computed: c.optional(c.Boolean)
70
- }), c.Struct({
71
- _tag: c.Literal("delta"),
72
- revision: c.Number,
73
- emittedAt: c.Number,
74
- patch: f
75
- }), c.Struct({
76
- _tag: c.Literal("error"),
77
- error: c.Unknown,
78
- revision: c.optional(c.Number)
79
- })), g = (e) => ({
69
+ computed: l.optional(l.Boolean)
70
+ }), l.Struct({
71
+ _tag: l.Literal("delta"),
72
+ revision: l.Number,
73
+ emittedAt: l.Number,
74
+ patch: p
75
+ }), l.Struct({
76
+ _tag: l.Literal("error"),
77
+ error: l.Unknown,
78
+ revision: l.optional(l.Number)
79
+ })), v = (e) => ({
80
80
  kind: "query",
81
81
  name: e.name,
82
82
  input: e.input,
83
83
  output: e.output,
84
- error: e.error ?? c.Never,
84
+ error: e.error ?? l.Never,
85
85
  source: e.source,
86
86
  cache: e.cache,
87
+ guards: e.guards,
87
88
  publicApi: e.publicApi,
88
89
  exposeAsTool: e.exposeAsTool
89
- }), _ = (e) => ({
90
+ }), y = (e) => ({
90
91
  kind: "mutation",
91
92
  name: e.name,
92
93
  input: e.input,
93
94
  output: e.output,
94
- error: e.error ?? c.Never,
95
+ error: e.error ?? l.Never,
95
96
  target: e.target,
97
+ guards: e.guards,
96
98
  publicApi: e.publicApi,
97
99
  exposeAsTool: e.exposeAsTool
98
- }), v = (e) => ({
100
+ }), b = (e) => ({
99
101
  kind: "action",
100
102
  name: e.name,
101
103
  input: e.input,
102
104
  output: e.output,
103
- error: e.error ?? c.Never,
105
+ error: e.error ?? l.Never,
106
+ guards: e.guards,
104
107
  publicApi: e.publicApi,
105
108
  exposeAsTool: e.exposeAsTool
106
- }), we = (e) => ({
109
+ }), Ee = (e) => ({
107
110
  kind: "stream",
108
111
  name: e.name,
109
112
  input: e.input,
110
113
  element: e.element,
111
- error: e.error ?? c.Never
112
- }), y = (e, t) => t && t.length > 0 ? c.Union(e, ...t) : e, b = (e, t) => l.make(e.name, {
114
+ error: e.error ?? l.Never
115
+ }), x = (e, t) => t && t.length > 0 ? l.Union(e, ...t) : e, S = (e, t) => t && t.length > 0 ? l.Union(e, s) : e, C = (e, t) => u.make(e.name, {
113
116
  payload: e.input,
114
- success: h(e.output),
115
- error: y(e.error, t),
117
+ success: _(e.output),
118
+ error: x(S(e.error, e.guards), t),
116
119
  stream: !0
117
- }), x = (e, t) => l.make(e.name, {
120
+ }), w = (e, t) => u.make(e.name, {
118
121
  payload: e.input,
119
122
  success: e.output,
120
- error: y(e.error, t)
121
- }), S = (e, t) => l.make(e.name, {
123
+ error: x(S(e.error, e.guards), t)
124
+ }), T = (e, t) => u.make(e.name, {
122
125
  payload: e.input,
123
126
  success: e.output,
124
- error: y(e.error, t)
125
- }), C = (e, t) => l.make(e.name, {
127
+ error: x(S(e.error, e.guards), t)
128
+ }), E = (e, t) => u.make(e.name, {
126
129
  payload: e.input,
127
130
  success: e.element,
128
- error: y(e.error, t),
131
+ error: x(e.error, t),
129
132
  stream: !0
130
- }), Te = (e) => {
133
+ }), D = (e) => {
131
134
  switch (e.kind) {
132
- case "query": return b(e);
133
- case "mutation": return x(e);
134
- case "action": return S(e);
135
- case "stream": return C(e);
135
+ case "query": return C(e);
136
+ case "mutation": return w(e);
137
+ case "action": return T(e);
138
+ case "stream": return E(e);
136
139
  }
137
- }, Ee = (e) => {
140
+ }, De = (e) => {
138
141
  let t = e.input, n = t === void 0 ? {} : { input: t }, r = e.output, i = r === void 0 ? {} : { output: r };
139
142
  if (e.kind === "query") return {
140
143
  kind: "query",
@@ -168,18 +171,21 @@ var ve = c.Union(c.String, c.Number), u = c.Record({
168
171
  op: e.op,
169
172
  ..."order" in e && e.order !== void 0 ? { order: e.order } : {},
170
173
  ..."shape" in e && e.shape !== void 0 ? { shape: e.shape } : {},
171
- ..."identify" in e && e.identify !== void 0 ? { identify: e.identify } : {}
174
+ ..."identify" in e && e.identify !== void 0 ? { identify: e.identify } : {},
175
+ ..."path" in e && e.path !== void 0 ? { path: e.path } : {},
176
+ ..."by" in e && e.by !== void 0 ? { by: e.by } : {},
177
+ ..."match" in e && e.match !== void 0 ? { match: e.match } : {}
172
178
  }))
173
179
  };
174
- }, w = (e) => e, T = (e) => e, E = (e) => {
180
+ }, Oe = (e) => e, ke = (e) => e, Ae = (e) => {
175
181
  if (e.length !== 0) return (t, n) => e.reduceRight((e, t) => t(e, n), t);
176
- }, D = (e, t) => {
177
- let n = ge.GenericTag(e);
182
+ }, je = (e, t) => {
183
+ let n = ye.GenericTag(e);
178
184
  return {
179
185
  Tag: n,
180
- Live: _e.succeed(n, t)
186
+ Live: be.succeed(n, t)
181
187
  };
182
- }, De = (e, t, n) => {
188
+ }, Me = (e, t, n) => {
183
189
  if (!t) return { ok: !0 };
184
190
  let r = O(n);
185
191
  if (!r) return {
@@ -189,7 +195,7 @@ var ve = c.Union(c.String, c.Number), u = c.Record({
189
195
  let i = t.trim();
190
196
  if (i === "*" || i === "") return { ok: !0 };
191
197
  let a = i.split(/\s+/).filter((e) => e.length > 0);
192
- for (let i of a) if (!Oe(i, r)) return {
198
+ for (let i of a) if (!Ne(i, r)) return {
193
199
  ok: !1,
194
200
  reason: `plugin "${e}" requires framework ${t}, running ${n}`
195
201
  };
@@ -202,7 +208,7 @@ var ve = c.Union(c.String, c.Number), u = c.Record({
202
208
  patch: Number(t[3]),
203
209
  pre: t[4] ?? ""
204
210
  } : null;
205
- }, k = (e, t) => e.major === t.major ? e.minor === t.minor ? e.patch === t.patch ? e.pre && !t.pre ? -1 : !e.pre && t.pre ? 1 : e.pre && t.pre ? e.pre < t.pre ? -1 : +(e.pre > t.pre) : 0 : e.patch - t.patch : e.minor - t.minor : e.major - t.major, Oe = (e, t) => {
211
+ }, k = (e, t) => e.major === t.major ? e.minor === t.minor ? e.patch === t.patch ? e.pre && !t.pre ? -1 : !e.pre && t.pre ? 1 : e.pre && t.pre ? e.pre < t.pre ? -1 : +(e.pre > t.pre) : 0 : e.patch - t.patch : e.minor - t.minor : e.major - t.major, Ne = (e, t) => {
206
212
  if (e === "*") return !0;
207
213
  if (e.startsWith("^")) {
208
214
  let n = O(e.slice(1));
@@ -224,173 +230,173 @@ var ve = c.Union(c.String, c.Number), u = c.Record({
224
230
  }
225
231
  let r = O(e);
226
232
  return r ? k(t, r) === 0 : !1;
227
- }, A = c.Literal("running", "succeeded", "failed", "cancelled", "suspended"), j = c.Literal("cancel", "terminate", "abandon"), ke = c.Struct({
228
- id: c.String,
229
- workflowName: c.String,
230
- executionId: c.String,
231
- status: c.Literal("running")
232
- }), M = c.Struct({
233
- id: c.String,
234
- tag: c.String,
235
- executionId: c.String,
233
+ }, A = l.Literal("running", "succeeded", "failed", "cancelled", "suspended"), j = l.Literal("cancel", "terminate", "abandon"), Pe = l.Struct({
234
+ id: l.String,
235
+ workflowName: l.String,
236
+ executionId: l.String,
237
+ status: l.Literal("running")
238
+ }), M = l.Struct({
239
+ id: l.String,
240
+ tag: l.String,
241
+ executionId: l.String,
236
242
  status: A,
237
- payload: c.Unknown,
238
- workflowVersion: c.NullOr(c.String),
239
- workflowPatches: c.NullOr(c.Unknown),
240
- output: c.NullOr(c.Unknown),
241
- subject: c.NullOr(c.Unknown),
242
- source: c.NullOr(c.String),
243
- errorTag: c.NullOr(c.String),
244
- errorMessage: c.NullOr(c.String),
245
- cancelled: c.Boolean,
246
- startedAt: c.Date,
247
- completedAt: c.NullOr(c.Date),
248
- durationMs: c.NullOr(c.Number),
249
- traceId: c.NullOr(c.String),
250
- parentExecutionId: c.NullOr(c.String),
251
- parentClosePolicy: c.NullOr(j)
252
- }), N = c.Struct({
253
- tag: c.optional(c.String),
254
- status: c.optional(A),
255
- limit: c.optional(c.Number)
256
- }), P = c.Struct({
257
- id: c.String,
258
- runId: c.String,
259
- stepName: c.String,
260
- attempt: c.Number,
261
- status: c.Literal("running", "succeeded", "failed"),
262
- input: c.NullOr(c.Unknown),
263
- retryPolicy: c.NullOr(c.Unknown),
264
- output: c.NullOr(c.Unknown),
265
- errorTag: c.NullOr(c.String),
266
- errorMessage: c.NullOr(c.String),
267
- errorCause: c.NullOr(c.Unknown),
268
- startedAt: c.Date,
269
- completedAt: c.NullOr(c.Date),
270
- durationMs: c.NullOr(c.Number)
271
- }), F = c.Struct({
272
- id: c.String,
273
- runId: c.String,
274
- eventType: c.String,
275
- payload: c.NullOr(c.Unknown),
276
- occurredAt: c.Date,
277
- stepName: c.NullOr(c.String),
278
- attempt: c.NullOr(c.Number)
279
- }), I = c.Struct({
280
- id: c.String,
281
- name: c.String,
282
- payload: c.Unknown,
283
- source: c.String,
284
- subject: c.NullOr(c.Unknown),
285
- traceId: c.NullOr(c.String),
286
- occurredAt: c.Date
287
- }), L = c.Struct({
288
- id: c.String,
289
- eventId: c.String,
290
- eventName: c.String,
291
- triggerId: c.String,
292
- workflowName: c.String,
293
- executionId: c.NullOr(c.String),
294
- status: c.Literal("starting", "started", "skipped", "failed"),
295
- idempotencyKey: c.String,
296
- skipped: c.Boolean,
297
- errorMessage: c.NullOr(c.String),
298
- createdAt: c.Date,
299
- completedAt: c.NullOr(c.Date)
300
- }), R = c.Struct({ id: c.String }), z = c.Struct({ runId: c.String }), B = c.Struct({
301
- name: c.optional(c.String),
302
- limit: c.optional(c.Number)
303
- }), V = c.Struct({ eventId: c.String }), H = c.Struct({
304
- workflowName: c.String,
305
- executionId: c.String
306
- }), U = c.Struct({
307
- id: c.String,
308
- signalName: c.String,
309
- payload: c.optional(c.Unknown)
310
- }), W = c.Struct({
311
- id: c.String,
312
- updateName: c.String,
313
- payload: c.optional(c.Unknown),
314
- timeoutMs: c.optional(c.Number)
315
- }), G = c.Struct({
316
- eventId: c.String,
317
- updateId: c.String,
318
- completedEventId: c.String,
319
- result: c.Unknown
320
- }), Ae = g({
243
+ payload: l.Unknown,
244
+ workflowVersion: l.NullOr(l.String),
245
+ workflowPatches: l.NullOr(l.Unknown),
246
+ output: l.NullOr(l.Unknown),
247
+ subject: l.NullOr(l.Unknown),
248
+ source: l.NullOr(l.String),
249
+ errorTag: l.NullOr(l.String),
250
+ errorMessage: l.NullOr(l.String),
251
+ cancelled: l.Boolean,
252
+ startedAt: l.Date,
253
+ completedAt: l.NullOr(l.Date),
254
+ durationMs: l.NullOr(l.Number),
255
+ traceId: l.NullOr(l.String),
256
+ parentExecutionId: l.NullOr(l.String),
257
+ parentClosePolicy: l.NullOr(j)
258
+ }), N = l.Struct({
259
+ tag: l.optional(l.String),
260
+ status: l.optional(A),
261
+ limit: l.optional(l.Number)
262
+ }), P = l.Struct({
263
+ id: l.String,
264
+ runId: l.String,
265
+ stepName: l.String,
266
+ attempt: l.Number,
267
+ status: l.Literal("running", "succeeded", "failed"),
268
+ input: l.NullOr(l.Unknown),
269
+ retryPolicy: l.NullOr(l.Unknown),
270
+ output: l.NullOr(l.Unknown),
271
+ errorTag: l.NullOr(l.String),
272
+ errorMessage: l.NullOr(l.String),
273
+ errorCause: l.NullOr(l.Unknown),
274
+ startedAt: l.Date,
275
+ completedAt: l.NullOr(l.Date),
276
+ durationMs: l.NullOr(l.Number)
277
+ }), F = l.Struct({
278
+ id: l.String,
279
+ runId: l.String,
280
+ eventType: l.String,
281
+ payload: l.NullOr(l.Unknown),
282
+ occurredAt: l.Date,
283
+ stepName: l.NullOr(l.String),
284
+ attempt: l.NullOr(l.Number)
285
+ }), I = l.Struct({
286
+ id: l.String,
287
+ name: l.String,
288
+ payload: l.Unknown,
289
+ source: l.String,
290
+ subject: l.NullOr(l.Unknown),
291
+ traceId: l.NullOr(l.String),
292
+ occurredAt: l.Date
293
+ }), L = l.Struct({
294
+ id: l.String,
295
+ eventId: l.String,
296
+ eventName: l.String,
297
+ triggerId: l.String,
298
+ workflowName: l.String,
299
+ executionId: l.NullOr(l.String),
300
+ status: l.Literal("starting", "started", "skipped", "failed"),
301
+ idempotencyKey: l.String,
302
+ skipped: l.Boolean,
303
+ errorMessage: l.NullOr(l.String),
304
+ createdAt: l.Date,
305
+ completedAt: l.NullOr(l.Date)
306
+ }), R = l.Struct({ id: l.String }), z = l.Struct({ runId: l.String }), B = l.Struct({
307
+ name: l.optional(l.String),
308
+ limit: l.optional(l.Number)
309
+ }), V = l.Struct({ eventId: l.String }), H = l.Struct({
310
+ workflowName: l.String,
311
+ executionId: l.String
312
+ }), U = l.Struct({
313
+ id: l.String,
314
+ signalName: l.String,
315
+ payload: l.optional(l.Unknown)
316
+ }), W = l.Struct({
317
+ id: l.String,
318
+ updateName: l.String,
319
+ payload: l.optional(l.Unknown),
320
+ timeoutMs: l.optional(l.Number)
321
+ }), G = l.Struct({
322
+ eventId: l.String,
323
+ updateId: l.String,
324
+ completedEventId: l.String,
325
+ result: l.Unknown
326
+ }), Fe = v({
321
327
  name: "__voltro.workflow.run",
322
328
  source: "_voltro_workflow_runs",
323
329
  input: R,
324
- output: c.Array(M)
325
- }), je = g({
330
+ output: l.Array(M)
331
+ }), Ie = v({
326
332
  name: "__voltro.workflow.runs",
327
333
  source: "_voltro_workflow_runs",
328
334
  input: N,
329
- output: c.Array(M)
330
- }), Me = g({
335
+ output: l.Array(M)
336
+ }), Le = v({
331
337
  name: "__voltro.workflow.run.steps",
332
338
  source: "_voltro_workflow_run_steps",
333
339
  input: z,
334
- output: c.Array(P)
335
- }), Ne = g({
340
+ output: l.Array(P)
341
+ }), Re = v({
336
342
  name: "__voltro.workflow.run.events",
337
343
  source: "_voltro_workflow_run_events",
338
344
  input: z,
339
- output: c.Array(F)
340
- }), Pe = g({
345
+ output: l.Array(F)
346
+ }), ze = v({
341
347
  name: "__voltro.workflow.domainEvents",
342
348
  source: "_voltro_workflow_events",
343
349
  input: B,
344
- output: c.Array(I)
345
- }), Fe = g({
350
+ output: l.Array(I)
351
+ }), Be = v({
346
352
  name: "__voltro.workflow.event.deliveries",
347
353
  source: "_voltro_workflow_event_deliveries",
348
354
  input: V,
349
- output: c.Array(L)
350
- }), Ie = v({
355
+ output: l.Array(L)
356
+ }), Ve = b({
351
357
  name: "__voltro.workflow.cancel",
352
358
  input: H,
353
- output: c.Struct({ ok: c.Boolean })
354
- }), Le = v({
359
+ output: l.Struct({ ok: l.Boolean })
360
+ }), He = b({
355
361
  name: "__voltro.workflow.resume",
356
362
  input: H,
357
- output: c.Struct({ ok: c.Boolean })
358
- }), Re = v({
363
+ output: l.Struct({ ok: l.Boolean })
364
+ }), Ue = b({
359
365
  name: "__voltro.workflow.signal",
360
366
  input: U,
361
- output: c.Struct({ eventId: c.String })
362
- }), ze = v({
367
+ output: l.Struct({ eventId: l.String })
368
+ }), We = b({
363
369
  name: "__voltro.workflow.update",
364
370
  input: W,
365
371
  output: G
366
- }), K = "__voltro.undo.log", q = "__voltro.undo.apply", J = "__voltro.undo.redo", Y = c.Struct({
367
- id: c.String,
368
- tag: c.String,
369
- label: c.NullOr(c.String),
370
- undone: c.Boolean,
371
- crossesAction: c.Boolean,
372
- createdAt: c.String
373
- }), X = class extends c.TaggedError()("UndoNotFound", { invocationId: c.String }) {}, Z = class extends c.TaggedError()("UndoForbidden", { invocationId: c.String }) {}, Q = class extends c.TaggedError()("UndoConflict", {
374
- invocationId: c.String,
375
- reason: c.Literal("conflict", "action")
376
- }) {}, $ = c.Union(X, Z, Q), Be = g({
372
+ }), K = "__voltro.undo.log", q = "__voltro.undo.apply", J = "__voltro.undo.redo", Y = l.Struct({
373
+ id: l.String,
374
+ tag: l.String,
375
+ label: l.NullOr(l.String),
376
+ undone: l.Boolean,
377
+ crossesAction: l.Boolean,
378
+ createdAt: l.String
379
+ }), X = class extends l.TaggedError()("UndoNotFound", { invocationId: l.String }) {}, Z = class extends l.TaggedError()("UndoForbidden", { invocationId: l.String }) {}, Q = class extends l.TaggedError()("UndoConflict", {
380
+ invocationId: l.String,
381
+ reason: l.Literal("conflict", "action")
382
+ }) {}, $ = l.Union(X, Z, Q), Ge = v({
377
383
  name: K,
378
384
  source: "_voltro_undo_log",
379
- input: c.Struct({ limit: c.optional(c.Number) }),
380
- output: c.Array(Y)
381
- }), Ve = _({
385
+ input: l.Struct({ limit: l.optional(l.Number) }),
386
+ output: l.Array(Y)
387
+ }), Ke = y({
382
388
  name: q,
383
- input: c.Struct({ invocationId: c.String }),
384
- output: c.Struct({ ok: c.Boolean }),
389
+ input: l.Struct({ invocationId: l.String }),
390
+ output: l.Struct({ ok: l.Boolean }),
385
391
  error: $
386
- }), He = _({
392
+ }), qe = y({
387
393
  name: J,
388
- input: c.Struct({ invocationId: c.String }),
389
- output: c.Struct({ ok: c.Boolean }),
394
+ input: l.Struct({ invocationId: l.String }),
395
+ output: l.Struct({ ok: l.Boolean }),
390
396
  error: $
391
- }), Ue = (e) => {
397
+ }), Je = (e) => {
392
398
  let t = e instanceof Date ? e.getTime() : typeof e == "number" ? e : typeof e == "string" ? new Date(e).getTime() : 0;
393
399
  return Number.isNaN(t) ? 0 : t;
394
- }, We = 1;
400
+ }, Ye = 1;
395
401
  //#endregion
396
- export { ae as ADMIN_SCOPE, ne as AuthMiddleware, o as ConnectionInfo, te as ConnectionInfoMiddleware, We as PROTOCOL_VERSION, le as ScopeError, i as Subject, e as SubjectService, q as UNDO_APPLY_TAG, K as UNDO_LOG_TAG, J as UNDO_REDO_TAG, ee as Unauthenticated, Q as UndoConflict, Z as UndoForbidden, Y as UndoLogEntry, X as UndoNotFound, H as WorkflowControlInputSchema, I as WorkflowDomainEventRowSchema, B as WorkflowDomainEventsInputSchema, V as WorkflowEventDeliveriesInputSchema, L as WorkflowEventDeliveryRowSchema, j as WorkflowParentClosePolicySchema, F as WorkflowRunEventRowSchema, ke as WorkflowRunHandleSchema, R as WorkflowRunRefSchema, M as WorkflowRunRowSchema, A as WorkflowRunStatusSchema, P as WorkflowRunStepRowSchema, z as WorkflowRunTableRefSchema, N as WorkflowRunsInputSchema, U as WorkflowSignalInputSchema, W as WorkflowUpdateInputSchema, G as WorkflowUpdateResultSchema, S as actionToRpc, s as anonymousSubject, Ce as applyRowPatch, t as assertAuthenticated, fe as beginIdempotent, De as checkFrameworkCompat, a as composeAuthStrategies, E as composeRpcInterceptors, v as defineAction, _ as defineMutation, w as definePlugin, T as definePluginRoute, D as definePluginService, g as defineQuery, we as defineStream, xe as diffRows, ce as failIdempotent, ie as finishIdempotent, re as hasCallbackRoutes, he as hasScope, p as idToPath, de as idempotencyScope, Se as isIdKeyed, n as isSystemSubject, pe as memoryIdempotencyStore, x as mutationToRpc, Ee as normalizeDescriptor, ye as pathToId, me as publishServerError, b as queryToRpc, oe as requireScope, d as rowPatchOpSchema, f as rowPatchSchema, C as streamToRpc, se as subjectScopes, ue as subscribeServerErrors, h as subscriptionEvent, r as systemSubject, Te as toRpc, Ue as tsMs, Ve as undoApplyDescriptor, Be as undoLogQueryDescriptor, He as undoRedoDescriptor, Ie as workflowCancelDescriptor, Pe as workflowDomainEventsQueryDescriptor, Fe as workflowEventDeliveriesQueryDescriptor, Le as workflowResumeDescriptor, Ne as workflowRunEventsQueryDescriptor, Ae as workflowRunQueryDescriptor, Me as workflowRunStepsQueryDescriptor, je as workflowRunsQueryDescriptor, Re as workflowSignalDescriptor, ze as workflowUpdateDescriptor };
402
+ export { t as ADMIN_SCOPE, _e as AuthMiddleware, me as ConnectionInfo, ge as ConnectionInfoMiddleware, Ye as PROTOCOL_VERSION, s as ScopeError, fe as Subject, ce as SubjectService, q as UNDO_APPLY_TAG, K as UNDO_LOG_TAG, J as UNDO_REDO_TAG, he as Unauthenticated, Q as UndoConflict, Z as UndoForbidden, Y as UndoLogEntry, X as UndoNotFound, H as WorkflowControlInputSchema, I as WorkflowDomainEventRowSchema, B as WorkflowDomainEventsInputSchema, V as WorkflowEventDeliveriesInputSchema, L as WorkflowEventDeliveryRowSchema, j as WorkflowParentClosePolicySchema, F as WorkflowRunEventRowSchema, Pe as WorkflowRunHandleSchema, R as WorkflowRunRefSchema, M as WorkflowRunRowSchema, A as WorkflowRunStatusSchema, P as WorkflowRunStepRowSchema, z as WorkflowRunTableRefSchema, N as WorkflowRunsInputSchema, U as WorkflowSignalInputSchema, W as WorkflowUpdateInputSchema, G as WorkflowUpdateResultSchema, T as actionToRpc, c as anonymousSubject, Te as applyRowPatch, le as assertAuthenticated, ie as beginIdempotent, Me as checkFrameworkCompat, se as checkGuards, pe as composeAuthStrategies, Ae as composeRpcInterceptors, b as defineAction, y as defineMutation, Oe as definePlugin, ke as definePluginRoute, je as definePluginService, v as defineQuery, Ee as defineStream, g as diffRows, n as effectiveScopes, o as failIdempotent, e as finishIdempotent, ve as hasCallbackRoutes, r as hasEffectiveScope, re as hasScope, m as idToPath, ne as idempotencyScope, we as isIdKeyed, ue as isSystemSubject, ae as memoryIdempotencyStore, w as mutationToRpc, De as normalizeDescriptor, Se as pathToId, oe as publishServerError, C as queryToRpc, ee as requireScope, f as rowPatchOpSchema, p as rowPatchSchema, a as setEffectiveScopes, E as streamToRpc, i as subjectScopes, te as subscribeServerErrors, _ as subscriptionEvent, de as systemSubject, D as toRpc, Je as tsMs, Ke as undoApplyDescriptor, Ge as undoLogQueryDescriptor, qe as undoRedoDescriptor, Ve as workflowCancelDescriptor, ze as workflowDomainEventsQueryDescriptor, Be as workflowEventDeliveriesQueryDescriptor, He as workflowResumeDescriptor, Re as workflowRunEventsQueryDescriptor, Fe as workflowRunQueryDescriptor, Le as workflowRunStepsQueryDescriptor, Ie as workflowRunsQueryDescriptor, Ue as workflowSignalDescriptor, We as workflowUpdateDescriptor };
package/dist/rest.d.ts CHANGED
@@ -6,6 +6,9 @@ declare interface ActionProcedureDescriptor<Name extends string, Input extends S
6
6
  readonly input: Input;
7
7
  readonly output: Output;
8
8
  readonly error: Error;
9
+ /** Declarative authorization guard(s) — enforced before the executor runs,
10
+ * failing with a typed `ScopeError`. Absent → no framework-level authz. */
11
+ readonly guards: Guards | undefined;
9
12
  /** Opt this action into a public REST endpoint (innovation/11). */
10
13
  readonly publicApi: PublicApiSpec | undefined;
11
14
  /** Opt this action into the auto-synthesized agent toolset (innovation/07). */
@@ -26,7 +29,7 @@ export declare const collectPublicApiRoutes: (entries: ReadonlyArray<PublicApiEn
26
29
  */
27
30
  export declare const defineRestRoute: <I, O>(descriptor: RestRouteDescriptor<I, O>) => RestRouteDescriptor<I, O>;
28
31
 
29
- declare interface DeleteTarget<Input = unknown> {
32
+ declare interface DeleteTarget<Input = unknown> extends NestedTargetFields<Input> {
30
33
  readonly table: string;
31
34
  readonly op: 'delete';
32
35
  /** Identify the row to remove. Default: `input.id`. */
@@ -71,6 +74,29 @@ declare interface ExposeAsToolSpec {
71
74
  readonly maxPerRun?: number;
72
75
  }
73
76
 
77
+ /** One or more declarative guards on a descriptor. All must pass (AND across
78
+ * entries; `mode` controls AND/OR WITHIN one entry's scope array). */
79
+ declare type Guards<Input = unknown> = ReadonlyArray<GuardSpec<Input>>;
80
+
81
+ declare interface GuardSpec<Input = unknown> {
82
+ /** Required permission scope(s). A single string, or an array combined by
83
+ * `mode`. Scope strings are the same values `hasScope` / rbac roles use. */
84
+ readonly scope: string | ReadonlyArray<string>;
85
+ /** How an array of scopes combines. `'all'` (default) = AND (hold every
86
+ * scope); `'any'` = OR (hold at least one). Ignored for a single scope. */
87
+ readonly mode?: 'all' | 'any';
88
+ /**
89
+ * PURE `input → resource id` extractor for a row/resource-scoped guard.
90
+ * Browser-safe (no DB, no server import) — exactly like `target.identify`.
91
+ * The framework passes the extracted id to a resource-aware scope resolver
92
+ * (a future ReBAC / `accessPolicy()` resolver) so the check can be scoped to
93
+ * THAT resource. With only the default (subject-global) resolver installed
94
+ * the id is advisory and the guard checks the subject's global scopes. Omit
95
+ * for a plain subject-scope guard.
96
+ */
97
+ readonly resource?: (input: Input) => string | undefined;
98
+ }
99
+
74
100
  declare interface IdempotencyRecord {
75
101
  readonly scope: string;
76
102
  readonly key: string;
@@ -103,7 +129,7 @@ declare interface IdempotencyStore {
103
129
  readonly release: (scope: string, key: string) => Promise<void>;
104
130
  }
105
131
 
106
- declare interface InsertTarget<Input = unknown, Row = unknown> {
132
+ declare interface InsertTarget<Input = unknown, Row = unknown> extends NestedTargetFields<Input> {
107
133
  readonly table: string;
108
134
  readonly op: 'insert';
109
135
  readonly order?: 'prepend' | 'append' | undefined;
@@ -144,12 +170,31 @@ declare interface MutationProcedureDescriptor<Name extends string, Input extends
144
170
  * query-invalidation analytics). Mutations without a target run
145
171
  * normally but skip auto-optimistic. */
146
172
  readonly target: Target | undefined;
173
+ /** Declarative authorization guard(s) — enforced before the transaction
174
+ * opens, failing with a typed `ScopeError`. Absent → no framework-level
175
+ * authz (author gates in-handler, or the mutation is unguarded). */
176
+ readonly guards: Guards | undefined;
147
177
  /** Opt this mutation into a public REST endpoint (innovation/11). */
148
178
  readonly publicApi: PublicApiSpec | undefined;
149
179
  /** Opt this mutation into the auto-synthesized agent toolset (innovation/07). */
150
180
  readonly exposeAsTool: ExposeAsTool | undefined;
151
181
  }
152
182
 
183
+ declare interface NestedTargetFields<Input = unknown> {
184
+ /** Dot-path to the nested array within the query VALUE to patch (e.g.
185
+ * `'snapshot.projects'`). Absent → patch the top-level row array (default). */
186
+ readonly path?: string | undefined;
187
+ /** Item key within the nested array (default `'id'`). Only meaningful with
188
+ * `path`. */
189
+ readonly by?: string | undefined;
190
+ /** Guard WHICH cached query entries this target patches: only entries whose
191
+ * CURRENT value satisfies the predicate. Pure + browser-safe. Prevents a
192
+ * patch from bleeding across sibling subscriptions that share a source table
193
+ * (the guard AWB hand-writes as `roadmap.id === input.roadmapId`). Absent →
194
+ * every entry matching the target `table` is patched. */
195
+ readonly match?: ((value: unknown, input: Input) => boolean) | undefined;
196
+ }
197
+
153
198
  /** A public raw-HTTP route a plugin serves on the framework listener. */
154
199
  declare interface PluginHttpRoute {
155
200
  /** HTTP method, or `'*'` for any (the handler decides). */
@@ -294,6 +339,9 @@ declare interface QueryProcedureDescriptor<Name extends string, Input extends Sc
294
339
  /** Server-side snapshot cache config. Absent → never cached (the
295
340
  * default; the dispatcher already keeps live subscriptions fresh). */
296
341
  readonly cache: QueryCacheConfig | undefined;
342
+ /** Declarative authorization guard(s) — enforced before the executor runs,
343
+ * failing with a typed `ScopeError`. Absent → no framework-level authz. */
344
+ readonly guards: Guards | undefined;
297
345
  /** Opt this query into a public REST endpoint (innovation/11). */
298
346
  readonly publicApi: PublicApiSpec | undefined;
299
347
  /** Opt this query into the auto-synthesized agent toolset (innovation/07). */
@@ -438,7 +486,7 @@ declare type Target<Input = unknown, Row = unknown> = TargetSpec<Input, Row> | R
438
486
 
439
487
  declare type TargetSpec<Input = unknown, Row = unknown> = InsertTarget<Input, Row> | UpdateTarget<Input, Row> | DeleteTarget<Input>;
440
488
 
441
- declare interface UpdateTarget<Input = unknown, Row = unknown> {
489
+ declare interface UpdateTarget<Input = unknown, Row = unknown> extends NestedTargetFields<Input> {
442
490
  readonly table: string;
443
491
  readonly op: 'update';
444
492
  /** Identify the row to patch. Default: `input.id`. */
package/dist/rest.js CHANGED
@@ -1,5 +1,5 @@
1
- import { s as e } from "./auth-DCE6m7Bo.js";
2
- import { a as t, i as n, o as r, r as i, t as a } from "./serverErrorBus-C1KVL0dr.js";
1
+ import { a as e, i as t, o as n, r, t as i } from "./serverErrorBus-BrqfEV84.js";
2
+ import { s as a } from "./auth-DCE6m7Bo.js";
3
3
  import { Effect as o, Schema as s } from "effect";
4
4
  //#region src/publicApi.ts
5
5
  var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.path ?? `/${t.version ?? "v1"}/${e.replace(/\./g, "/")}`, u = (e, t) => {
@@ -95,7 +95,7 @@ var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.
95
95
  }, f);
96
96
  p = t.value;
97
97
  }
98
- let h = l.resolveSubject ? await l.resolveSubject(s.headers) : e(s.headers["x-tenant"] ?? null), g = {
98
+ let h = l.resolveSubject ? await l.resolveSubject(s.headers) : a(s.headers["x-tenant"] ?? null), g = {
99
99
  subject: h,
100
100
  headers: s.headers,
101
101
  store: l.store
@@ -104,9 +104,9 @@ var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.
104
104
  let t = await e(g);
105
105
  if (t) return m(t.status, { error: t.message }, f);
106
106
  }
107
- let b = l.idempotency, x = b && y.has(c.method) ? s.headers[b.header.toLowerCase()] : void 0, S = b && x ? r(h.tenantId, c.method, c.path) : "";
107
+ let b = l.idempotency, x = b && y.has(c.method) ? s.headers[b.header.toLowerCase()] : void 0, S = b && x ? n(h.tenantId, c.method, c.path) : "";
108
108
  if (b && x) {
109
- let e = await i(b.store, S, x, b.ttlMs, Date.now());
109
+ let e = await r(b.store, S, x, b.ttlMs, Date.now());
110
110
  if (e.kind === "replay") return m(e.response.status, e.response.body, {
111
111
  ...f,
112
112
  "Idempotency-Replayed": "true"
@@ -114,17 +114,17 @@ var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.
114
114
  if (e.kind === "conflict") return m(409, { error: "A request with this Idempotency-Key is already being processed" }, f);
115
115
  }
116
116
  try {
117
- let e = await c.handler(p, g), n = await o.runPromise(d(e));
118
- return b && x && await t(b.store, S, x, {
117
+ let t = await c.handler(p, g), n = await o.runPromise(d(t));
118
+ return b && x && await e(b.store, S, x, {
119
119
  status: 200,
120
120
  body: n
121
121
  }, Date.now()), m(200, n, f);
122
122
  } catch (e) {
123
- if (b && x && await n(b.store, S, x), typeof e == "object" && e && typeof e.status == "number") {
123
+ if (b && x && await t(b.store, S, x), typeof e == "object" && e && typeof e.status == "number") {
124
124
  let t = e;
125
125
  return m(t.status, { error: t.message ?? "Error" }, f);
126
126
  }
127
- return a({
127
+ return i({
128
128
  error: e,
129
129
  source: "rest",
130
130
  name: `${c.method} ${c.path}`,
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/protocol",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "The Voltro wire + plugin contract — defineQuery/Mutation/Action/Stream, definePlugin, sessions / JWT / API-keys, and the RPC protocol.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@effect/sql": "^0.51.1",
56
- "@voltro/database": "0.2.1",
56
+ "@voltro/database": "0.3.0",
57
57
  "jose": "^6.2.3"
58
58
  },
59
59
  "peerDependencies": {
Binary file