@voltro/protocol 0.2.2 → 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 +20 -0
- package/dist/index.d.ts +108 -5
- package/dist/index.js +212 -206
- package/dist/rest.d.ts +51 -3
- package/dist/rest.js +9 -9
- package/dist/serverErrorBus-BrqfEV84.js +0 -0
- package/package.json +2 -2
- package/dist/serverErrorBus-C1KVL0dr.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,26 @@ _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
|
+
|
|
42
62
|
## [0.2.2] — 2026-07-17
|
|
43
63
|
|
|
44
64
|
### Added
|
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,
|
|
2
|
-
import { a as
|
|
3
|
-
import { Context as
|
|
4
|
-
import { Rpc as
|
|
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
|
|
7
|
-
key:
|
|
8
|
-
value:
|
|
9
|
-
}).pipe(
|
|
10
|
-
op:
|
|
11
|
-
path:
|
|
12
|
-
value:
|
|
13
|
-
}),
|
|
14
|
-
op:
|
|
15
|
-
path:
|
|
16
|
-
value:
|
|
17
|
-
}),
|
|
18
|
-
op:
|
|
19
|
-
path:
|
|
20
|
-
})),
|
|
21
|
-
ops:
|
|
22
|
-
order:
|
|
23
|
-
}),
|
|
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
|
-
},
|
|
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:
|
|
36
|
+
path: m(e.id),
|
|
37
37
|
value: e
|
|
38
|
-
}) :
|
|
38
|
+
}) : Ce(t, e) || r.push({
|
|
39
39
|
op: "replace",
|
|
40
|
-
path:
|
|
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:
|
|
46
|
+
path: m(t.id)
|
|
47
47
|
});
|
|
48
48
|
return {
|
|
49
49
|
ops: r,
|
|
50
50
|
order: i
|
|
51
51
|
};
|
|
52
|
-
},
|
|
52
|
+
}, we = (e) => e.every((e) => {
|
|
53
53
|
let t = e.id;
|
|
54
54
|
return typeof t == "string" || typeof t == "number";
|
|
55
|
-
}),
|
|
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
|
-
},
|
|
66
|
-
_tag:
|
|
67
|
-
revision:
|
|
65
|
+
}, _ = (e) => l.Union(l.Struct({
|
|
66
|
+
_tag: l.Literal("snapshot"),
|
|
67
|
+
revision: l.Number,
|
|
68
68
|
data: e,
|
|
69
|
-
computed:
|
|
70
|
-
}),
|
|
71
|
-
_tag:
|
|
72
|
-
revision:
|
|
73
|
-
emittedAt:
|
|
74
|
-
patch:
|
|
75
|
-
}),
|
|
76
|
-
_tag:
|
|
77
|
-
error:
|
|
78
|
-
revision:
|
|
79
|
-
})),
|
|
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 ??
|
|
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
|
-
}),
|
|
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 ??
|
|
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
|
-
}),
|
|
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 ??
|
|
105
|
+
error: e.error ?? l.Never,
|
|
106
|
+
guards: e.guards,
|
|
104
107
|
publicApi: e.publicApi,
|
|
105
108
|
exposeAsTool: e.exposeAsTool
|
|
106
|
-
}),
|
|
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 ??
|
|
112
|
-
}),
|
|
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:
|
|
115
|
-
error:
|
|
117
|
+
success: _(e.output),
|
|
118
|
+
error: x(S(e.error, e.guards), t),
|
|
116
119
|
stream: !0
|
|
117
|
-
}),
|
|
120
|
+
}), w = (e, t) => u.make(e.name, {
|
|
118
121
|
payload: e.input,
|
|
119
122
|
success: e.output,
|
|
120
|
-
error:
|
|
121
|
-
}),
|
|
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:
|
|
125
|
-
}),
|
|
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:
|
|
131
|
+
error: x(e.error, t),
|
|
129
132
|
stream: !0
|
|
130
|
-
}),
|
|
133
|
+
}), D = (e) => {
|
|
131
134
|
switch (e.kind) {
|
|
132
|
-
case "query": return
|
|
133
|
-
case "mutation": return
|
|
134
|
-
case "action": return
|
|
135
|
-
case "stream": return
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
177
|
-
let n =
|
|
182
|
+
}, je = (e, t) => {
|
|
183
|
+
let n = ye.GenericTag(e);
|
|
178
184
|
return {
|
|
179
185
|
Tag: n,
|
|
180
|
-
Live:
|
|
186
|
+
Live: be.succeed(n, t)
|
|
181
187
|
};
|
|
182
|
-
},
|
|
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 (!
|
|
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,
|
|
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 =
|
|
228
|
-
id:
|
|
229
|
-
workflowName:
|
|
230
|
-
executionId:
|
|
231
|
-
status:
|
|
232
|
-
}), M =
|
|
233
|
-
id:
|
|
234
|
-
tag:
|
|
235
|
-
executionId:
|
|
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:
|
|
238
|
-
workflowVersion:
|
|
239
|
-
workflowPatches:
|
|
240
|
-
output:
|
|
241
|
-
subject:
|
|
242
|
-
source:
|
|
243
|
-
errorTag:
|
|
244
|
-
errorMessage:
|
|
245
|
-
cancelled:
|
|
246
|
-
startedAt:
|
|
247
|
-
completedAt:
|
|
248
|
-
durationMs:
|
|
249
|
-
traceId:
|
|
250
|
-
parentExecutionId:
|
|
251
|
-
parentClosePolicy:
|
|
252
|
-
}), N =
|
|
253
|
-
tag:
|
|
254
|
-
status:
|
|
255
|
-
limit:
|
|
256
|
-
}), P =
|
|
257
|
-
id:
|
|
258
|
-
runId:
|
|
259
|
-
stepName:
|
|
260
|
-
attempt:
|
|
261
|
-
status:
|
|
262
|
-
input:
|
|
263
|
-
retryPolicy:
|
|
264
|
-
output:
|
|
265
|
-
errorTag:
|
|
266
|
-
errorMessage:
|
|
267
|
-
errorCause:
|
|
268
|
-
startedAt:
|
|
269
|
-
completedAt:
|
|
270
|
-
durationMs:
|
|
271
|
-
}), F =
|
|
272
|
-
id:
|
|
273
|
-
runId:
|
|
274
|
-
eventType:
|
|
275
|
-
payload:
|
|
276
|
-
occurredAt:
|
|
277
|
-
stepName:
|
|
278
|
-
attempt:
|
|
279
|
-
}), I =
|
|
280
|
-
id:
|
|
281
|
-
name:
|
|
282
|
-
payload:
|
|
283
|
-
source:
|
|
284
|
-
subject:
|
|
285
|
-
traceId:
|
|
286
|
-
occurredAt:
|
|
287
|
-
}), L =
|
|
288
|
-
id:
|
|
289
|
-
eventId:
|
|
290
|
-
eventName:
|
|
291
|
-
triggerId:
|
|
292
|
-
workflowName:
|
|
293
|
-
executionId:
|
|
294
|
-
status:
|
|
295
|
-
idempotencyKey:
|
|
296
|
-
skipped:
|
|
297
|
-
errorMessage:
|
|
298
|
-
createdAt:
|
|
299
|
-
completedAt:
|
|
300
|
-
}), R =
|
|
301
|
-
name:
|
|
302
|
-
limit:
|
|
303
|
-
}), V =
|
|
304
|
-
workflowName:
|
|
305
|
-
executionId:
|
|
306
|
-
}), U =
|
|
307
|
-
id:
|
|
308
|
-
signalName:
|
|
309
|
-
payload:
|
|
310
|
-
}), W =
|
|
311
|
-
id:
|
|
312
|
-
updateName:
|
|
313
|
-
payload:
|
|
314
|
-
timeoutMs:
|
|
315
|
-
}), G =
|
|
316
|
-
eventId:
|
|
317
|
-
updateId:
|
|
318
|
-
completedEventId:
|
|
319
|
-
result:
|
|
320
|
-
}),
|
|
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:
|
|
325
|
-
}),
|
|
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:
|
|
330
|
-
}),
|
|
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:
|
|
335
|
-
}),
|
|
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:
|
|
340
|
-
}),
|
|
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:
|
|
345
|
-
}),
|
|
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:
|
|
350
|
-
}),
|
|
355
|
+
output: l.Array(L)
|
|
356
|
+
}), Ve = b({
|
|
351
357
|
name: "__voltro.workflow.cancel",
|
|
352
358
|
input: H,
|
|
353
|
-
output:
|
|
354
|
-
}),
|
|
359
|
+
output: l.Struct({ ok: l.Boolean })
|
|
360
|
+
}), He = b({
|
|
355
361
|
name: "__voltro.workflow.resume",
|
|
356
362
|
input: H,
|
|
357
|
-
output:
|
|
358
|
-
}),
|
|
363
|
+
output: l.Struct({ ok: l.Boolean })
|
|
364
|
+
}), Ue = b({
|
|
359
365
|
name: "__voltro.workflow.signal",
|
|
360
366
|
input: U,
|
|
361
|
-
output:
|
|
362
|
-
}),
|
|
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 =
|
|
367
|
-
id:
|
|
368
|
-
tag:
|
|
369
|
-
label:
|
|
370
|
-
undone:
|
|
371
|
-
crossesAction:
|
|
372
|
-
createdAt:
|
|
373
|
-
}), X = class extends
|
|
374
|
-
invocationId:
|
|
375
|
-
reason:
|
|
376
|
-
}) {}, $ =
|
|
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:
|
|
380
|
-
output:
|
|
381
|
-
}),
|
|
385
|
+
input: l.Struct({ limit: l.optional(l.Number) }),
|
|
386
|
+
output: l.Array(Y)
|
|
387
|
+
}), Ke = y({
|
|
382
388
|
name: q,
|
|
383
|
-
input:
|
|
384
|
-
output:
|
|
389
|
+
input: l.Struct({ invocationId: l.String }),
|
|
390
|
+
output: l.Struct({ ok: l.Boolean }),
|
|
385
391
|
error: $
|
|
386
|
-
}),
|
|
392
|
+
}), qe = y({
|
|
387
393
|
name: J,
|
|
388
|
-
input:
|
|
389
|
-
output:
|
|
394
|
+
input: l.Struct({ invocationId: l.String }),
|
|
395
|
+
output: l.Struct({ ok: l.Boolean }),
|
|
390
396
|
error: $
|
|
391
|
-
}),
|
|
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
|
-
},
|
|
400
|
+
}, Ye = 1;
|
|
395
401
|
//#endregion
|
|
396
|
-
export {
|
|
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 {
|
|
2
|
-
import {
|
|
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) :
|
|
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 ?
|
|
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
|
|
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
|
|
118
|
-
return b && x && await
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
56
|
+
"@voltro/database": "0.3.0",
|
|
57
57
|
"jose": "^6.2.3"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
Binary file
|