@voltro/protocol 0.1.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 +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +347 -0
- package/dist/apikey.d.ts +88 -0
- package/dist/apikey.js +32 -0
- package/dist/auth-DCE6m7Bo.js +64 -0
- package/dist/index.d.ts +2564 -0
- package/dist/index.js +396 -0
- package/dist/jwt.d.ts +230 -0
- package/dist/jwt.js +142 -0
- package/dist/rest.d.ts +450 -0
- package/dist/rest.js +142 -0
- package/dist/serverErrorBus-C1KVL0dr.js +0 -0
- package/dist/session.d.ts +262 -0
- package/dist/session.js +106 -0
- package/package.json +67 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2564 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
import { DataStore } from '@voltro/database';
|
|
3
|
+
import { Effect } from 'effect';
|
|
4
|
+
import { Layer } from 'effect';
|
|
5
|
+
import { Rpc } from '@effect/rpc';
|
|
6
|
+
import { RpcMiddleware } from '@effect/rpc';
|
|
7
|
+
import { Schema } from 'effect';
|
|
8
|
+
import { SqlClient } from '@effect/sql';
|
|
9
|
+
import { Stream } from '@effect/rpc/RpcSchema';
|
|
10
|
+
|
|
11
|
+
export declare interface ActionProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All> {
|
|
12
|
+
readonly kind: 'action';
|
|
13
|
+
readonly name: Name;
|
|
14
|
+
readonly input: Input;
|
|
15
|
+
readonly output: Output;
|
|
16
|
+
readonly error: Error;
|
|
17
|
+
/** Opt this action into a public REST endpoint (innovation/11). */
|
|
18
|
+
readonly publicApi: PublicApiSpec | undefined;
|
|
19
|
+
/** Opt this action into the auto-synthesized agent toolset (innovation/07). */
|
|
20
|
+
readonly exposeAsTool: ExposeAsTool | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export declare const actionToRpc: <Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: ActionProcedureDescriptor<Name, Input, Output, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Output, Schema.Schema.All, never>;
|
|
24
|
+
|
|
25
|
+
export declare const ADMIN_SCOPE = "admin:full";
|
|
26
|
+
|
|
27
|
+
export declare const anonymousSubject: (tenantId: string | null) => Subject;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Apply a `RowPatch` to `prev`, producing `next`. Exact inverse of
|
|
31
|
+
* `diffRows`: `applyRowPatch(prev, diffRows(prev, next))` deep-equals
|
|
32
|
+
* `next` for adds, removes, replaces, and reorderings.
|
|
33
|
+
*
|
|
34
|
+
* Builds an id→row map from prev, applies `add` / `replace` (which set the
|
|
35
|
+
* id → next row), then materialises the result strictly in the patch's
|
|
36
|
+
* `order`. A `remove`d id simply isn't in `order`, so it drops out of the
|
|
37
|
+
* output naturally — no key-type juggling to delete it from the map. Any
|
|
38
|
+
* id in `order` that the map doesn't resolve (a desync — the client missed
|
|
39
|
+
* an `add`) is skipped rather than throwing, so one dropped frame degrades
|
|
40
|
+
* to a slightly stale set instead of a crash.
|
|
41
|
+
*/
|
|
42
|
+
export declare const applyRowPatch: (prev: ReadonlyArray<PatchRow>, patch: RowPatch) => ReadonlyArray<PatchRow>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Throws `Unauthenticated` when the resolved Subject is anonymous (no
|
|
46
|
+
* real user identity). Handlers that require a signed-in caller put
|
|
47
|
+
* this as the first line of their executor:
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* const execute = async (input, ctx) => {
|
|
51
|
+
* assertAuthenticated(ctx.request.subject)
|
|
52
|
+
* …
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare const assertAuthenticated: (subject: Subject, reason?: string) => void;
|
|
57
|
+
|
|
58
|
+
/** Optional callback-route mount surface for strategies that handle
|
|
59
|
+
* OAuth callbacks. Apps wire these via the framework's HTTP router
|
|
60
|
+
* when the strategy declares `mountRoutes`. */
|
|
61
|
+
export declare interface AuthCallbackRouter {
|
|
62
|
+
readonly get: (path: string, handler: AuthRouteHandler) => void;
|
|
63
|
+
readonly post: (path: string, handler: AuthRouteHandler) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The framework's per-request auth middleware. Attach to an RpcGroup with
|
|
68
|
+
* `group.middleware(AuthMiddleware)` to require every call to flow through
|
|
69
|
+
* an auth resolver before reaching its handler. The application provides
|
|
70
|
+
* the resolver via `Layer.succeed(AuthMiddleware, AuthMiddleware.of(fn))`,
|
|
71
|
+
* where `fn` receives `{headers, payload, rpc, clientId}` and returns an
|
|
72
|
+
* `Effect<Subject>` — typically by inspecting headers / Bearer token and
|
|
73
|
+
* looking the caller up.
|
|
74
|
+
*
|
|
75
|
+
* `provides: SubjectService` means the resolver's return value is placed
|
|
76
|
+
* into the handler's context tagged as SubjectService; handlers read it
|
|
77
|
+
* with `yield* SubjectService`. Connections with no headers (or whatever
|
|
78
|
+
* the resolver requires) can fail the middleware — that becomes a
|
|
79
|
+
* mutationResult error / error frame for the caller.
|
|
80
|
+
*
|
|
81
|
+
* Apps that don't need per-request resolution can skip attaching this
|
|
82
|
+
* middleware and provide SubjectService directly with a static
|
|
83
|
+
* `Layer.succeed(SubjectService, anonymousSubject(tenantId))` instead.
|
|
84
|
+
*/
|
|
85
|
+
export declare class AuthMiddleware extends AuthMiddleware_base {
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
declare const AuthMiddleware_base: RpcMiddleware.TagClass<AuthMiddleware, "@voltro/AuthMiddleware", {
|
|
89
|
+
readonly provides: typeof SubjectService;
|
|
90
|
+
}>;
|
|
91
|
+
|
|
92
|
+
export declare type AuthRouteHandler = (req: AuthRouteRequest) => Promise<AuthRouteResponse>;
|
|
93
|
+
|
|
94
|
+
export declare interface AuthRouteRequest {
|
|
95
|
+
readonly url: URL;
|
|
96
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
97
|
+
readonly body: () => Promise<string>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export declare interface AuthRouteResponse {
|
|
101
|
+
readonly status: number;
|
|
102
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
103
|
+
readonly body: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Pluggable auth strategy. Strategies are SYNC-fast on no-match
|
|
107
|
+
* (cookie-name lookup) and cache any JWKS / DB roundtrips on match
|
|
108
|
+
* so steady-state verification stays CPU-local. */
|
|
109
|
+
export declare interface AuthStrategy {
|
|
110
|
+
/** Stable id (`'voltro-password'`, `'workos'`, `'kinde'`, …). Used
|
|
111
|
+
* in logs + `Subject.metadata.provider`. */
|
|
112
|
+
readonly id: string;
|
|
113
|
+
readonly resolve: (input: AuthStrategyInput) => Promise<StrategyResolution> | StrategyResolution;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Per-call input the framework hands every strategy. */
|
|
117
|
+
export declare interface AuthStrategyInput {
|
|
118
|
+
readonly headers: Readonly<Record<string, string | undefined>>;
|
|
119
|
+
readonly clientId: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Strategies that need server-side callbacks (OAuth code-exchange,
|
|
123
|
+
* magic-link landing, etc.) ALSO export this companion via the
|
|
124
|
+
* `routes` factory below. Kept separate from `AuthStrategy.resolve`
|
|
125
|
+
* so apps that only need pure JWT-verify strategies don't pay the
|
|
126
|
+
* callback-wiring cost. */
|
|
127
|
+
export declare interface AuthStrategyWithCallback extends AuthStrategy {
|
|
128
|
+
readonly mountRoutes: (router: AuthCallbackRouter) => void;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Decide what to do for an incoming (scope, key) in ONE atomic store call. On
|
|
133
|
+
* `fresh` the caller runs the handler then calls `finish`/`fail`; on
|
|
134
|
+
* `replay`/`conflict` it returns immediately without touching the handler.
|
|
135
|
+
*/
|
|
136
|
+
export declare const beginIdempotent: (store: IdempotencyStore, scope: string, key: string, ttlMs: number, now: number) => Promise<IdempotencyOutcome>;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Validate `plugin.framework` (npm-semver range) against the running
|
|
140
|
+
* voltro version. Returns either `{ ok: true }` or
|
|
141
|
+
* `{ ok: false, reason }` so the boot path can log a clear warning.
|
|
142
|
+
*
|
|
143
|
+
* Intentionally NOT throwing — incompat is a soft signal in v1.
|
|
144
|
+
* Aborting boot on a missed minor would freeze ecosystem evolution.
|
|
145
|
+
* The framework logs WARN and proceeds; the operator gets visibility
|
|
146
|
+
* + decides whether to pin or upgrade.
|
|
147
|
+
*
|
|
148
|
+
* Supports the subset apps in the wild actually use:
|
|
149
|
+
* - `^x.y.z` — caret: same major
|
|
150
|
+
* - `~x.y.z` — tilde: same major+minor
|
|
151
|
+
* - `>=x.y.z` — minimum version
|
|
152
|
+
* - `>=x.y <a.b` — compound (AND)
|
|
153
|
+
* - `x.y.z` — exact
|
|
154
|
+
* - `*` — any
|
|
155
|
+
*/
|
|
156
|
+
export declare const checkFrameworkCompat: (pluginName: string, range: string | undefined, runningVersion: string) => {
|
|
157
|
+
ok: true;
|
|
158
|
+
} | {
|
|
159
|
+
ok: false;
|
|
160
|
+
reason: string;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export declare interface ClientDescriptor {
|
|
164
|
+
readonly kind: 'query' | 'mutation' | 'action' | 'stream' | 'workflow';
|
|
165
|
+
/** Query only: table(s) the cache should match this subscription against. */
|
|
166
|
+
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
167
|
+
/** Mutation only: tables this mutation writes, with op. */
|
|
168
|
+
readonly targets?: ReadonlyArray<ClientTarget> | undefined;
|
|
169
|
+
/** The procedure's input `Schema`, carried to the client so schema-driven
|
|
170
|
+
* UI (forms, query-bound pickers) and the capability map can introspect
|
|
171
|
+
* it with no extra fetch. Browser-safe: the schema is already loaded
|
|
172
|
+
* value-level via the rpc group, so this is the same object by reference. */
|
|
173
|
+
readonly input?: Schema.Schema.Any | undefined;
|
|
174
|
+
/** The procedure's output `Schema` — carried so schema-driven tables
|
|
175
|
+
* (`<DataTable>` derives its columns from it) + the capability map can
|
|
176
|
+
* introspect it. Same browser-safety rationale as `input`. */
|
|
177
|
+
readonly output?: Schema.Schema.Any | undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Client-side view of a target. Carries the same fields as `TargetSpec`
|
|
182
|
+
* but with input/row generics erased — the client sees concrete runtime
|
|
183
|
+
* row shapes, not Schema instances. Optional `shape`/`identify` flow
|
|
184
|
+
* through because the codegen references the live descriptor (rather
|
|
185
|
+
* than emitting JSON), so function refs survive the ES-module boundary.
|
|
186
|
+
*/
|
|
187
|
+
export declare interface ClientTarget {
|
|
188
|
+
readonly table: string;
|
|
189
|
+
readonly op: 'insert' | 'update' | 'delete';
|
|
190
|
+
readonly order?: 'prepend' | 'append' | undefined;
|
|
191
|
+
readonly shape?: ((input: Record<string, unknown>, optimisticIdOrCurrent?: unknown) => Record<string, unknown>) | undefined;
|
|
192
|
+
readonly identify?: ((input: Record<string, unknown>) => string) | undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Compose multiple strategies into a single resolver function. The
|
|
196
|
+
* composer evaluates them in declaration order; first `matched`
|
|
197
|
+
* wins; first `failed` short-circuits to anonymous (does NOT fall
|
|
198
|
+
* through — see StrategyResolution doc).
|
|
199
|
+
*
|
|
200
|
+
* Apps wrap the return in `AuthMiddleware.of(...)` themselves so they
|
|
201
|
+
* can inject the connection-override fast-path (`getConnectionSubject`)
|
|
202
|
+
* ahead of the strategy chain — that path lives in `@voltro/runtime`
|
|
203
|
+
* and the protocol package stays runtime-agnostic.
|
|
204
|
+
*
|
|
205
|
+
* Returns an async resolver `(input) => Promise<Subject>`.
|
|
206
|
+
*/
|
|
207
|
+
export declare const composeAuthStrategies: (strategies: ReadonlyArray<AuthStrategy>, options?: {
|
|
208
|
+
/** Fallback when no strategy matched. Default: returns the input's
|
|
209
|
+
* tenant from `x-tenant` header or null. */
|
|
210
|
+
readonly fallback?: (input: AuthStrategyInput) => Subject;
|
|
211
|
+
/** Hook for logging — called on every `failed` resolution. */
|
|
212
|
+
readonly onStrategyFailed?: (event: {
|
|
213
|
+
strategyId: string;
|
|
214
|
+
reason: string;
|
|
215
|
+
}) => void;
|
|
216
|
+
/**
|
|
217
|
+
* Reject anonymous callers that carry no tenant. When `true` and no
|
|
218
|
+
* strategy matched AND no `x-tenant` header is present, the resolver
|
|
219
|
+
* throws `Unauthenticated` instead of returning a null-tenant
|
|
220
|
+
* anonymous Subject. Use it on multi-tenant apps where every call —
|
|
221
|
+
* even an anonymous one — must be scoped to a tenant; without it a
|
|
222
|
+
* tenant-less anonymous Subject can read across the whole DB on
|
|
223
|
+
* tables that aren't `tenant()`-scoped. Ignored when a custom
|
|
224
|
+
* `fallback` is supplied (the fallback owns that decision).
|
|
225
|
+
*/
|
|
226
|
+
readonly anonymousTenantRequired?: boolean;
|
|
227
|
+
}) => ((input: AuthStrategyInput) => Promise<Subject>);
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Compose a list of Effect-native interceptors into ONE function the
|
|
231
|
+
* runtime calls. Reduces right-to-left so the array order reads
|
|
232
|
+
* naturally as outer → inner: `[A, B, C]` produces A(B(C(next))). The
|
|
233
|
+
* output is `undefined` when the list is empty so callers can skip the
|
|
234
|
+
* wrap entirely.
|
|
235
|
+
*
|
|
236
|
+
* Used for mutation / query / action chains alike.
|
|
237
|
+
*/
|
|
238
|
+
export declare const composeRpcInterceptors: (interceptors: ReadonlyArray<RpcInterceptor>) => RpcInterceptor | undefined;
|
|
239
|
+
|
|
240
|
+
export declare class ConnectionInfo extends ConnectionInfo_base {
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
declare const ConnectionInfo_base: Context.TagClass<ConnectionInfo, "@voltro/ConnectionInfo", ConnectionInfoValue>;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Second middleware on every RpcGroup that wants the
|
|
247
|
+
* `ConnectionInfo` service available to handlers. Resolves from the
|
|
248
|
+
* per-call `{clientId}` the @effect/rpc transport passes in. Handlers
|
|
249
|
+
* that re-bind the connection's subject (e.g. `auth.signin` writing
|
|
250
|
+
* an override after a successful credential check) read clientId
|
|
251
|
+
* from this service.
|
|
252
|
+
*
|
|
253
|
+
* Apps without a soft-reauth flow can skip attaching this middleware.
|
|
254
|
+
* The framework provides a default-live Layer in `@voltro/runtime`
|
|
255
|
+
* which the rpcServer wires alongside AuthMiddleware.
|
|
256
|
+
*/
|
|
257
|
+
export declare class ConnectionInfoMiddleware extends ConnectionInfoMiddleware_base {
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
declare const ConnectionInfoMiddleware_base: RpcMiddleware.TagClass<ConnectionInfoMiddleware, "@voltro/ConnectionInfoMiddleware", {
|
|
261
|
+
readonly provides: typeof ConnectionInfo;
|
|
262
|
+
}>;
|
|
263
|
+
|
|
264
|
+
export declare interface ConnectionInfoValue {
|
|
265
|
+
/** Per-connection identifier assigned by the rpc transport. Stable for the
|
|
266
|
+
* lifetime of the underlying WebSocket connection; reused as the key for
|
|
267
|
+
* the per-connection subject override map. */
|
|
268
|
+
readonly clientId: number;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* A handle to a cluster-coordinated periodic task the plugin armed via
|
|
273
|
+
* `PluginBindContext.scheduleCoordinated`. Keep it to `stop()` the task
|
|
274
|
+
* in `onDeactivate` — though the framework also stops every task a plugin
|
|
275
|
+
* armed at shutdown, so an explicit `stop()` is only needed for tasks the
|
|
276
|
+
* plugin wants to cancel EARLY (before shutdown).
|
|
277
|
+
*/
|
|
278
|
+
export declare interface CoordinatedScheduleHandle {
|
|
279
|
+
/** Stop the periodic task. Idempotent. */
|
|
280
|
+
readonly stop: () => void;
|
|
281
|
+
/** The task name (for logging / dedup diagnostics). */
|
|
282
|
+
readonly name: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export declare const defineAction: <const Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All = typeof Schema.Never>(options: {
|
|
286
|
+
readonly name: Name;
|
|
287
|
+
readonly input: Input;
|
|
288
|
+
readonly output: Output;
|
|
289
|
+
readonly error?: Error;
|
|
290
|
+
/** Project this action as a public REST endpoint (innovation/11). */
|
|
291
|
+
readonly publicApi?: PublicApiSpec;
|
|
292
|
+
/** Expose this action as an agent tool (innovation/07). */
|
|
293
|
+
readonly exposeAsTool?: ExposeAsTool;
|
|
294
|
+
}) => ActionProcedureDescriptor<Name, Input, Output, Error>;
|
|
295
|
+
|
|
296
|
+
export declare const defineMutation: <const Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All = typeof Schema.Never>(options: {
|
|
297
|
+
readonly name: Name;
|
|
298
|
+
readonly input: Input;
|
|
299
|
+
readonly output: Output;
|
|
300
|
+
readonly error?: Error;
|
|
301
|
+
/** Optional declarative target(s) — drives auto-optimistic. */
|
|
302
|
+
readonly target?: Target<Schema.Schema.Type<Input>, Schema.Schema.Type<Output>>;
|
|
303
|
+
/** Project this mutation as a public REST endpoint (innovation/11). */
|
|
304
|
+
readonly publicApi?: PublicApiSpec;
|
|
305
|
+
/** Expose this mutation as an agent tool (innovation/07). */
|
|
306
|
+
readonly exposeAsTool?: ExposeAsTool;
|
|
307
|
+
}) => MutationProcedureDescriptor<Name, Input, Output, Error>;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Builder helper that returns the input object unchanged AT RUNTIME
|
|
311
|
+
* but enforces the `VoltroPlugin` shape at the TYPE level. Use over
|
|
312
|
+
* plain object literals when you want the type-checker to catch
|
|
313
|
+
* misnamed hooks or missing required fields at the plugin's
|
|
314
|
+
* declaration site rather than at the `app.config.ts` consumption
|
|
315
|
+
* site.
|
|
316
|
+
*
|
|
317
|
+
* ```ts
|
|
318
|
+
* export const myPlugin = (options: MyOptions): VoltroPlugin =>
|
|
319
|
+
* definePlugin({
|
|
320
|
+
* name: '@vendor/my-plugin',
|
|
321
|
+
* framework: '^1.0.0',
|
|
322
|
+
* interceptMutation: (next, ctx) =>
|
|
323
|
+
* next.pipe(Effect.tap(() => recordAudit(ctx))),
|
|
324
|
+
* onActivate: (ctx) => Effect.sync(() => ctx.logger.info('warming caches')),
|
|
325
|
+
* onDeactivate: () => Effect.void,
|
|
326
|
+
* })
|
|
327
|
+
* ```
|
|
328
|
+
*
|
|
329
|
+
* Identity function — kept narrow so the compiler picks excess-property
|
|
330
|
+
* checks on the literal.
|
|
331
|
+
*/
|
|
332
|
+
export declare const definePlugin: (plugin: VoltroPlugin) => VoltroPlugin;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Helper to declare one rpc route contributed by a plugin. Returns
|
|
336
|
+
* the input unchanged; exists for the type-level enforcement (plus
|
|
337
|
+
* symmetry with `defineMutation` / `defineQuery` user-side).
|
|
338
|
+
*/
|
|
339
|
+
export declare const definePluginRoute: (route: PluginRpcRoute) => PluginRpcRoute;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Declarative builder for a plugin's `services` layer. Wraps the
|
|
343
|
+
* `Context.Tag` + `Layer.succeed` pair so a plugin author doesn't
|
|
344
|
+
* have to manage both. Use when a plugin's service interface is
|
|
345
|
+
* straightforward (a record of functions); reach for raw Layer when
|
|
346
|
+
* the implementation depends on other services (Layer.effect) or
|
|
347
|
+
* needs resource acquisition (Layer.scoped).
|
|
348
|
+
*/
|
|
349
|
+
export declare const definePluginService: <I, S>(identifier: string, implementation: S) => {
|
|
350
|
+
readonly Tag: Context.Tag<I, S>;
|
|
351
|
+
readonly Live: Layer.Layer<I, never, never>;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export declare const defineQuery: <const Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All = typeof Schema.Never>(options: {
|
|
355
|
+
readonly name: Name;
|
|
356
|
+
readonly input: Input;
|
|
357
|
+
readonly output: Output;
|
|
358
|
+
readonly error?: Error;
|
|
359
|
+
/** Optional: the store table(s) this query reads. Declaring it enables
|
|
360
|
+
* auto-optimistic patch routing from mutations targeting any of those
|
|
361
|
+
* tables. For a COMPUTED query (handler returns a shaped value) it is the
|
|
362
|
+
* reactive trigger set: the handler re-runs when ANY listed table changes
|
|
363
|
+
* — pass an array to depend on several (e.g. a matrix joining two tables). */
|
|
364
|
+
readonly source?: string | ReadonlyArray<string>;
|
|
365
|
+
/** Opt into server-side snapshot caching with auto-invalidation. */
|
|
366
|
+
readonly cache?: QueryCacheConfig;
|
|
367
|
+
/** Project this query as a public REST endpoint (innovation/11). */
|
|
368
|
+
readonly publicApi?: PublicApiSpec;
|
|
369
|
+
/** Expose this query as an agent tool (innovation/07). */
|
|
370
|
+
readonly exposeAsTool?: ExposeAsTool;
|
|
371
|
+
}) => QueryProcedureDescriptor<Name, Input, Output, Error>;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Declare a non-reactive server→client stream. `handler(input, ctx)`
|
|
375
|
+
* returns a `Stream<Element>` (e.g. an agent run's `AgentEvent`s); the
|
|
376
|
+
* client consumes it via `useAgentStream`. Reusable for ANY push stream.
|
|
377
|
+
*/
|
|
378
|
+
export declare const defineStream: <const Name extends string, Input extends Schema.Schema.Any, Element extends Schema.Schema.Any, Error extends Schema.Schema.All = typeof Schema.Never>(options: {
|
|
379
|
+
readonly name: Name;
|
|
380
|
+
readonly input: Input;
|
|
381
|
+
readonly element: Element;
|
|
382
|
+
readonly error?: Error;
|
|
383
|
+
}) => StreamProcedureDescriptor<Name, Input, Element, Error>;
|
|
384
|
+
|
|
385
|
+
export declare interface DeleteTarget<Input = unknown> {
|
|
386
|
+
readonly table: string;
|
|
387
|
+
readonly op: 'delete';
|
|
388
|
+
/** Identify the row to remove. Default: `input.id`. */
|
|
389
|
+
readonly identify?: ((input: Input) => string) | undefined;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Compute the patch that turns `prev` into `next`, keyed by row id.
|
|
394
|
+
*
|
|
395
|
+
* Algorithm:
|
|
396
|
+
* - Index both sides by id.
|
|
397
|
+
* - For each id in next: not in prev → `add`; in prev but content
|
|
398
|
+
* differs → `replace`; in prev and equal → no op.
|
|
399
|
+
* - For each id in prev but not in next → `remove`.
|
|
400
|
+
* - `order` is next's id sequence verbatim, so apply can reconstruct
|
|
401
|
+
* the exact ordering (including a pure reorder, where `ops` is empty).
|
|
402
|
+
*
|
|
403
|
+
* Throws if any row lacks an `id` — the caller (dispatcher) only diffs
|
|
404
|
+
* id-keyed result sets; a result without ids must ship a full snapshot,
|
|
405
|
+
* not a patch.
|
|
406
|
+
*/
|
|
407
|
+
export declare const diffRows: (prev: ReadonlyArray<PatchRow>, next: ReadonlyArray<PatchRow>) => RowPatch;
|
|
408
|
+
|
|
409
|
+
/** Normalize the `exposeAsTool` shorthand. `true` is only valid when the
|
|
410
|
+
* descriptor carries a top-level `description`; callers pass that in. */
|
|
411
|
+
export declare type ExposeAsTool = boolean | ExposeAsToolSpec;
|
|
412
|
+
|
|
413
|
+
export declare interface ExposeAsToolSpec {
|
|
414
|
+
/** Shown to the model — REQUIRED to expose (a tool with no description is
|
|
415
|
+
* unusable). What the tool does + when to call it. */
|
|
416
|
+
readonly description: string;
|
|
417
|
+
/** Require human confirmation of the concrete call before it executes.
|
|
418
|
+
* Default posture: writes (mutation/action) confirm, reads don't. */
|
|
419
|
+
readonly confirm?: boolean;
|
|
420
|
+
/** Cap how many times the agent may call this tool per run. */
|
|
421
|
+
readonly maxPerRun?: number;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Cross-cutting error schemas a plugin contributes to EVERY procedure's
|
|
426
|
+
* wire error union (see `VoltroPlugin.errorSchemas`). Unioned into each
|
|
427
|
+
* rpc's `error:` so an interceptor that fails with one of these (e.g. a
|
|
428
|
+
* rate-limit `RateLimited`) decodes as a TYPED error on the client
|
|
429
|
+
* instead of crossing the wire as an untyped defect. Passed identically
|
|
430
|
+
* by the server group (cli/dev.ts) and the generated client group
|
|
431
|
+
* (cli/codegen.ts) so both ends agree on the wire shape.
|
|
432
|
+
*/
|
|
433
|
+
export declare type ExtraErrors = ReadonlyArray<Schema.Schema.All>;
|
|
434
|
+
|
|
435
|
+
/** Release a claim after the handler errored, so a retry can re-process. */
|
|
436
|
+
export declare const failIdempotent: (store: IdempotencyStore, scope: string, key: string) => Promise<void>;
|
|
437
|
+
|
|
438
|
+
/** Record a completed response for replay. */
|
|
439
|
+
export declare const finishIdempotent: (store: IdempotencyStore, scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
|
|
440
|
+
|
|
441
|
+
export declare const hasCallbackRoutes: (s: AuthStrategy) => s is AuthStrategyWithCallback;
|
|
442
|
+
|
|
443
|
+
/** True if the subject holds `scope` (or the `admin:full` bypass). */
|
|
444
|
+
export declare const hasScope: (subject: Subject, scope: string) => boolean;
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Short-circuit response shape. Returning a `HttpInterceptResponse`
|
|
448
|
+
* from the interceptor halts the pipeline — the framework sends the
|
|
449
|
+
* response and never invokes the downstream handler.
|
|
450
|
+
*
|
|
451
|
+
* Returning `null` (or calling `next()` and returning its result)
|
|
452
|
+
* lets the request continue to the normal pipeline.
|
|
453
|
+
*/
|
|
454
|
+
export declare interface HttpInterceptResponse {
|
|
455
|
+
readonly status: number;
|
|
456
|
+
readonly body?: string;
|
|
457
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Per-call context passed to HTTP-request interceptors. Fires BEFORE
|
|
462
|
+
* auth-resolution, BEFORE rpc-routing, BEFORE inspect — at the very
|
|
463
|
+
* top of the HTTP pipeline. Use for pre-auth concerns: rate-limit
|
|
464
|
+
* (token bucket per IP), geo-block (451), bot-detection, header
|
|
465
|
+
* injection for downstream observability, CORS-override.
|
|
466
|
+
*
|
|
467
|
+
* The interceptor is NOT an auth replacement — `AuthMiddleware` still
|
|
468
|
+
* runs on the rpc path. If you need to GATE auth, wrap a different
|
|
469
|
+
* surface (`interceptAction` for unary calls, `interceptMutation` for
|
|
470
|
+
* writes). HTTP-interceptors run on EVERY HTTP request including
|
|
471
|
+
* inspect endpoints and the rpc websocket upgrade.
|
|
472
|
+
*/
|
|
473
|
+
export declare interface HttpRequestContext {
|
|
474
|
+
/** HTTP method (`'GET'`, `'POST'`, etc.). */
|
|
475
|
+
readonly method: string;
|
|
476
|
+
/** Request path (no query string). */
|
|
477
|
+
readonly path: string;
|
|
478
|
+
/** Lowercased request headers. */
|
|
479
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
480
|
+
/** Best-effort remote address. `undefined` when running behind a
|
|
481
|
+
* proxy without `x-forwarded-for`. */
|
|
482
|
+
readonly remoteAddr: string | undefined;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Wraps every HTTP request before the framework's auth + routing.
|
|
487
|
+
* Composed across plugins in declaration order — first listed =
|
|
488
|
+
* outermost. The chain short-circuits as soon as a plugin returns
|
|
489
|
+
* a `HttpInterceptResponse`; later plugins don't see the request.
|
|
490
|
+
*
|
|
491
|
+
* Plugin MUST declare the `http:intercept` permission or boot fails
|
|
492
|
+
* loudly. Plugins without that permission get their `onHttpRequest`
|
|
493
|
+
* hook stripped at activation (so the rest of the plugin still
|
|
494
|
+
* works).
|
|
495
|
+
*/
|
|
496
|
+
export declare type HttpRequestInterceptor = (next: () => Promise<HttpInterceptResponse | null>, ctx: HttpRequestContext) => Promise<HttpInterceptResponse | null>;
|
|
497
|
+
|
|
498
|
+
export declare type IdempotencyOutcome = {
|
|
499
|
+
readonly kind: 'fresh';
|
|
500
|
+
} | {
|
|
501
|
+
readonly kind: 'replay';
|
|
502
|
+
readonly response: IdempotencyResponse;
|
|
503
|
+
} | {
|
|
504
|
+
readonly kind: 'conflict';
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
export declare interface IdempotencyRecord {
|
|
508
|
+
readonly scope: string;
|
|
509
|
+
readonly key: string;
|
|
510
|
+
readonly status: 'in_flight' | 'completed';
|
|
511
|
+
readonly response: IdempotencyResponse | null;
|
|
512
|
+
/** epoch ms */
|
|
513
|
+
readonly createdAt: number;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export declare interface IdempotencyResponse {
|
|
517
|
+
readonly status: number;
|
|
518
|
+
readonly body: unknown;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Build the scope key — keep keys from colliding across tenants + endpoints. */
|
|
522
|
+
export declare const idempotencyScope: (tenantId: string | null | undefined, method: string, path: string) => string;
|
|
523
|
+
|
|
524
|
+
export declare interface IdempotencyStore {
|
|
525
|
+
/** Current record for (scope, key), or null. Read-side (inspect/dashboard). */
|
|
526
|
+
readonly get: (scope: string, key: string) => Promise<IdempotencyRecord | null>;
|
|
527
|
+
/**
|
|
528
|
+
* Atomically claim (scope, key) IF absent OR stale (older than `ttlMs`):
|
|
529
|
+
* write a fresh `in_flight` row and return `'claimed'`. Otherwise return the
|
|
530
|
+
* EXISTING (still-fresh) record. The atomicity here is the whole game — two
|
|
531
|
+
* concurrent same-key requests both hit this, exactly one gets `'claimed'`,
|
|
532
|
+
* the loser reads the winner's record. In SQL this is one
|
|
533
|
+
* `INSERT … ON CONFLICT DO UPDATE … WHERE existing.createdAt < now - ttl`.
|
|
534
|
+
*/
|
|
535
|
+
readonly claim: (scope: string, key: string, now: number, ttlMs: number) => Promise<'claimed' | IdempotencyRecord>;
|
|
536
|
+
/** Flip the claim to `completed` and store the response. */
|
|
537
|
+
readonly complete: (scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
|
|
538
|
+
/** Drop the claim (handler errored → a retry may re-process). */
|
|
539
|
+
readonly release: (scope: string, key: string) => Promise<void>;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** `/<id>` — the op path for a row id. Ids are JSON-pointer-escaped
|
|
543
|
+
* (`~` → `~0`, `/` → `~1`) so a slash or tilde inside a string id can't
|
|
544
|
+
* corrupt the pointer. */
|
|
545
|
+
export declare const idToPath: (id: RowId) => string;
|
|
546
|
+
|
|
547
|
+
export declare interface InsertTarget<Input = unknown, Row = unknown> {
|
|
548
|
+
readonly table: string;
|
|
549
|
+
readonly op: 'insert';
|
|
550
|
+
readonly order?: 'prepend' | 'append' | undefined;
|
|
551
|
+
/**
|
|
552
|
+
* Build the optimistic row from the mutation input. The framework
|
|
553
|
+
* injects `id` (from `optimisticId`) and the `optimistic: true` flag
|
|
554
|
+
* around the return — your `shape` returns ONLY the user-controllable
|
|
555
|
+
* row body. Default when omitted: `{ ...input, id, optimistic: true }`.
|
|
556
|
+
*
|
|
557
|
+
* Return type intentionally excludes `id`: it's server-generated;
|
|
558
|
+
* the optimistic id placeholder is the framework's responsibility.
|
|
559
|
+
* Excluding `optimistic` (also framework-injected) follows the same
|
|
560
|
+
* principle — `Omit<Row, 'id' | 'optimistic'>` lets you describe
|
|
561
|
+
* EVERYTHING ELSE without re-stating the bookkeeping fields.
|
|
562
|
+
*
|
|
563
|
+
* The `optimisticId` parameter is exposed for rare cases where the
|
|
564
|
+
* shape function needs to reference it (e.g., setting a foreign key
|
|
565
|
+
* on a child row spread in the same optimistic insert).
|
|
566
|
+
*/
|
|
567
|
+
readonly shape?: ((input: Input, optimisticId: string) => Omit<Row, 'id' | 'optimistic'>) | undefined;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** True when every row in the set carries an `id`. The dispatcher uses
|
|
571
|
+
* this to decide patch-vs-full-snapshot: a result whose rows aren't
|
|
572
|
+
* id-keyed (rare — a custom projection that drops the id) can't be
|
|
573
|
+
* diffed by id and falls back to shipping the full data. */
|
|
574
|
+
export declare const isIdKeyed: (rows: ReadonlyArray<Readonly<Record<string, unknown>>>) => rows is ReadonlyArray<PatchRow>;
|
|
575
|
+
|
|
576
|
+
export declare const isSystemSubject: (subject: Subject) => boolean;
|
|
577
|
+
|
|
578
|
+
/** In-memory store — the default for single-process dev + the test double. */
|
|
579
|
+
export declare const memoryIdempotencyStore: () => IdempotencyStore;
|
|
580
|
+
|
|
581
|
+
export declare interface MutationProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All> {
|
|
582
|
+
readonly kind: 'mutation';
|
|
583
|
+
readonly name: Name;
|
|
584
|
+
readonly input: Input;
|
|
585
|
+
readonly output: Output;
|
|
586
|
+
readonly error: Error;
|
|
587
|
+
/** Declarative target(s) — drives auto-optimistic on the client AND
|
|
588
|
+
* surfaces which tables this mutation touches (debug, future
|
|
589
|
+
* query-invalidation analytics). Mutations without a target run
|
|
590
|
+
* normally but skip auto-optimistic. */
|
|
591
|
+
readonly target: Target | undefined;
|
|
592
|
+
/** Opt this mutation into a public REST endpoint (innovation/11). */
|
|
593
|
+
readonly publicApi: PublicApiSpec | undefined;
|
|
594
|
+
/** Opt this mutation into the auto-synthesized agent toolset (innovation/07). */
|
|
595
|
+
readonly exposeAsTool: ExposeAsTool | undefined;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
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
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Project a runtime descriptor down to the subset the client needs.
|
|
602
|
+
* Functions (`shape`, `identify`) are passed through by reference —
|
|
603
|
+
* the client's auto-optimistic uses them as-is when present.
|
|
604
|
+
*/
|
|
605
|
+
export declare const normalizeDescriptor: (descriptor: ProcedureDescriptor) => ClientDescriptor;
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* What a plugin contributes to the OTel layer via `contributeObservability`.
|
|
609
|
+
* The OTel types are intentionally `unknown` so this browser-safe protocol
|
|
610
|
+
* package never imports `@opentelemetry/*` (the cli casts to the concrete
|
|
611
|
+
* `SpanProcessor` / `MetricReader` / `Sampler` when wiring `buildTracingLayer`).
|
|
612
|
+
*/
|
|
613
|
+
export declare interface ObservabilityContribution {
|
|
614
|
+
/** Merged into the OTel Resource (e.g. `service.version`,
|
|
615
|
+
* `deployment.environment`). */
|
|
616
|
+
readonly resourceAttributes?: Record<string, string>;
|
|
617
|
+
/** Additional OTel `SpanProcessor`s installed alongside the framework's
|
|
618
|
+
* exporter + buffer sink (e.g. a vendor OTLP exporter or `SentrySpanProcessor`). */
|
|
619
|
+
readonly spanProcessors?: ReadonlyArray<unknown>;
|
|
620
|
+
/** Additional OTel `MetricReader`s (e.g. a vendor OTLP metric reader). */
|
|
621
|
+
readonly metricReaders?: ReadonlyArray<unknown>;
|
|
622
|
+
/** OTel `Sampler` (e.g. Sentry's `SentrySampler` honouring `tracesSampleRate`). */
|
|
623
|
+
readonly sampler?: unknown;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** A single row in a subscription result. Must carry an `id`; everything
|
|
627
|
+
* else is opaque to the patch layer. */
|
|
628
|
+
export declare type PatchRow = Readonly<Record<string, unknown>> & {
|
|
629
|
+
readonly id: RowId;
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
/** Inverse of `idToPath` for the string case. Numeric ids round-trip via
|
|
633
|
+
* the `order` array (which preserves the original type), so apply never
|
|
634
|
+
* needs to re-parse a number out of a path. */
|
|
635
|
+
export declare const pathToId: (path: string) => string;
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Called once at app boot, after `voltro dev` resolves the plugin
|
|
639
|
+
* list and before the rpc server starts accepting connections. Use
|
|
640
|
+
* for warming caches, opening connection pools, registering metrics,
|
|
641
|
+
* or any one-shot setup the plugin needs. Throws abort boot —
|
|
642
|
+
* malformed plugin config should surface as a clear, immediate error.
|
|
643
|
+
*/
|
|
644
|
+
export declare type PluginActivateHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Server-only context delivered to `bindDataStore`, ALONGSIDE the store,
|
|
648
|
+
* AFTER the framework has opened its store + pool. Gives a plugin the
|
|
649
|
+
* framework's ALREADY-OPEN handles so it never rebuilds a pg pool from
|
|
650
|
+
* raw env (the anti-pattern `@voltro/plugin-ratelimit` used to hand-roll)
|
|
651
|
+
* and never runs an un-coordinated per-replica `setInterval` sweep (what
|
|
652
|
+
* presence + governance used to do).
|
|
653
|
+
*
|
|
654
|
+
* These fields are SERVER-only — they are TYPES only in this browser-safe
|
|
655
|
+
* protocol package (the `DataStore` / `SqlClient` imports are erased), and
|
|
656
|
+
* the live values exist only inside the serve pipeline that calls
|
|
657
|
+
* `bindDataStore`. Nothing in the browser-loaded descriptor/schema graph
|
|
658
|
+
* touches them.
|
|
659
|
+
*/
|
|
660
|
+
export declare interface PluginBindContext {
|
|
661
|
+
/**
|
|
662
|
+
* The framework's already-open `SqlClient` (from `@effect/sql`) — the
|
|
663
|
+
* SAME pool the app's store uses. A plugin runs raw SQL through this
|
|
664
|
+
* instead of standing up its OWN `ManagedRuntime` + pool from env. Only
|
|
665
|
+
* present for SQL-backed stores; `undefined` on the in-memory store
|
|
666
|
+
* (there is no SQL engine) — guard with `if (ctx.sql)`.
|
|
667
|
+
*/
|
|
668
|
+
readonly sql?: SqlClient.SqlClient;
|
|
669
|
+
/**
|
|
670
|
+
* Run `effect` every `intervalMs` on ONLY ONE replica per tick —
|
|
671
|
+
* cluster-coordinated via the framework's existing claim-table
|
|
672
|
+
* exactly-once gate (the same seam the cron scheduler uses). Replaces
|
|
673
|
+
* the hand-rolled `setInterval` a plugin used to run inside
|
|
674
|
+
* `bindDataStore`, which fired on EVERY replica un-coordinated. On a
|
|
675
|
+
* single-process / memory / sqlite deployment it simply runs every tick
|
|
676
|
+
* locally (correct — one process needs no fan-out dedup).
|
|
677
|
+
*
|
|
678
|
+
* `name` is the task's stable id (namespace it, e.g. `presence.sweep`);
|
|
679
|
+
* it becomes the claim key. Returns a handle whose `stop()` cancels the
|
|
680
|
+
* task early. The framework also stops every armed task at shutdown.
|
|
681
|
+
*/
|
|
682
|
+
readonly scheduleCoordinated: (name: string, intervalMs: number, effect: () => void | Promise<void>) => CoordinatedScheduleHandle;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Post-commit change event delivered to a plugin's `onChangeEvent` tap.
|
|
687
|
+
* Minimal, browser-safe shape (protocol must not depend on the database
|
|
688
|
+
* package's concrete `ChangeEvent`) — the framework maps its internal event
|
|
689
|
+
* to this before fan-out.
|
|
690
|
+
*/
|
|
691
|
+
export declare interface PluginChangeEvent {
|
|
692
|
+
readonly table: string;
|
|
693
|
+
readonly op: 'insert' | 'update' | 'delete';
|
|
694
|
+
/** Row after the change — present on insert + update, null on delete. */
|
|
695
|
+
readonly new: Record<string, unknown> | null;
|
|
696
|
+
/** Row before the change — present on update + delete, null on insert. */
|
|
697
|
+
readonly old: Record<string, unknown> | null;
|
|
698
|
+
/** How the event reached this process: absent/'inline' = this process's
|
|
699
|
+
* own write; 'injected' = delivered over a cross-instance transport
|
|
700
|
+
* (broadcast bus / CDC consumer). Combine with `changeScope` to act
|
|
701
|
+
* exactly once per change fleet-wide. */
|
|
702
|
+
readonly origin?: 'inline' | 'injected';
|
|
703
|
+
/** The store's change visibility (see `DataStore.changeScope`):
|
|
704
|
+
* 'local' — own writes inline (skip origin 'injected' for
|
|
705
|
+
* exactly-once-on-the-writer); 'fleet' — every replica sees the full
|
|
706
|
+
* stream (elect one worker instead). */
|
|
707
|
+
readonly changeScope: 'local' | 'fleet';
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Plugin-supplied codegen contribution. Emitted into the user app's
|
|
712
|
+
* `rpcGroup.generated.ts` as an extra section AFTER the framework's
|
|
713
|
+
* own output. Use for typed bindings that don't fit the rpc wire —
|
|
714
|
+
* helper exports, type re-exports, accessor functions that wrap the
|
|
715
|
+
* plugin's runtime apis.
|
|
716
|
+
*
|
|
717
|
+
* The framework calls this function once per codegen run. Returns:
|
|
718
|
+
* - a string → emitted verbatim
|
|
719
|
+
* - `null` → contributes nothing
|
|
720
|
+
*
|
|
721
|
+
* The function MUST be pure: it gets the plugin name + the api name
|
|
722
|
+
* + the list of discovered rpc-tag-and-kind tuples (for plugins that
|
|
723
|
+
* want to emit per-rpc bindings). No filesystem / network access —
|
|
724
|
+
* codegen runs as a build step on every dev restart.
|
|
725
|
+
*
|
|
726
|
+
* Typical pattern: emit a typed accessor for the plugin's service Tag:
|
|
727
|
+
*
|
|
728
|
+
* codegen: (ctx) => `
|
|
729
|
+
* export const use${capitalize(ctx.pluginAlias)} = () =>
|
|
730
|
+
* (ctx: AppContext) => ctx[${JSON.stringify(ctx.pluginAlias)}]
|
|
731
|
+
* `
|
|
732
|
+
*
|
|
733
|
+
* The output is wrapped between `// <plugin:NAME>` / `// </plugin:NAME>`
|
|
734
|
+
* markers in the generated file so the user can identify the source.
|
|
735
|
+
*/
|
|
736
|
+
export declare type PluginCodegen = (ctx: PluginCodegenContext) => string | null;
|
|
737
|
+
|
|
738
|
+
export declare interface PluginCodegenContext {
|
|
739
|
+
/** The plugin's full name (`@voltro/plugin-audit`). */
|
|
740
|
+
readonly pluginName: string;
|
|
741
|
+
/** The derived alias (`audit`). */
|
|
742
|
+
readonly pluginAlias: string;
|
|
743
|
+
/** The api app's name from `app.config.ts`. */
|
|
744
|
+
readonly apiName: string;
|
|
745
|
+
/** Flat list of rpc tags discovered in the app (user routes only). */
|
|
746
|
+
readonly rpcTags: ReadonlyArray<{
|
|
747
|
+
readonly kind: 'query' | 'mutation' | 'action' | 'stream' | 'workflow';
|
|
748
|
+
readonly tag: string;
|
|
749
|
+
}>;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* A remote-mounted dashboard surface a plugin contributes. The
|
|
754
|
+
* dashboard-host (voltro-cloud / voltro-devtools) renders the mount
|
|
755
|
+
* by `import()`ing the ESM module at `bundleUrl` and calling the
|
|
756
|
+
* exported component with host-provided context (auth subject,
|
|
757
|
+
* routing, theme tokens).
|
|
758
|
+
*
|
|
759
|
+
* Not iframe — true in-process mount. Trade-offs:
|
|
760
|
+
* - Plugin's bundle MUST be ESM with React/Effect as peerDeps; the
|
|
761
|
+
* host pins the versions. Mismatch logs a warning + still mounts.
|
|
762
|
+
* - No CSS isolation. Plugin authors are expected to use scoped
|
|
763
|
+
* Tailwind classes (the dashboard ships the framework token set)
|
|
764
|
+
* or CSS modules. Inline `style` on a top-level wrapper avoids
|
|
765
|
+
* bleeding host styles in.
|
|
766
|
+
* - Plugin code shares the host JS realm — honor-system sandboxing.
|
|
767
|
+
* Mounts are gated by the `dashboard:mount` permission to keep
|
|
768
|
+
* the operator's audit trail explicit.
|
|
769
|
+
*
|
|
770
|
+
* The framework surfaces declared mounts via
|
|
771
|
+
* `/_voltro/inspect/plugins/dashboard-mounts`. The actual mount
|
|
772
|
+
* runtime ships in voltro-cloud-dashboard + voltro-devtools.
|
|
773
|
+
*/
|
|
774
|
+
export declare interface PluginDashboardMount {
|
|
775
|
+
/** Stable id within the plugin (kebab-case). Full id surfaces as
|
|
776
|
+
* `<plugin-alias>:<mount.id>` in the dashboard's mount registry. */
|
|
777
|
+
readonly id: string;
|
|
778
|
+
/**
|
|
779
|
+
* Where the dashboard host should mount this contribution:
|
|
780
|
+
* - `'page'` — full-page mount under `/plugins/<alias>/<route>`
|
|
781
|
+
* - `'widget'` — card-shaped tile on the dashboard home
|
|
782
|
+
* - `'nav'` — sidebar nav item linking to a `page`-mount route
|
|
783
|
+
*/
|
|
784
|
+
readonly slot: 'page' | 'widget' | 'nav';
|
|
785
|
+
/** Required for `slot: 'page'`. Path is mounted under
|
|
786
|
+
* `/plugins/<plugin-alias>/<route>`. Ignored for widget/nav. */
|
|
787
|
+
readonly route?: string;
|
|
788
|
+
/** Human-readable label (nav text, page title, widget header). */
|
|
789
|
+
readonly label: string;
|
|
790
|
+
/** Optional icon — emoji or a `lucide-react` icon name. */
|
|
791
|
+
readonly icon?: string;
|
|
792
|
+
/**
|
|
793
|
+
* Absolute URL the host fetches at mount time. Must serve an ESM
|
|
794
|
+
* module. CDN-hosted or self-hosted (`https://my-plugin.example/v1/bundle.mjs`).
|
|
795
|
+
* The plugin author is responsible for HTTPS + immutable versioning.
|
|
796
|
+
*/
|
|
797
|
+
readonly bundleUrl: string;
|
|
798
|
+
/** Export name to read from the imported module. Defaults to
|
|
799
|
+
* `'default'`. The export must be a React component. */
|
|
800
|
+
readonly exportName?: string;
|
|
801
|
+
/** Optional dashboard-framework version range the mount supports
|
|
802
|
+
* (npm-semver). Host logs a warning on mismatch but mounts. */
|
|
803
|
+
readonly dashboardVersion?: string;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Called once at graceful shutdown (SIGTERM in production; HMR-reload
|
|
808
|
+
* in dev). Mirror of `onActivate`: close connections, flush buffers,
|
|
809
|
+
* cancel timers. The framework waits up to 5s for `onDeactivate` to
|
|
810
|
+
* settle before SIGKILL falls through.
|
|
811
|
+
*/
|
|
812
|
+
export declare type PluginDeactivateHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* An environment variable a plugin reads. DECLARATION ONLY — metadata, not a
|
|
816
|
+
* read path: the plugin still reads the value itself (typically
|
|
817
|
+
* `options.X ?? process.env.X`). Declaring it makes the plugin's env needs
|
|
818
|
+
* visible to the env manifest (`/_voltro/inspect/env`), the generated
|
|
819
|
+
* `.env.example`, and the dashboard Env panel — so an operator can see, before
|
|
820
|
+
* running, exactly what each plugin expects.
|
|
821
|
+
*
|
|
822
|
+
* Structurally identical to `@voltro/env`'s `DeclaredEnvVar` (kept local here
|
|
823
|
+
* to avoid a protocol → env package dependency).
|
|
824
|
+
*/
|
|
825
|
+
export declare interface PluginEnvVar {
|
|
826
|
+
/** The env var name, e.g. `DD_API_KEY`. */
|
|
827
|
+
readonly name: string;
|
|
828
|
+
/** True when the plugin cannot function without it (no sensible default). */
|
|
829
|
+
readonly required: boolean;
|
|
830
|
+
/** True for tokens / passwords / credential URLs — the manifest then
|
|
831
|
+
* reports `isSet` only, never the value. */
|
|
832
|
+
readonly secret: boolean;
|
|
833
|
+
/** One-line description — shown in `.env.example` + the manifest. */
|
|
834
|
+
readonly description?: string;
|
|
835
|
+
/** Example value for `.env.example`. Never a real secret. */
|
|
836
|
+
readonly example?: string;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* A cross-cutting error schema a plugin merges into EVERY procedure's
|
|
841
|
+
* wire error union. Use when a plugin interceptor can fail with a typed
|
|
842
|
+
* error (e.g. a rate-limit `RateLimited`) that must decode TYPED on the
|
|
843
|
+
* client rather than crossing as an untyped defect.
|
|
844
|
+
*
|
|
845
|
+
* Two halves because the error must be applied in two places that can't
|
|
846
|
+
* share a value:
|
|
847
|
+
* - `schema`: the live `effect/Schema` — used at runtime when the cli
|
|
848
|
+
* assembles the SERVER rpc group.
|
|
849
|
+
* - `import`: where to import the same schema from — used by codegen to
|
|
850
|
+
* emit the import into the generated CLIENT rpc group, so both ends
|
|
851
|
+
* agree on the wire shape. `name` must be the exported identifier.
|
|
852
|
+
*
|
|
853
|
+
* Requires the plugin to ship the schema as a NAMED export from `module`.
|
|
854
|
+
*/
|
|
855
|
+
export declare interface PluginErrorSchema {
|
|
856
|
+
readonly schema: Schema.Schema.All;
|
|
857
|
+
readonly import: {
|
|
858
|
+
readonly module: string;
|
|
859
|
+
readonly name: string;
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/** A public raw-HTTP route a plugin serves on the framework listener. */
|
|
864
|
+
export declare interface PluginHttpRoute {
|
|
865
|
+
/** HTTP method, or `'*'` for any (the handler decides). */
|
|
866
|
+
readonly method: '*' | 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
867
|
+
/** Absolute path prefix, e.g. `/_voltro/storage`. Matches the path AND
|
|
868
|
+
* any sub-path (`/_voltro/storage/abc123`). */
|
|
869
|
+
readonly path: string;
|
|
870
|
+
readonly handle: (req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
export declare interface PluginHttpRouteRequest {
|
|
874
|
+
readonly method: string;
|
|
875
|
+
/** Path WITHOUT query string. */
|
|
876
|
+
readonly path: string;
|
|
877
|
+
/** Query string WITHOUT the leading `?` (empty when absent). Parse with
|
|
878
|
+
* `new URLSearchParams(req.query)`. */
|
|
879
|
+
readonly query: string;
|
|
880
|
+
readonly headers: Record<string, string>;
|
|
881
|
+
readonly rawBody: Uint8Array;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* One HTTP endpoint a plugin contributes under the framework's
|
|
886
|
+
* `/_voltro/inspect/*` introspection surface. Plugins use this to
|
|
887
|
+
* surface tooling / dashboards that don't fit the rpc wire (e.g.
|
|
888
|
+
* Server-Sent-Event streams of plugin-internal state, on-demand
|
|
889
|
+
* health probes, plugin-specific debug dumps).
|
|
890
|
+
*
|
|
891
|
+
* Path convention: `/_voltro/inspect/plugins/<plugin-slug>/<route.path>`.
|
|
892
|
+
* The plugin slug (derived from `plugin.name` with `@scope/` +
|
|
893
|
+
* `plugin-` stripped, KEBAB-CASE — `plugin-cdc-out` → `cdc-out`,
|
|
894
|
+
* instance suffix `#x` → `--x`) is prepended by the framework so
|
|
895
|
+
* plugin endpoints never collide with the framework's own inspect
|
|
896
|
+
* endpoints OR with another plugin's. Kebab (not the camelCase rpc
|
|
897
|
+
* alias) because it's a URL every dashboard fetches. The dashboard
|
|
898
|
+
* lists every plugin's endpoints grouped by plugin in the manifest.
|
|
899
|
+
*
|
|
900
|
+
* Plugin endpoints inherit the SAME auth resolver the framework's
|
|
901
|
+
* own inspect endpoints use (`VOLTRO_INSPECT_TOKEN` by default;
|
|
902
|
+
* customisable by passing an `authResolver` at the http-handler
|
|
903
|
+
* layer). The plugin can NOT bypass auth — that's the framework's
|
|
904
|
+
* job, not the plugin's.
|
|
905
|
+
*/
|
|
906
|
+
/** What a plugin HTTP route returns. `body` may be bytes (e.g. a served
|
|
907
|
+
* blob) or text; `headers` carries redirects (302 `location`) + cache
|
|
908
|
+
* policy. */
|
|
909
|
+
export declare interface PluginHttpRouteResult {
|
|
910
|
+
readonly status: number;
|
|
911
|
+
readonly body?: string | Uint8Array;
|
|
912
|
+
readonly contentType?: string;
|
|
913
|
+
readonly headers?: Record<string, string>;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
export declare interface PluginInspectEndpoint {
|
|
917
|
+
/** HTTP method. */
|
|
918
|
+
readonly method: 'GET' | 'POST' | 'DELETE';
|
|
919
|
+
/**
|
|
920
|
+
* Path relative to the plugin's inspect prefix. Leading slash
|
|
921
|
+
* optional. `health` → `/_voltro/inspect/plugins/<slug>/health`.
|
|
922
|
+
*/
|
|
923
|
+
readonly path: string;
|
|
924
|
+
/** Optional human-readable description; surfaced in the dashboard. */
|
|
925
|
+
readonly description?: string;
|
|
926
|
+
/**
|
|
927
|
+
* Effect-based handler. Receives the raw request + headers,
|
|
928
|
+
* returns the response shape. Body parsing is the handler's
|
|
929
|
+
* job — the framework just hands the string through. JSON/CORS
|
|
930
|
+
* are sugar on top: returning `{ json: ... }` from the response
|
|
931
|
+
* shape sets the right content-type + serialises.
|
|
932
|
+
*
|
|
933
|
+
* Long-running responses (SSE, streaming) are NOT supported in
|
|
934
|
+
* v1 — every endpoint returns one buffered response. SSE plugins
|
|
935
|
+
* can route to a separate path mounted by the plugin's own
|
|
936
|
+
* `routes:` array via the rpc layer, which DOES stream.
|
|
937
|
+
*/
|
|
938
|
+
readonly handler: (request: PluginInspectRequest) => Effect.Effect<PluginInspectResponse, unknown, never>;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export declare interface PluginInspectRequest {
|
|
942
|
+
readonly method: 'GET' | 'POST' | 'DELETE';
|
|
943
|
+
/** Full URL the client requested (path + query). */
|
|
944
|
+
readonly url: string;
|
|
945
|
+
/** Lowercased headers. */
|
|
946
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
947
|
+
/** Raw body (empty string on GET / DELETE). */
|
|
948
|
+
readonly body: string;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export declare type PluginInspectResponse = {
|
|
952
|
+
readonly kind: 'json';
|
|
953
|
+
readonly status?: number;
|
|
954
|
+
readonly data: unknown;
|
|
955
|
+
} | {
|
|
956
|
+
readonly kind: 'text';
|
|
957
|
+
readonly status?: number;
|
|
958
|
+
readonly contentType?: string;
|
|
959
|
+
readonly body: string;
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Called ONCE per host across the plugin's lifetime — the first time
|
|
964
|
+
* it's seen by the host. Used for one-time schema migrations + resource
|
|
965
|
+
* provisioning that survive across deactivate/activate cycles. Skipped
|
|
966
|
+
* on subsequent boots once the host has recorded the install.
|
|
967
|
+
*
|
|
968
|
+
* v1 model: install state is in-process only (in-memory marker keyed by
|
|
969
|
+
* plugin name + version). A future slice will persist it to
|
|
970
|
+
* `_voltro_plugin_installs` so install/uninstall idempotency survives
|
|
971
|
+
* process restarts. For now, treat `onInstall` as "what runs on a fresh
|
|
972
|
+
* dev process the first time this plugin is wired" — sufficient for the
|
|
973
|
+
* "create a schema table on first wire-up" use case.
|
|
974
|
+
*/
|
|
975
|
+
export declare type PluginInstallHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Per-app context handed to lifecycle hooks. The shape is intentionally
|
|
979
|
+
* thin — the framework's deeper services (DataStore, Logger, etc.) are
|
|
980
|
+
* available via the per-request `AppContext` plugins see in their rpc
|
|
981
|
+
* interceptors. Lifecycle hooks run OUTSIDE any request, so they
|
|
982
|
+
* receive a minimal slice:
|
|
983
|
+
*
|
|
984
|
+
* - `app`: the resolved app-config metadata
|
|
985
|
+
* (`{ name, type, voltroVersion }`).
|
|
986
|
+
* - `logger`: a scoped `@voltro/logger` instance keyed to the
|
|
987
|
+
* plugin's `name`, so log lines are attributable.
|
|
988
|
+
* - `env`: the process env (alias of `process.env` for typing).
|
|
989
|
+
* - `config`: the plugin's own validated config (if `configSchema`
|
|
990
|
+
* declared) — already decoded against the plugin's Schema, ready
|
|
991
|
+
* for the hook to consume. Unknown otherwise.
|
|
992
|
+
*/
|
|
993
|
+
export declare interface PluginLifecycleContext {
|
|
994
|
+
readonly app: {
|
|
995
|
+
readonly name: string;
|
|
996
|
+
readonly type: 'api' | 'web';
|
|
997
|
+
readonly voltroVersion: string;
|
|
998
|
+
};
|
|
999
|
+
readonly logger: {
|
|
1000
|
+
info: (message: string, fields?: Record<string, unknown>) => void;
|
|
1001
|
+
warn: (message: string, fields?: Record<string, unknown>) => void;
|
|
1002
|
+
error: (message: string, fields?: Record<string, unknown>) => void;
|
|
1003
|
+
debug: (message: string, fields?: Record<string, unknown>) => void;
|
|
1004
|
+
};
|
|
1005
|
+
readonly env: NodeJS.ProcessEnv;
|
|
1006
|
+
/** Validated plugin config (if `configSchema` was declared). */
|
|
1007
|
+
readonly config: unknown;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
export declare interface PluginMigration {
|
|
1011
|
+
readonly id: string;
|
|
1012
|
+
readonly description?: string;
|
|
1013
|
+
readonly up: (sql: PluginMigrationSqlClient) => Effect.Effect<void, unknown>;
|
|
1014
|
+
readonly down?: (sql: PluginMigrationSqlClient) => Effect.Effect<void, unknown>;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* A plugin-supplied custom migration. Runs ONCE per app DB, tracked in
|
|
1019
|
+
* the framework's `_voltro_plugin_migrations` ledger so re-runs of
|
|
1020
|
+
* `voltro dev` skip already-applied steps. Use for setup the
|
|
1021
|
+
* declarative-tables path can't express: extension installs (`CREATE
|
|
1022
|
+
* EXTENSION pgcrypto`), seed data, DDL for pre-existing /
|
|
1023
|
+
* externally-owned tables the plugin wraps, etc.
|
|
1024
|
+
*
|
|
1025
|
+
* Ids are stable strings the plugin author owns. The framework
|
|
1026
|
+
* namespaces them as `<plugin-alias>__<migration.id>` in the ledger
|
|
1027
|
+
* so two plugins can each ship a `'001-init'` without collision.
|
|
1028
|
+
*
|
|
1029
|
+
* The `up` Effect runs against the live `SqlClient` after the
|
|
1030
|
+
* framework's own schema apply. Failures abort boot (loud +
|
|
1031
|
+
* recoverable: fix + restart). Down-migrations are optional and not
|
|
1032
|
+
* auto-run — they exist for ops tooling.
|
|
1033
|
+
*/
|
|
1034
|
+
/**
|
|
1035
|
+
* The `sql` parameter the framework hands the migration is the live
|
|
1036
|
+
* `SqlClient.SqlClient` from `@effect/sql`. The reference is type-only,
|
|
1037
|
+
* so it adds no runtime dependency — `@voltro/protocol` still ships
|
|
1038
|
+
* without pulling `@effect/sql` into a plugin's bundle.
|
|
1039
|
+
*/
|
|
1040
|
+
export declare type PluginMigrationSqlClient = SqlClient.SqlClient;
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Declared permission scope. The framework gates hook surfaces on
|
|
1044
|
+
* the declared set: plugins that ship `onHttpRequest` without
|
|
1045
|
+
* declaring `'http:intercept'` are rejected at boot. Pattern-perms
|
|
1046
|
+
* support a `:*` wildcard suffix (`'secrets:read:auth0:*'` covers
|
|
1047
|
+
* `'secrets:read:auth0:clientSecret'`).
|
|
1048
|
+
*
|
|
1049
|
+
* The static surface is a typed union so plugin authors get autocomplete;
|
|
1050
|
+
* pattern perms fall through the template-literal arm. Surfaced in the
|
|
1051
|
+
* boot log + `/_voltro/inspect/plugins` so operators audit what each
|
|
1052
|
+
* plugin asks for before installing.
|
|
1053
|
+
*/
|
|
1054
|
+
export declare type PluginPermission = 'rpc:intercept:mutation' | 'rpc:intercept:query' | 'rpc:intercept:action' | 'schedule:fire' | 'schedule:read' | 'workflow:step' | 'http:intercept' | 'inspect:read' | 'inspect:write' | 'dashboard:mount' | 'store:write' | 'migration:run' | 'store:changes:read' | `secrets:read:${string}` | `network:outbound:${string}` | `plugin:hook:${string}`;
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Per-call context handed to a plugin-contributed rpc route's
|
|
1058
|
+
* executor. Mirror of the user-handler `AppContext` shape but typed
|
|
1059
|
+
* loosely so the plugin doesn't need to depend on `@voltro/runtime`.
|
|
1060
|
+
*/
|
|
1061
|
+
export declare interface PluginRouteContext {
|
|
1062
|
+
readonly request: {
|
|
1063
|
+
readonly subject: Subject;
|
|
1064
|
+
readonly traceId: string;
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Client-facing declaration of a plugin RPC route's descriptor, so the codegen
|
|
1070
|
+
* can emit it into the generated client rpc group (`appGroup` + `appDescriptors`)
|
|
1071
|
+
* — without this, a plugin route is mounted server-side but the browser client
|
|
1072
|
+
* can't resolve its tag ("not reachable on client").
|
|
1073
|
+
*
|
|
1074
|
+
* `import` MUST point at a BROWSER-SAFE module that exports the route's descriptor
|
|
1075
|
+
* value (tag + input/output/error Schema, NO executor, NO `node:*`/server deps) —
|
|
1076
|
+
* the same discipline `PluginErrorSchema.import` follows (e.g.
|
|
1077
|
+
* `@voltro/plugin-storage/rpc`). The generated file is loaded value-level by the
|
|
1078
|
+
* web client + guarded by `assertBrowserSafeRpcGroup`, so a server import here is
|
|
1079
|
+
* a boot failure. `tag` is the route's EFFECTIVE tag (`<alias>.<name>`); `kind`
|
|
1080
|
+
* selects `queryToRpc` / `mutationToRpc` / `actionToRpc`.
|
|
1081
|
+
*/
|
|
1082
|
+
export declare interface PluginRpcClientDescriptor {
|
|
1083
|
+
readonly tag: string;
|
|
1084
|
+
readonly kind: RpcKind;
|
|
1085
|
+
readonly import: {
|
|
1086
|
+
readonly module: string;
|
|
1087
|
+
readonly name: string;
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* One descriptor + executor pair a plugin contributes to the host's
|
|
1093
|
+
* rpc surface. Same shape as a user-authored mutation/query/action,
|
|
1094
|
+
* but registered programmatically by the plugin instead of via file
|
|
1095
|
+
* discovery.
|
|
1096
|
+
*
|
|
1097
|
+
* The plugin's name is prepended to the rpc tag at registration time
|
|
1098
|
+
* unless the descriptor already carries a dot — e.g. a plugin named
|
|
1099
|
+
* `@voltro/plugin-audit` exporting `route({ name: 'list' })` lands
|
|
1100
|
+
* as `audit.list`; exporting `route({ name: 'admin.events' })` lands
|
|
1101
|
+
* unchanged. This guarantees plugin routes never collide with
|
|
1102
|
+
* user-authored routes (user routes don't ship the plugin prefix) and
|
|
1103
|
+
* lets the dashboard show them grouped by plugin.
|
|
1104
|
+
*/
|
|
1105
|
+
export declare interface PluginRpcRoute {
|
|
1106
|
+
/** 'mutation' | 'query' | 'action' — determines the runtime path. */
|
|
1107
|
+
readonly kind: RpcKind;
|
|
1108
|
+
/** RPC tag relative to the plugin (e.g. `'list'`, `'admin.events'`). */
|
|
1109
|
+
readonly name: string;
|
|
1110
|
+
/** Optional human-readable description; surfaced in the dashboard. */
|
|
1111
|
+
readonly description?: string;
|
|
1112
|
+
/** Input schema — same `effect/Schema` shape user routes use. */
|
|
1113
|
+
readonly input: Schema.Schema.Any;
|
|
1114
|
+
/** Output schema. */
|
|
1115
|
+
readonly output: Schema.Schema.Any;
|
|
1116
|
+
/** Optional typed-error schema (TaggedError union). */
|
|
1117
|
+
readonly error?: Schema.Schema.All;
|
|
1118
|
+
/**
|
|
1119
|
+
* REACTIVE source table(s) — ONLY for `kind: 'query'`. When set, the
|
|
1120
|
+
* framework drives the query REACTIVELY: it re-runs the executor and pushes
|
|
1121
|
+
* a fresh result over the SAME subscription/WS transport app reactive
|
|
1122
|
+
* queries use, every time one of the named tables changes. This makes a
|
|
1123
|
+
* plugin query PUSH-DRIVEN — no client polling — by reusing the framework's
|
|
1124
|
+
* computed-reactive-query machinery (the descriptor is browser-safe, the
|
|
1125
|
+
* executor runs server-side).
|
|
1126
|
+
*
|
|
1127
|
+
* The executor returns the query's VALUE (an array / object) — the same
|
|
1128
|
+
* shape it returns for a poll; the framework recomputes it on change. Omit
|
|
1129
|
+
* for a plain (poll-only) plugin query, a mutation, or an action. For a
|
|
1130
|
+
* query that mirrors a plugin table, set this to that table's name (e.g.
|
|
1131
|
+
* `@voltro/plugin-presence` sets `_voltro_presence`, which is `.reactive()`).
|
|
1132
|
+
*
|
|
1133
|
+
* `| undefined` is explicit so a plugin can spread a browser-safe query
|
|
1134
|
+
* DESCRIPTOR (which always carries `source: … | undefined`) into a route
|
|
1135
|
+
* literal under `exactOptionalPropertyTypes` without a cast.
|
|
1136
|
+
*/
|
|
1137
|
+
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
1138
|
+
/**
|
|
1139
|
+
* Effect-only executor. The base layer is provided by the framework
|
|
1140
|
+
* (DataStore, HttpClient, the plugin's own service Tags) — the
|
|
1141
|
+
* executor declares its requirements through Effect's R channel like
|
|
1142
|
+
* any other handler. Mutations land inside `store.transactional()`
|
|
1143
|
+
* automatically; queries return a `QueryDescriptor`-shaped result;
|
|
1144
|
+
* actions run without a transaction.
|
|
1145
|
+
*/
|
|
1146
|
+
readonly execute: (input: never, ctx: PluginRouteContext) => Effect.Effect<unknown, unknown, never>;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* What a plugin contributes to the host app's schema. `tables` join
|
|
1151
|
+
* the user's table set + go through the same `applySchema()` path
|
|
1152
|
+
* (idempotent CREATE TABLE IF NOT EXISTS). `migrations` run after
|
|
1153
|
+
* `applySchema()` against the live SqlClient, tracked in
|
|
1154
|
+
* `_voltro_plugin_migrations`.
|
|
1155
|
+
*
|
|
1156
|
+
* Plugin MUST declare `store:write` for `tables`, `migration:run`
|
|
1157
|
+
* for `migrations`. Boot fails if the declared-permission set is
|
|
1158
|
+
* insufficient.
|
|
1159
|
+
*/
|
|
1160
|
+
export declare interface PluginSchemaContribution {
|
|
1161
|
+
readonly tables?: ReadonlyArray<unknown>;
|
|
1162
|
+
readonly migrations?: ReadonlyArray<PluginMigration>;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* A scaffold template a plugin contributes to `voltro init` /
|
|
1167
|
+
* `voltro add-app`. Templates are listed under the plugin's name in
|
|
1168
|
+
* `voltro list-templates`; the operator picks one and the CLI seeds
|
|
1169
|
+
* the project tree.
|
|
1170
|
+
*
|
|
1171
|
+
* The framework keeps the template registry per-plugin scoped so two
|
|
1172
|
+
* plugins can each ship an `auth` template without collision: the
|
|
1173
|
+
* full template id is `<plugin-alias>:<template.id>`.
|
|
1174
|
+
*
|
|
1175
|
+
* v1 ships the registration shape + listing; the actual
|
|
1176
|
+
* `voltro init --from-plugin` invocation lands once a real consumer
|
|
1177
|
+
* needs it. Until then, templates are surfaced in
|
|
1178
|
+
* `/_voltro/inspect/plugins` so the dashboard can preview them.
|
|
1179
|
+
*/
|
|
1180
|
+
export declare interface PluginTemplate {
|
|
1181
|
+
/** Stable id within the plugin (kebab-case). The framework
|
|
1182
|
+
* prepends the plugin alias when surfacing — `audit:starter` /
|
|
1183
|
+
* `webhooks:stripe-receiver`. */
|
|
1184
|
+
readonly id: string;
|
|
1185
|
+
/** Human-readable label for the picker. */
|
|
1186
|
+
readonly title: string;
|
|
1187
|
+
/** One-line description; surfaced in `voltro list-templates`. */
|
|
1188
|
+
readonly description: string;
|
|
1189
|
+
/** Which kind of app this template scaffolds. */
|
|
1190
|
+
readonly kind: 'api' | 'web' | 'fullstack';
|
|
1191
|
+
/**
|
|
1192
|
+
* Source directory inside the plugin's package. The CLI resolves
|
|
1193
|
+
* it relative to the plugin's npm-resolved root. Files inside are
|
|
1194
|
+
* copied verbatim into the target project; `package.json` is
|
|
1195
|
+
* patched to point dependencies at the plugin.
|
|
1196
|
+
*/
|
|
1197
|
+
readonly sourcePath: string;
|
|
1198
|
+
/** Optional list of post-install hints surfaced after scaffolding. */
|
|
1199
|
+
readonly postInstallSteps?: ReadonlyArray<string>;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Called ONCE when the host removes the plugin. Mirror of `onInstall`.
|
|
1204
|
+
* Used to clean up the plugin's persisted resources (drop tables,
|
|
1205
|
+
* delete queue topics) so an uninstall fully reverses an install.
|
|
1206
|
+
*
|
|
1207
|
+
* v1 model: triggered explicitly by `voltro plugins uninstall <name>`
|
|
1208
|
+
* (CLI command — see manifest plan). NOT triggered on plain shutdown
|
|
1209
|
+
* (that's `onDeactivate`). Plugins are responsible for making
|
|
1210
|
+
* `onUninstall` idempotent — repeating it should be a no-op once the
|
|
1211
|
+
* resources are gone.
|
|
1212
|
+
*/
|
|
1213
|
+
export declare type PluginUninstallHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
|
|
1214
|
+
|
|
1215
|
+
export declare type ProcedureDescriptor = QueryProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | MutationProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | ActionProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | StreamProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All>;
|
|
1216
|
+
|
|
1217
|
+
export declare const PROTOCOL_VERSION: 1;
|
|
1218
|
+
|
|
1219
|
+
export declare type ProtocolVersion = typeof PROTOCOL_VERSION;
|
|
1220
|
+
|
|
1221
|
+
export declare interface PublicApiSpec {
|
|
1222
|
+
/** Derived by kind when omitted: query→GET, mutation/action→POST. */
|
|
1223
|
+
readonly method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
1224
|
+
/** Derived from the tag + version when omitted: `/<version>/<tag-as-path>`. */
|
|
1225
|
+
readonly path?: string;
|
|
1226
|
+
/** Path version segment; multiple versions coexist as separate descriptors. */
|
|
1227
|
+
readonly version?: string;
|
|
1228
|
+
/** Additional API-key scopes required ON TOP of the handler's RBAC. */
|
|
1229
|
+
readonly scopes?: ReadonlyArray<string>;
|
|
1230
|
+
/** Per-endpoint rate limit (rides @voltro/plugin-ratelimit when mounted). */
|
|
1231
|
+
readonly rateLimit?: {
|
|
1232
|
+
readonly limit: number;
|
|
1233
|
+
readonly window: string;
|
|
1234
|
+
};
|
|
1235
|
+
/** Honor `Idempotency-Key` for mutating methods (existing HTTP idempotency). */
|
|
1236
|
+
readonly idempotent?: boolean;
|
|
1237
|
+
/** Replacement hint → `Deprecation: true` header. */
|
|
1238
|
+
readonly deprecated?: string;
|
|
1239
|
+
/** ISO date → `Sunset:` header, `410` past the date. */
|
|
1240
|
+
readonly sunset?: string;
|
|
1241
|
+
readonly summary?: string;
|
|
1242
|
+
readonly description?: string;
|
|
1243
|
+
/** Streaming queries over request/response: 'snapshot' (default — first
|
|
1244
|
+
* snapshot, like POST /rpc) | 'sse' (Server-Sent Events of snapshot+deltas). */
|
|
1245
|
+
readonly stream?: 'snapshot' | 'sse';
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/** Publish a server-primitive error. Cheap + sync; a broken reporter can
|
|
1249
|
+
* never break the caller (each listener is isolated). Safe no-op when
|
|
1250
|
+
* nothing is subscribed — primitives call it unconditionally. */
|
|
1251
|
+
export declare const publishServerError: (event: ServerErrorEvent) => void;
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* Server-side snapshot caching for a query (see `defineQuery`). When set,
|
|
1255
|
+
* the dispatcher caches the initial snapshot result and auto-invalidates it
|
|
1256
|
+
* when a mutation writes any table the query depends on.
|
|
1257
|
+
*/
|
|
1258
|
+
export declare interface QueryCacheConfig {
|
|
1259
|
+
/** Fresh window — seconds (number) or a duration string (`'30s'`,
|
|
1260
|
+
* `'5m'`, `'1h'`). */
|
|
1261
|
+
readonly ttl: number | string;
|
|
1262
|
+
/** Stale-while-revalidate window past `ttl` (same units). While stale,
|
|
1263
|
+
* the cached value is served immediately and refreshed in the
|
|
1264
|
+
* background. */
|
|
1265
|
+
readonly swr?: number | string | undefined;
|
|
1266
|
+
/**
|
|
1267
|
+
* Cross-subject safety — REQUIRED, no default, because guessing wrong
|
|
1268
|
+
* leaks rows. `'subject'` keys the cache by the caller's subject id
|
|
1269
|
+
* (safe for tenant- or row-filtered queries). `'global'` shares ONE
|
|
1270
|
+
* entry across every caller — legal ONLY when the resolved query is
|
|
1271
|
+
* subject-independent (reference data). Rubric: does the resolved
|
|
1272
|
+
* predicate depend on the caller? Yes → `subject`; no → `global`.
|
|
1273
|
+
*/
|
|
1274
|
+
readonly scope: 'subject' | 'global';
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
export declare interface QueryProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All> {
|
|
1278
|
+
readonly kind: 'query';
|
|
1279
|
+
readonly name: Name;
|
|
1280
|
+
readonly input: Input;
|
|
1281
|
+
readonly output: Output;
|
|
1282
|
+
readonly error: Error;
|
|
1283
|
+
/** Which DataStore table(s) this query reads. Drives auto-optimistic patch
|
|
1284
|
+
* routing — mutations that target ANY of these tables patch caches keyed
|
|
1285
|
+
* to queries with a matching `source`. A computed query re-runs when ANY
|
|
1286
|
+
* listed table changes (pass an array to depend on several). Optional:
|
|
1287
|
+
* queries without a source never receive auto-patches (joins, aggregates). */
|
|
1288
|
+
readonly source: string | ReadonlyArray<string> | undefined;
|
|
1289
|
+
/** Server-side snapshot cache config. Absent → never cached (the
|
|
1290
|
+
* default; the dispatcher already keeps live subscriptions fresh). */
|
|
1291
|
+
readonly cache: QueryCacheConfig | undefined;
|
|
1292
|
+
/** Opt this query into a public REST endpoint (innovation/11). */
|
|
1293
|
+
readonly publicApi: PublicApiSpec | undefined;
|
|
1294
|
+
/** Opt this query into the auto-synthesized agent toolset (innovation/07). */
|
|
1295
|
+
readonly exposeAsTool: ExposeAsTool | undefined;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
export declare const queryToRpc: <Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: QueryProcedureDescriptor<Name, Input, Output, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Schema.Union<[Schema.Struct<{
|
|
1299
|
+
_tag: Schema.Literal<["snapshot"]>;
|
|
1300
|
+
revision: typeof Schema.Number;
|
|
1301
|
+
data: Output;
|
|
1302
|
+
computed: Schema.optional<typeof Schema.Boolean>;
|
|
1303
|
+
}>, Schema.Struct<{
|
|
1304
|
+
_tag: Schema.Literal<["delta"]>;
|
|
1305
|
+
revision: typeof Schema.Number;
|
|
1306
|
+
emittedAt: typeof Schema.Number;
|
|
1307
|
+
patch: Schema.Struct<{
|
|
1308
|
+
ops: Schema.Array$<Schema.Union<[Schema.Struct<{
|
|
1309
|
+
op: Schema.Literal<["add"]>;
|
|
1310
|
+
path: typeof Schema.String;
|
|
1311
|
+
value: Schema.refine<{
|
|
1312
|
+
readonly [x: string]: unknown;
|
|
1313
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1314
|
+
readonly id: RowId;
|
|
1315
|
+
}, Schema.Schema<{
|
|
1316
|
+
readonly [x: string]: unknown;
|
|
1317
|
+
}, {
|
|
1318
|
+
readonly [x: string]: unknown;
|
|
1319
|
+
}, never>>;
|
|
1320
|
+
}>, Schema.Struct<{
|
|
1321
|
+
op: Schema.Literal<["replace"]>;
|
|
1322
|
+
path: typeof Schema.String;
|
|
1323
|
+
value: Schema.refine<{
|
|
1324
|
+
readonly [x: string]: unknown;
|
|
1325
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1326
|
+
readonly id: RowId;
|
|
1327
|
+
}, Schema.Schema<{
|
|
1328
|
+
readonly [x: string]: unknown;
|
|
1329
|
+
}, {
|
|
1330
|
+
readonly [x: string]: unknown;
|
|
1331
|
+
}, never>>;
|
|
1332
|
+
}>, Schema.Struct<{
|
|
1333
|
+
op: Schema.Literal<["remove"]>;
|
|
1334
|
+
path: typeof Schema.String;
|
|
1335
|
+
}>]>>;
|
|
1336
|
+
order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
|
|
1337
|
+
}>;
|
|
1338
|
+
}>, Schema.Struct<{
|
|
1339
|
+
_tag: Schema.Literal<["error"]>;
|
|
1340
|
+
error: typeof Schema.Unknown;
|
|
1341
|
+
revision: Schema.optional<typeof Schema.Number>;
|
|
1342
|
+
}>]>, Schema.Schema.All>, typeof Schema.Never, never>;
|
|
1343
|
+
|
|
1344
|
+
/** Gate a handler on a scope; fails with a typed `ScopeError` if missing. */
|
|
1345
|
+
export declare const requireScope: (subject: Subject, scope: string) => Effect.Effect<void, ScopeError>;
|
|
1346
|
+
|
|
1347
|
+
/** The id of a row, addressed in op paths as `/<id>`. */
|
|
1348
|
+
export declare type RowId = string | number;
|
|
1349
|
+
|
|
1350
|
+
/** The full delta payload: the per-row ops plus the authoritative id
|
|
1351
|
+
* ordering of the resulting set. */
|
|
1352
|
+
export declare interface RowPatch {
|
|
1353
|
+
readonly ops: ReadonlyArray<RowPatchOp>;
|
|
1354
|
+
/** Id sequence of the resulting (next) row set, in order. */
|
|
1355
|
+
readonly order: ReadonlyArray<RowId>;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* One RFC-6902-style op against an id-keyed row set. `path` is always
|
|
1360
|
+
* `/<id>` (the row's id), NOT an array index — this is the deliberate
|
|
1361
|
+
* departure that makes the diff reorder-stable.
|
|
1362
|
+
*
|
|
1363
|
+
* - `replace` — the row with this id exists in both prev and next but
|
|
1364
|
+
* its content changed; `value` is the full next row.
|
|
1365
|
+
* - `add` — the row with this id is new in next; `value` is it.
|
|
1366
|
+
* - `remove` — the row with this id is gone in next.
|
|
1367
|
+
*/
|
|
1368
|
+
export declare type RowPatchOp = {
|
|
1369
|
+
readonly op: 'add';
|
|
1370
|
+
readonly path: string;
|
|
1371
|
+
readonly value: PatchRow;
|
|
1372
|
+
} | {
|
|
1373
|
+
readonly op: 'replace';
|
|
1374
|
+
readonly path: string;
|
|
1375
|
+
readonly value: PatchRow;
|
|
1376
|
+
} | {
|
|
1377
|
+
readonly op: 'remove';
|
|
1378
|
+
readonly path: string;
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
export declare const rowPatchOpSchema: Schema.Union<[Schema.Struct<{
|
|
1382
|
+
op: Schema.Literal<["add"]>;
|
|
1383
|
+
path: typeof Schema.String;
|
|
1384
|
+
value: Schema.refine<{
|
|
1385
|
+
readonly [x: string]: unknown;
|
|
1386
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1387
|
+
readonly id: RowId;
|
|
1388
|
+
}, Schema.Schema<{
|
|
1389
|
+
readonly [x: string]: unknown;
|
|
1390
|
+
}, {
|
|
1391
|
+
readonly [x: string]: unknown;
|
|
1392
|
+
}, never>>;
|
|
1393
|
+
}>, Schema.Struct<{
|
|
1394
|
+
op: Schema.Literal<["replace"]>;
|
|
1395
|
+
path: typeof Schema.String;
|
|
1396
|
+
value: Schema.refine<{
|
|
1397
|
+
readonly [x: string]: unknown;
|
|
1398
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1399
|
+
readonly id: RowId;
|
|
1400
|
+
}, Schema.Schema<{
|
|
1401
|
+
readonly [x: string]: unknown;
|
|
1402
|
+
}, {
|
|
1403
|
+
readonly [x: string]: unknown;
|
|
1404
|
+
}, never>>;
|
|
1405
|
+
}>, Schema.Struct<{
|
|
1406
|
+
op: Schema.Literal<["remove"]>;
|
|
1407
|
+
path: typeof Schema.String;
|
|
1408
|
+
}>]>;
|
|
1409
|
+
|
|
1410
|
+
export declare const rowPatchSchema: Schema.Struct<{
|
|
1411
|
+
ops: Schema.Array$<Schema.Union<[Schema.Struct<{
|
|
1412
|
+
op: Schema.Literal<["add"]>;
|
|
1413
|
+
path: typeof Schema.String;
|
|
1414
|
+
value: Schema.refine<{
|
|
1415
|
+
readonly [x: string]: unknown;
|
|
1416
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1417
|
+
readonly id: RowId;
|
|
1418
|
+
}, Schema.Schema<{
|
|
1419
|
+
readonly [x: string]: unknown;
|
|
1420
|
+
}, {
|
|
1421
|
+
readonly [x: string]: unknown;
|
|
1422
|
+
}, never>>;
|
|
1423
|
+
}>, Schema.Struct<{
|
|
1424
|
+
op: Schema.Literal<["replace"]>;
|
|
1425
|
+
path: typeof Schema.String;
|
|
1426
|
+
value: Schema.refine<{
|
|
1427
|
+
readonly [x: string]: unknown;
|
|
1428
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1429
|
+
readonly id: RowId;
|
|
1430
|
+
}, Schema.Schema<{
|
|
1431
|
+
readonly [x: string]: unknown;
|
|
1432
|
+
}, {
|
|
1433
|
+
readonly [x: string]: unknown;
|
|
1434
|
+
}, never>>;
|
|
1435
|
+
}>, Schema.Struct<{
|
|
1436
|
+
op: Schema.Literal<["remove"]>;
|
|
1437
|
+
path: typeof Schema.String;
|
|
1438
|
+
}>]>>;
|
|
1439
|
+
order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
|
|
1440
|
+
}>;
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Effect-native interceptor. Receives the rest of the chain as an
|
|
1444
|
+
* `Effect`; the interceptor returns an `Effect` that wraps it (or
|
|
1445
|
+
* substitutes a different one). Whatever the returned Effect produces
|
|
1446
|
+
* becomes the final result.
|
|
1447
|
+
*
|
|
1448
|
+
* Typical patterns:
|
|
1449
|
+
*
|
|
1450
|
+
* // Pre-only authz: short-circuit BEFORE the executor runs.
|
|
1451
|
+
* const guard: RpcInterceptor = (next, ctx) =>
|
|
1452
|
+
* ctx.tag.startsWith('admin.') && ctx.subject.type !== 'user'
|
|
1453
|
+
* ? Effect.fail(new Forbidden({ tag: ctx.tag }))
|
|
1454
|
+
* : next
|
|
1455
|
+
*
|
|
1456
|
+
* // Post-only side effects: tap the success/failure channels.
|
|
1457
|
+
* const log: RpcInterceptor = (next, ctx) =>
|
|
1458
|
+
* next.pipe(
|
|
1459
|
+
* Effect.tap((result) => recordAudit(ctx.tag, ctx.input, result, ctx.subject)),
|
|
1460
|
+
* Effect.tapError((err) => recordAuditError(ctx.tag, err)),
|
|
1461
|
+
* )
|
|
1462
|
+
*
|
|
1463
|
+
* // Tracing: wrap with a span; OTel spans nest automatically.
|
|
1464
|
+
* const trace: RpcInterceptor = (next, ctx) =>
|
|
1465
|
+
* next.pipe(Effect.withSpan(`plugin.audit.${ctx.kind}`, { attributes: { tag: ctx.tag } }))
|
|
1466
|
+
*
|
|
1467
|
+
* The `next` Effect MUST be flowed through somehow — either yielded,
|
|
1468
|
+
* piped, or returned — for the executor to run. Returning a different
|
|
1469
|
+
* Effect substitutes the result entirely (rare, e.g. cache hits).
|
|
1470
|
+
*/
|
|
1471
|
+
export declare type RpcInterceptor = (next: Effect.Effect<unknown, unknown>, context: RpcInterceptorContext) => Effect.Effect<unknown, unknown>;
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* Per-call context passed to every rpc interceptor. The fields are
|
|
1475
|
+
* deliberately shallow — plugins shouldn't need the full AppContext +
|
|
1476
|
+
* DataStore to do their job. For runtime services (DataStore, custom
|
|
1477
|
+
* Tags, etc.) declare them via `definePlugin({ services })` and read
|
|
1478
|
+
* via `yield* MyTag` in the interceptor body; the framework provides
|
|
1479
|
+
* them automatically.
|
|
1480
|
+
*/
|
|
1481
|
+
export declare interface RpcInterceptorContext {
|
|
1482
|
+
/** Procedure tag, e.g. `todos.create`. */
|
|
1483
|
+
readonly tag: string;
|
|
1484
|
+
/** Discriminator — `'mutation' | 'query' | 'action'`. */
|
|
1485
|
+
readonly kind: RpcKind;
|
|
1486
|
+
/** The validated input the executor is about to (or already did) receive. */
|
|
1487
|
+
readonly input: unknown;
|
|
1488
|
+
/** The resolved subject (user / apiKey / serviceAccount / anonymous). */
|
|
1489
|
+
readonly subject: Subject;
|
|
1490
|
+
/** Per-call trace id (matches OTel spans + inspect events). */
|
|
1491
|
+
readonly traceId: string;
|
|
1492
|
+
/** Active OTel span id (16-hex), when tracing is on. Lets observability
|
|
1493
|
+
* plugins (Sentry/Datadog) attach an error to the exact span, not just
|
|
1494
|
+
* the trace. Absent when no tracer is installed. */
|
|
1495
|
+
readonly spanId?: string;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* Which kind of rpc the interceptor is wrapping. The runtime passes
|
|
1500
|
+
* `kind: 'mutation'` to interceptors registered as `interceptMutation`,
|
|
1501
|
+
* `kind: 'query'` to `interceptQuery`, and `kind: 'action'` to
|
|
1502
|
+
* `interceptAction` — one plugin can implement any combination.
|
|
1503
|
+
*/
|
|
1504
|
+
export declare type RpcKind = 'mutation' | 'query' | 'action';
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* Per-firing context passed to schedule-fire interceptors. Shape
|
|
1508
|
+
* parallels `RpcInterceptorContext` so plugin authors can lift the
|
|
1509
|
+
* same patterns across rpc + schedule surfaces.
|
|
1510
|
+
*/
|
|
1511
|
+
export declare interface ScheduleFireContext {
|
|
1512
|
+
/** Schedule name (e.g. `'nightlyBilling'`). */
|
|
1513
|
+
readonly name: string;
|
|
1514
|
+
/** When the firing was scheduled by the cron expression. */
|
|
1515
|
+
readonly scheduledAt: Date;
|
|
1516
|
+
/** When the firing actually started. */
|
|
1517
|
+
readonly firedAt: Date;
|
|
1518
|
+
/** Run id recorded in `_voltro_schedule_runs` (`'unrecorded'` if
|
|
1519
|
+
* the run-row insert failed). */
|
|
1520
|
+
readonly runId: string;
|
|
1521
|
+
/** What triggered this firing. `'self'`/`'external'`/`'manual'`. */
|
|
1522
|
+
readonly trigger: 'self' | 'external' | 'manual';
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
/**
|
|
1526
|
+
* Wraps a single schedule firing. Receives `next` as a thunk so the
|
|
1527
|
+
* interceptor can choose to skip the firing entirely (don't call
|
|
1528
|
+
* `next()`), wrap it with side effects, or substitute behaviour.
|
|
1529
|
+
* Throwing/rejecting propagates as a handler failure recorded in
|
|
1530
|
+
* `_voltro_schedule_runs.status = 'failed'`. The watchdog
|
|
1531
|
+
* (`maxRuntimeMs`) wraps the WHOLE chain — exceeding it aborts.
|
|
1532
|
+
*
|
|
1533
|
+
* Use for: per-schedule observability (custom metrics/traces beyond
|
|
1534
|
+
* the framework's defaults), durable-lock extension, feature-flag-
|
|
1535
|
+
* gated suppression, suspend/resume integration with external
|
|
1536
|
+
* orchestrators.
|
|
1537
|
+
*
|
|
1538
|
+
* Typical pattern:
|
|
1539
|
+
*
|
|
1540
|
+
* onScheduleFire: async (next, ctx) => {
|
|
1541
|
+
* if (await isFeatureKilled(ctx.name)) return // suppress
|
|
1542
|
+
* const start = Date.now()
|
|
1543
|
+
* try {
|
|
1544
|
+
* await next()
|
|
1545
|
+
* } finally {
|
|
1546
|
+
* recordCustomMetric({ name: ctx.name, durationMs: Date.now() - start })
|
|
1547
|
+
* }
|
|
1548
|
+
* }
|
|
1549
|
+
*/
|
|
1550
|
+
export declare type ScheduleFireInterceptor = (next: () => Promise<void>, ctx: ScheduleFireContext) => Promise<void>;
|
|
1551
|
+
|
|
1552
|
+
export declare class ScopeError extends ScopeError_base {
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
declare const ScopeError_base: Schema.TaggedErrorClass<ScopeError, "ScopeError", {
|
|
1556
|
+
readonly _tag: Schema.tag<"ScopeError">;
|
|
1557
|
+
} & {
|
|
1558
|
+
required: typeof Schema.String;
|
|
1559
|
+
message: typeof Schema.String;
|
|
1560
|
+
}>;
|
|
1561
|
+
|
|
1562
|
+
export declare interface ServerErrorEvent {
|
|
1563
|
+
/** The thrown value (Error | tagged error | anything). */
|
|
1564
|
+
readonly error: unknown;
|
|
1565
|
+
/** Which primitive caught it. */
|
|
1566
|
+
readonly source: ServerErrorSource;
|
|
1567
|
+
/** Human identifier: route path / aggregate name / schedule name /
|
|
1568
|
+
* workflow name / webhook id / startup id / subscriber table. */
|
|
1569
|
+
readonly name?: string;
|
|
1570
|
+
/** Distributed-trace id when one is in scope (workflows carry it). */
|
|
1571
|
+
readonly traceId?: string;
|
|
1572
|
+
/** Extra context — op, runId, executionId, http status, etc. */
|
|
1573
|
+
readonly fields?: Record<string, unknown>;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
declare type ServerErrorListener = (event: ServerErrorEvent) => void;
|
|
1577
|
+
|
|
1578
|
+
export declare type ServerErrorSource = 'rest' | 'aggregate' | 'subscriber' | 'reaction' | 'schedule' | 'workflow' | 'webhook' | 'startup';
|
|
1579
|
+
|
|
1580
|
+
/** A strategy's verdict on a request.
|
|
1581
|
+
* - `matched`: this strategy claims the request; here's the Subject.
|
|
1582
|
+
* - `skip`: not my request (e.g. cookie absent); try next strategy.
|
|
1583
|
+
* - `failed`: this IS my request BUT validation failed (signature
|
|
1584
|
+
* mismatch, expired JWT). Composer bails to anonymous + logs;
|
|
1585
|
+
* silently falling through would mask attacks. */
|
|
1586
|
+
export declare type StrategyResolution = {
|
|
1587
|
+
readonly kind: 'matched';
|
|
1588
|
+
readonly subject: Subject;
|
|
1589
|
+
} | {
|
|
1590
|
+
readonly kind: 'skip';
|
|
1591
|
+
} | {
|
|
1592
|
+
readonly kind: 'failed';
|
|
1593
|
+
readonly reason: string;
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
/**
|
|
1597
|
+
* A non-reactive server→client stream — the load-bearing distinction from
|
|
1598
|
+
* a query: a query streams a reactive `SubscriptionEvent` (snapshot+delta)
|
|
1599
|
+
* envelope over a table; a stream emits PLAIN `element`s (e.g. an agent
|
|
1600
|
+
* run's `AgentEvent`s), one-shot, no table, no snapshot. Backs A3's
|
|
1601
|
+
* agent runtime + any other server-push stream.
|
|
1602
|
+
*/
|
|
1603
|
+
export declare interface StreamProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Element extends Schema.Schema.Any, Error extends Schema.Schema.All> {
|
|
1604
|
+
readonly kind: 'stream';
|
|
1605
|
+
readonly name: Name;
|
|
1606
|
+
readonly input: Input;
|
|
1607
|
+
readonly element: Element;
|
|
1608
|
+
readonly error: Error;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
export declare const streamToRpc: <Name extends string, Input extends Schema.Schema.Any, Element extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: StreamProcedureDescriptor<Name, Input, Element, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Element, Schema.Schema.All>, typeof Schema.Never, never>;
|
|
1612
|
+
|
|
1613
|
+
export declare const Subject: Schema.Union<[Schema.Struct<{
|
|
1614
|
+
type: Schema.Literal<["user"]>;
|
|
1615
|
+
id: typeof Schema.String;
|
|
1616
|
+
tenantId: typeof Schema.String;
|
|
1617
|
+
scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
1618
|
+
metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
|
|
1619
|
+
}>, Schema.Struct<{
|
|
1620
|
+
type: Schema.Literal<["apiKey"]>;
|
|
1621
|
+
id: typeof Schema.String;
|
|
1622
|
+
tenantId: typeof Schema.String;
|
|
1623
|
+
scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
1624
|
+
metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
|
|
1625
|
+
}>, Schema.Struct<{
|
|
1626
|
+
type: Schema.Literal<["serviceAccount"]>;
|
|
1627
|
+
id: typeof Schema.String;
|
|
1628
|
+
tenantId: typeof Schema.String;
|
|
1629
|
+
scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
1630
|
+
metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
|
|
1631
|
+
}>, Schema.Struct<{
|
|
1632
|
+
type: Schema.Literal<["anonymous"]>;
|
|
1633
|
+
id: typeof Schema.Null;
|
|
1634
|
+
tenantId: Schema.NullOr<typeof Schema.String>;
|
|
1635
|
+
}>, Schema.Struct<{
|
|
1636
|
+
type: Schema.Literal<["system"]>;
|
|
1637
|
+
id: typeof Schema.String;
|
|
1638
|
+
tenantId: typeof Schema.Null;
|
|
1639
|
+
scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
1640
|
+
metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
|
|
1641
|
+
}>]>;
|
|
1642
|
+
|
|
1643
|
+
export declare type Subject = typeof Subject.Type;
|
|
1644
|
+
|
|
1645
|
+
/** The subject's scopes (empty for anonymous / unscoped). */
|
|
1646
|
+
export declare const subjectScopes: (subject: Subject) => ReadonlyArray<string>;
|
|
1647
|
+
|
|
1648
|
+
export declare class SubjectService extends SubjectService_base {
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
declare const SubjectService_base: Context.TagClass<SubjectService, "@voltro/Subject", {
|
|
1652
|
+
readonly id: string;
|
|
1653
|
+
readonly type: "user";
|
|
1654
|
+
readonly tenantId: string;
|
|
1655
|
+
readonly scopes?: readonly string[] | undefined;
|
|
1656
|
+
readonly metadata?: {
|
|
1657
|
+
readonly [x: string]: unknown;
|
|
1658
|
+
} | undefined;
|
|
1659
|
+
} | {
|
|
1660
|
+
readonly id: string;
|
|
1661
|
+
readonly type: "apiKey";
|
|
1662
|
+
readonly tenantId: string;
|
|
1663
|
+
readonly scopes?: readonly string[] | undefined;
|
|
1664
|
+
readonly metadata?: {
|
|
1665
|
+
readonly [x: string]: unknown;
|
|
1666
|
+
} | undefined;
|
|
1667
|
+
} | {
|
|
1668
|
+
readonly id: string;
|
|
1669
|
+
readonly type: "serviceAccount";
|
|
1670
|
+
readonly tenantId: string;
|
|
1671
|
+
readonly scopes?: readonly string[] | undefined;
|
|
1672
|
+
readonly metadata?: {
|
|
1673
|
+
readonly [x: string]: unknown;
|
|
1674
|
+
} | undefined;
|
|
1675
|
+
} | {
|
|
1676
|
+
readonly id: null;
|
|
1677
|
+
readonly type: "anonymous";
|
|
1678
|
+
readonly tenantId: string | null;
|
|
1679
|
+
} | {
|
|
1680
|
+
readonly id: string;
|
|
1681
|
+
readonly type: "system";
|
|
1682
|
+
readonly tenantId: null;
|
|
1683
|
+
readonly scopes?: readonly string[] | undefined;
|
|
1684
|
+
readonly metadata?: {
|
|
1685
|
+
readonly [x: string]: unknown;
|
|
1686
|
+
} | undefined;
|
|
1687
|
+
}>;
|
|
1688
|
+
|
|
1689
|
+
/** Subscribe to server-primitive errors. Returns an unsubscribe fn. */
|
|
1690
|
+
export declare const subscribeServerErrors: (listener: ServerErrorListener) => (() => void);
|
|
1691
|
+
|
|
1692
|
+
export declare type SubscriptionEvent<T> = {
|
|
1693
|
+
readonly _tag: 'snapshot';
|
|
1694
|
+
readonly revision: number;
|
|
1695
|
+
readonly data: T;
|
|
1696
|
+
readonly computed?: boolean;
|
|
1697
|
+
} | {
|
|
1698
|
+
readonly _tag: 'delta';
|
|
1699
|
+
readonly revision: number;
|
|
1700
|
+
readonly emittedAt: number;
|
|
1701
|
+
readonly patch: RowPatch;
|
|
1702
|
+
} | {
|
|
1703
|
+
readonly _tag: 'error';
|
|
1704
|
+
readonly error: unknown;
|
|
1705
|
+
readonly revision?: number;
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
export declare const subscriptionEvent: <D extends Schema.Schema.Any>(data: D) => Schema.Union<[Schema.Struct<{
|
|
1709
|
+
_tag: Schema.Literal<["snapshot"]>;
|
|
1710
|
+
revision: typeof Schema.Number;
|
|
1711
|
+
data: D;
|
|
1712
|
+
computed: Schema.optional<typeof Schema.Boolean>;
|
|
1713
|
+
}>, Schema.Struct<{
|
|
1714
|
+
_tag: Schema.Literal<["delta"]>;
|
|
1715
|
+
revision: typeof Schema.Number;
|
|
1716
|
+
emittedAt: typeof Schema.Number;
|
|
1717
|
+
patch: Schema.Struct<{
|
|
1718
|
+
ops: Schema.Array$<Schema.Union<[Schema.Struct<{
|
|
1719
|
+
op: Schema.Literal<["add"]>;
|
|
1720
|
+
path: typeof Schema.String;
|
|
1721
|
+
value: Schema.refine<{
|
|
1722
|
+
readonly [x: string]: unknown;
|
|
1723
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1724
|
+
readonly id: RowId;
|
|
1725
|
+
}, Schema.Schema<{
|
|
1726
|
+
readonly [x: string]: unknown;
|
|
1727
|
+
}, {
|
|
1728
|
+
readonly [x: string]: unknown;
|
|
1729
|
+
}, never>>;
|
|
1730
|
+
}>, Schema.Struct<{
|
|
1731
|
+
op: Schema.Literal<["replace"]>;
|
|
1732
|
+
path: typeof Schema.String;
|
|
1733
|
+
value: Schema.refine<{
|
|
1734
|
+
readonly [x: string]: unknown;
|
|
1735
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1736
|
+
readonly id: RowId;
|
|
1737
|
+
}, Schema.Schema<{
|
|
1738
|
+
readonly [x: string]: unknown;
|
|
1739
|
+
}, {
|
|
1740
|
+
readonly [x: string]: unknown;
|
|
1741
|
+
}, never>>;
|
|
1742
|
+
}>, Schema.Struct<{
|
|
1743
|
+
op: Schema.Literal<["remove"]>;
|
|
1744
|
+
path: typeof Schema.String;
|
|
1745
|
+
}>]>>;
|
|
1746
|
+
order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
|
|
1747
|
+
}>;
|
|
1748
|
+
}>, Schema.Struct<{
|
|
1749
|
+
_tag: Schema.Literal<["error"]>;
|
|
1750
|
+
error: typeof Schema.Unknown;
|
|
1751
|
+
revision: Schema.optional<typeof Schema.Number>;
|
|
1752
|
+
}>]>;
|
|
1753
|
+
|
|
1754
|
+
export declare const systemSubject: (id: string, scopes?: ReadonlyArray<string>) => Subject;
|
|
1755
|
+
|
|
1756
|
+
export declare type Target<Input = unknown, Row = unknown> = TargetSpec<Input, Row> | ReadonlyArray<TargetSpec<Input, Row>>;
|
|
1757
|
+
|
|
1758
|
+
export declare type TargetSpec<Input = unknown, Row = unknown> = InsertTarget<Input, Row> | UpdateTarget<Input, Row> | DeleteTarget<Input>;
|
|
1759
|
+
|
|
1760
|
+
export declare const toRpc: <Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: QueryProcedureDescriptor<Name, Input, Output, Err> | MutationProcedureDescriptor<Name, Input, Output, Err> | ActionProcedureDescriptor<Name, Input, Output, Err> | StreamProcedureDescriptor<Name, Input, Output, Err>) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Schema.Union<[Schema.Struct<{
|
|
1761
|
+
_tag: Schema.Literal<["snapshot"]>;
|
|
1762
|
+
revision: typeof Schema.Number;
|
|
1763
|
+
data: Output;
|
|
1764
|
+
computed: Schema.optional<typeof Schema.Boolean>;
|
|
1765
|
+
}>, Schema.Struct<{
|
|
1766
|
+
_tag: Schema.Literal<["delta"]>;
|
|
1767
|
+
revision: typeof Schema.Number;
|
|
1768
|
+
emittedAt: typeof Schema.Number;
|
|
1769
|
+
patch: Schema.Struct<{
|
|
1770
|
+
ops: Schema.Array$<Schema.Union<[Schema.Struct<{
|
|
1771
|
+
op: Schema.Literal<["add"]>;
|
|
1772
|
+
path: typeof Schema.String;
|
|
1773
|
+
value: Schema.refine<{
|
|
1774
|
+
readonly [x: string]: unknown;
|
|
1775
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1776
|
+
readonly id: RowId;
|
|
1777
|
+
}, Schema.Schema<{
|
|
1778
|
+
readonly [x: string]: unknown;
|
|
1779
|
+
}, {
|
|
1780
|
+
readonly [x: string]: unknown;
|
|
1781
|
+
}, never>>;
|
|
1782
|
+
}>, Schema.Struct<{
|
|
1783
|
+
op: Schema.Literal<["replace"]>;
|
|
1784
|
+
path: typeof Schema.String;
|
|
1785
|
+
value: Schema.refine<{
|
|
1786
|
+
readonly [x: string]: unknown;
|
|
1787
|
+
} & Readonly<Record<string, unknown>> & {
|
|
1788
|
+
readonly id: RowId;
|
|
1789
|
+
}, Schema.Schema<{
|
|
1790
|
+
readonly [x: string]: unknown;
|
|
1791
|
+
}, {
|
|
1792
|
+
readonly [x: string]: unknown;
|
|
1793
|
+
}, never>>;
|
|
1794
|
+
}>, Schema.Struct<{
|
|
1795
|
+
op: Schema.Literal<["remove"]>;
|
|
1796
|
+
path: typeof Schema.String;
|
|
1797
|
+
}>]>>;
|
|
1798
|
+
order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
|
|
1799
|
+
}>;
|
|
1800
|
+
}>, Schema.Struct<{
|
|
1801
|
+
_tag: Schema.Literal<["error"]>;
|
|
1802
|
+
error: typeof Schema.Unknown;
|
|
1803
|
+
revision: Schema.optional<typeof Schema.Number>;
|
|
1804
|
+
}>]>, Schema.Schema.All>, typeof Schema.Never, never> | Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Output, Schema.Schema.All, never> | Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Output, Schema.Schema.All>, typeof Schema.Never, never>;
|
|
1805
|
+
|
|
1806
|
+
/**
|
|
1807
|
+
* Coerce a timestamp read (`Date` / epoch-ms number / ISO string) to
|
|
1808
|
+
* epoch milliseconds. Unrecognised inputs collapse to `0`.
|
|
1809
|
+
*/
|
|
1810
|
+
export declare const tsMs: (v: unknown) => number;
|
|
1811
|
+
|
|
1812
|
+
/**
|
|
1813
|
+
* Typed error every handler can throw to signal "you must be signed
|
|
1814
|
+
* in to perform this action". The framework's @effect/rpc layer
|
|
1815
|
+
* preserves the `_tag` across the wire, and `@voltro/client`'s error
|
|
1816
|
+
* bus surfaces it to the consuming app so the dashboard can
|
|
1817
|
+
* auto-redirect to the sign-in page (cleared session + UI swap).
|
|
1818
|
+
*
|
|
1819
|
+
* Distinct from TenantMismatch (which means "you ARE signed in but
|
|
1820
|
+
* touching the wrong tenant"). Unauthenticated means "no real subject
|
|
1821
|
+
* resolved at all" — anonymous, expired token, missing cookie, etc.
|
|
1822
|
+
*/
|
|
1823
|
+
export declare class Unauthenticated extends Unauthenticated_base {
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
declare const Unauthenticated_base: Schema.TaggedErrorClass<Unauthenticated, "Unauthenticated", {
|
|
1827
|
+
readonly _tag: Schema.tag<"Unauthenticated">;
|
|
1828
|
+
} & {
|
|
1829
|
+
/** Optional context — e.g. "auth.signin required", "session expired". */
|
|
1830
|
+
reason: Schema.optional<typeof Schema.String>;
|
|
1831
|
+
}>;
|
|
1832
|
+
|
|
1833
|
+
export declare const UNDO_APPLY_TAG: "__voltro.undo.apply";
|
|
1834
|
+
|
|
1835
|
+
export declare const UNDO_LOG_TAG: "__voltro.undo.log";
|
|
1836
|
+
|
|
1837
|
+
export declare const UNDO_REDO_TAG: "__voltro.undo.redo";
|
|
1838
|
+
|
|
1839
|
+
/** `__voltro.undo.apply` — undo one invocation (synthesize + apply its inverse).
|
|
1840
|
+
* No `target` (it writes whatever tables the change-set touched — its effects
|
|
1841
|
+
* reach the client via those tables' own reactive deltas, not optimistic). */
|
|
1842
|
+
export declare const undoApplyDescriptor: MutationProcedureDescriptor<"__voltro.undo.apply", Schema.Struct<{
|
|
1843
|
+
invocationId: typeof Schema.String;
|
|
1844
|
+
}>, Schema.Struct<{
|
|
1845
|
+
ok: typeof Schema.Boolean;
|
|
1846
|
+
}>, Schema.Union<[typeof UndoNotFound, typeof UndoForbidden, typeof UndoConflict]>>;
|
|
1847
|
+
|
|
1848
|
+
/** The row changed since the action (a concurrent writer), OR the action
|
|
1849
|
+
* crossed an external side effect — a blind restore would clobber/lie, so
|
|
1850
|
+
* undo refuses. `reason` distinguishes the two (mirrors the engine's
|
|
1851
|
+
* UndoBoundaryError boundary). */
|
|
1852
|
+
export declare class UndoConflict extends UndoConflict_base {
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
declare const UndoConflict_base: Schema.TaggedErrorClass<UndoConflict, "UndoConflict", {
|
|
1856
|
+
readonly _tag: Schema.tag<"UndoConflict">;
|
|
1857
|
+
} & {
|
|
1858
|
+
invocationId: typeof Schema.String;
|
|
1859
|
+
reason: Schema.Literal<["conflict", "action"]>;
|
|
1860
|
+
}>;
|
|
1861
|
+
|
|
1862
|
+
/** The action belongs to a different subject — undo is per-actor. */
|
|
1863
|
+
export declare class UndoForbidden extends UndoForbidden_base {
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
declare const UndoForbidden_base: Schema.TaggedErrorClass<UndoForbidden, "UndoForbidden", {
|
|
1867
|
+
readonly _tag: Schema.tag<"UndoForbidden">;
|
|
1868
|
+
} & {
|
|
1869
|
+
invocationId: typeof Schema.String;
|
|
1870
|
+
}>;
|
|
1871
|
+
|
|
1872
|
+
/** One undoable action in the calling subject's recent history. The heavy
|
|
1873
|
+
* `changes` payload is server-only — the client list needs only this. */
|
|
1874
|
+
export declare const UndoLogEntry: Schema.Struct<{
|
|
1875
|
+
/** The invocation id (= the `_voltro_undo_log` row id) to pass to apply/redo. */
|
|
1876
|
+
id: typeof Schema.String;
|
|
1877
|
+
/** Rpc tag of the captured mutation (e.g. `todos.create`). */
|
|
1878
|
+
tag: typeof Schema.String;
|
|
1879
|
+
/** Human label ("create todo") — falls back to the tag. */
|
|
1880
|
+
label: Schema.NullOr<typeof Schema.String>;
|
|
1881
|
+
/** True once undone (so the UI shows it as redo-able). */
|
|
1882
|
+
undone: typeof Schema.Boolean;
|
|
1883
|
+
/** True when the action crossed an external side effect → not undoable. */
|
|
1884
|
+
crossesAction: typeof Schema.Boolean;
|
|
1885
|
+
/** Display-only creation time (stringified). */
|
|
1886
|
+
createdAt: typeof Schema.String;
|
|
1887
|
+
}>;
|
|
1888
|
+
|
|
1889
|
+
export declare type UndoLogEntry = Schema.Schema.Type<typeof UndoLogEntry>;
|
|
1890
|
+
|
|
1891
|
+
/** `__voltro.undo.log` — the calling subject's recent undoable actions, newest
|
|
1892
|
+
* first. Reactive (source `_voltro_undo_log`) so the list updates live as the
|
|
1893
|
+
* subject makes + undoes changes. */
|
|
1894
|
+
export declare const undoLogQueryDescriptor: QueryProcedureDescriptor<"__voltro.undo.log", Schema.Struct<{
|
|
1895
|
+
limit: Schema.optional<typeof Schema.Number>;
|
|
1896
|
+
}>, Schema.Array$<Schema.Struct<{
|
|
1897
|
+
/** The invocation id (= the `_voltro_undo_log` row id) to pass to apply/redo. */
|
|
1898
|
+
id: typeof Schema.String;
|
|
1899
|
+
/** Rpc tag of the captured mutation (e.g. `todos.create`). */
|
|
1900
|
+
tag: typeof Schema.String;
|
|
1901
|
+
/** Human label ("create todo") — falls back to the tag. */
|
|
1902
|
+
label: Schema.NullOr<typeof Schema.String>;
|
|
1903
|
+
/** True once undone (so the UI shows it as redo-able). */
|
|
1904
|
+
undone: typeof Schema.Boolean;
|
|
1905
|
+
/** True when the action crossed an external side effect → not undoable. */
|
|
1906
|
+
crossesAction: typeof Schema.Boolean;
|
|
1907
|
+
/** Display-only creation time (stringified). */
|
|
1908
|
+
createdAt: typeof Schema.String;
|
|
1909
|
+
}>>, typeof Schema.Never>;
|
|
1910
|
+
|
|
1911
|
+
/** No undo-log row for that invocation (already pruned, or wrong id). */
|
|
1912
|
+
export declare class UndoNotFound extends UndoNotFound_base {
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
declare const UndoNotFound_base: Schema.TaggedErrorClass<UndoNotFound, "UndoNotFound", {
|
|
1916
|
+
readonly _tag: Schema.tag<"UndoNotFound">;
|
|
1917
|
+
} & {
|
|
1918
|
+
invocationId: typeof Schema.String;
|
|
1919
|
+
}>;
|
|
1920
|
+
|
|
1921
|
+
/** `__voltro.undo.redo` — re-apply a previously-undone invocation's forward changes. */
|
|
1922
|
+
export declare const undoRedoDescriptor: MutationProcedureDescriptor<"__voltro.undo.redo", Schema.Struct<{
|
|
1923
|
+
invocationId: typeof Schema.String;
|
|
1924
|
+
}>, Schema.Struct<{
|
|
1925
|
+
ok: typeof Schema.Boolean;
|
|
1926
|
+
}>, Schema.Union<[typeof UndoNotFound, typeof UndoForbidden, typeof UndoConflict]>>;
|
|
1927
|
+
|
|
1928
|
+
export declare interface UpdateTarget<Input = unknown, Row = unknown> {
|
|
1929
|
+
readonly table: string;
|
|
1930
|
+
readonly op: 'update';
|
|
1931
|
+
/** Identify the row to patch. Default: `input.id`. */
|
|
1932
|
+
readonly identify?: ((input: Input) => string) | undefined;
|
|
1933
|
+
/** Build the patch. Default: merges input over current. */
|
|
1934
|
+
readonly shape?: ((input: Input, current: Row) => Row) | undefined;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
export declare interface VoltroPlugin {
|
|
1938
|
+
/**
|
|
1939
|
+
* Plugin identifier — printed in `voltro dev` boot logs + surfaced in
|
|
1940
|
+
* the inspect manifest. Use a stable string scoped to the package
|
|
1941
|
+
* (e.g. `@voltro/audit`, `acme.invoicing`). Required so multiple
|
|
1942
|
+
* instances of the same package can be disambiguated by suffix
|
|
1943
|
+
* (`@voltro/audit#analytics`).
|
|
1944
|
+
*/
|
|
1945
|
+
readonly name: string;
|
|
1946
|
+
/**
|
|
1947
|
+
* Plugin version — the package's own semver. Distinct from
|
|
1948
|
+
* `framework` (the FRAMEWORK range the plugin works with). Used by
|
|
1949
|
+
* the manifest endpoint + future signing + install/uninstall
|
|
1950
|
+
* idempotency. Optional today; recommended.
|
|
1951
|
+
*/
|
|
1952
|
+
readonly version?: string;
|
|
1953
|
+
/** Optional human-readable description; surfaced by the dashboard. */
|
|
1954
|
+
readonly description?: string;
|
|
1955
|
+
/**
|
|
1956
|
+
* Compatible framework version range, in npm-semver syntax
|
|
1957
|
+
* (`'^1.0.0'`, `'>=2.3.0 <3'`). The runtime checks the running
|
|
1958
|
+
* framework against this at boot — incompatible plugins log a
|
|
1959
|
+
* boot-time warning naming the constraint + the running version,
|
|
1960
|
+
* but DON'T abort boot. App author makes the call; surfacing the
|
|
1961
|
+
* mismatch is the framework's job.
|
|
1962
|
+
*
|
|
1963
|
+
* Absent → no compat check (plugin opts out of versioning). Suitable
|
|
1964
|
+
* for in-tree plugins that ship lockstep with the framework.
|
|
1965
|
+
*/
|
|
1966
|
+
readonly framework?: string;
|
|
1967
|
+
/**
|
|
1968
|
+
* Declared permission scopes. v1 advisory; future slice enforces.
|
|
1969
|
+
* Surfaced in the boot log + `/_voltro/inspect/plugins` endpoint
|
|
1970
|
+
* so operators audit what each plugin is asking for before granting.
|
|
1971
|
+
*/
|
|
1972
|
+
readonly permissions?: ReadonlyArray<PluginPermission>;
|
|
1973
|
+
/**
|
|
1974
|
+
* Environment variables this plugin reads (declaration only — metadata, not
|
|
1975
|
+
* a read path). The plugin still reads its values directly (`options.X ??
|
|
1976
|
+
* process.env.X`); declaring them here surfaces the plugin's env needs in the
|
|
1977
|
+
* manifest (`/_voltro/inspect/env`), the generated `.env.example`, and the
|
|
1978
|
+
* dashboard Env panel. Mirror exactly what the plugin reads.
|
|
1979
|
+
*/
|
|
1980
|
+
readonly declaredEnv?: ReadonlyArray<PluginEnvVar>;
|
|
1981
|
+
/**
|
|
1982
|
+
* Optional `effect/Schema` describing the plugin's user-supplied
|
|
1983
|
+
* config. When present the framework decodes the operator's config
|
|
1984
|
+
* payload against this schema at boot — invalid config aborts boot
|
|
1985
|
+
* with a typed error message. The decoded value lands in
|
|
1986
|
+
* `PluginLifecycleContext.config` so lifecycle hooks consume the
|
|
1987
|
+
* already-validated shape.
|
|
1988
|
+
*
|
|
1989
|
+
* For plugins that take options via a factory function (the
|
|
1990
|
+
* idiomatic TS pattern), `configSchema` is optional — the factory
|
|
1991
|
+
* already validates at the type level. Declare `configSchema` when
|
|
1992
|
+
* the plugin will surface a config form in the dashboard, or when
|
|
1993
|
+
* the plugin reads dynamic config from env / a config file.
|
|
1994
|
+
*/
|
|
1995
|
+
readonly configSchema?: Schema.Schema.Any;
|
|
1996
|
+
/**
|
|
1997
|
+
* Wraps every MUTATION handler call. Composed in declaration order
|
|
1998
|
+
* across the plugin list: outermost = first listed in app.config.ts.
|
|
1999
|
+
* Absent → the plugin contributes nothing at the mutation boundary
|
|
2000
|
+
* (schema-only / lifecycle-only plugins are fine).
|
|
2001
|
+
*/
|
|
2002
|
+
readonly interceptMutation?: RpcInterceptor;
|
|
2003
|
+
/**
|
|
2004
|
+
* Wraps every QUERY (streaming subscription handler) call. Same
|
|
2005
|
+
* composition semantics as `interceptMutation`. Most observability
|
|
2006
|
+
* plugins want to install this AND `interceptMutation` so the audit
|
|
2007
|
+
* trail covers both read + write.
|
|
2008
|
+
*/
|
|
2009
|
+
readonly interceptQuery?: RpcInterceptor;
|
|
2010
|
+
/**
|
|
2011
|
+
* Wraps every ACTION (unary non-transactional handler) call. Same
|
|
2012
|
+
* composition semantics as `interceptMutation`. Useful for plugins
|
|
2013
|
+
* that need to govern outbound HTTP / file IO calls (rate limiting,
|
|
2014
|
+
* tenant-scoped IO quotas).
|
|
2015
|
+
*/
|
|
2016
|
+
readonly interceptAction?: RpcInterceptor;
|
|
2017
|
+
/**
|
|
2018
|
+
* Effect Layer that contributes services into the per-request
|
|
2019
|
+
* context. Anything declared here is yieldable from any handler in
|
|
2020
|
+
* the host app (`const myTag = yield* MyTag`). Use this when a
|
|
2021
|
+
* plugin needs to expose APIs to handlers without forcing every
|
|
2022
|
+
* handler to import the plugin directly — handlers depend on the
|
|
2023
|
+
* Tag, the plugin owns the Live implementation.
|
|
2024
|
+
*
|
|
2025
|
+
* The layer is provided ONCE at boot — it must be self-contained
|
|
2026
|
+
* (`RIn = never`). For per-call data, expose a `Service` whose
|
|
2027
|
+
* methods return Effects that consume per-call inputs.
|
|
2028
|
+
*/
|
|
2029
|
+
readonly services?: Layer.Layer<never, never, never>;
|
|
2030
|
+
/**
|
|
2031
|
+
* RPC routes the plugin contributes. Registered alongside the
|
|
2032
|
+
* user-authored routes — same wire protocol, same dashboard
|
|
2033
|
+
* surface. The plugin's name is prepended to each route's `name`
|
|
2034
|
+
* field unless the name already carries a dot.
|
|
2035
|
+
*
|
|
2036
|
+
* Use for plugin-shaped endpoints the user app shouldn't have to
|
|
2037
|
+
* re-implement: webhook ingestion targets, OAuth callbacks, plugin
|
|
2038
|
+
* admin queries, etc.
|
|
2039
|
+
*/
|
|
2040
|
+
readonly routes?: ReadonlyArray<PluginRpcRoute>;
|
|
2041
|
+
/**
|
|
2042
|
+
* Plugin-contributed inspect endpoints. Mounted under
|
|
2043
|
+
* `/_voltro/inspect/plugins/<plugin-slug>/<path>` — see
|
|
2044
|
+
* `PluginInspectEndpoint`. Use for plugin-specific tooling that
|
|
2045
|
+
* doesn't fit the rpc wire (health probes, debug dumps,
|
|
2046
|
+
* on-demand stats exports).
|
|
2047
|
+
*/
|
|
2048
|
+
readonly inspectEndpoints?: ReadonlyArray<PluginInspectEndpoint>;
|
|
2049
|
+
/**
|
|
2050
|
+
* Public raw-HTTP routes the plugin serves directly (not rpc, not
|
|
2051
|
+
* inspect). Mounted by BOTH `voltro dev` and the production `serveApi`
|
|
2052
|
+
* on the same listener — so e.g. `@voltro/plugin-storage` can serve
|
|
2053
|
+
* `GET /_voltro/storage/:id` (302-presigned or streamed bytes) in dev
|
|
2054
|
+
* AND prod. The handler owns its own auth + returns a status, an
|
|
2055
|
+
* optional body (string or bytes), content-type, and headers (so it can
|
|
2056
|
+
* 302-redirect or set immutable cache headers).
|
|
2057
|
+
*/
|
|
2058
|
+
readonly httpRoutes?: ReadonlyArray<PluginHttpRoute>;
|
|
2059
|
+
/**
|
|
2060
|
+
* Called once by the serve pipeline (dev + serveApi) AFTER the app's
|
|
2061
|
+
* DataStore is built — earlier than this it doesn't exist (dev activates
|
|
2062
|
+
* plugins before the store). Lets a plugin bind a DB-backed resource it
|
|
2063
|
+
* couldn't construct from static config: e.g. `@voltro/plugin-storage`
|
|
2064
|
+
* swaps its in-memory ref store for `dataStoreRefStore(store)` so blob
|
|
2065
|
+
* metadata persists in `_voltro_storage_refs`.
|
|
2066
|
+
*
|
|
2067
|
+
* `store` is the framework `DataStore` — TYPED now (was `unknown`); a
|
|
2068
|
+
* `(s) => s as DataStore` implementation still compiles (DataStore →
|
|
2069
|
+
* DataStore is a no-op cast) so the tightening is additive. `ctx` is the
|
|
2070
|
+
* post-store bind context: the framework's already-open `SqlClient`
|
|
2071
|
+
* (`ctx.sql`) so a plugin reuses the app's pool instead of rebuilding one
|
|
2072
|
+
* from env, and `ctx.scheduleCoordinated(name, intervalMs, effect)` for
|
|
2073
|
+
* a cluster-coordinated sweep that runs on ONE replica per tick instead
|
|
2074
|
+
* of an un-coordinated per-replica `setInterval`.
|
|
2075
|
+
*
|
|
2076
|
+
* `ctx` is OPTIONAL in the type so the tightening stays additive: the
|
|
2077
|
+
* framework ALWAYS passes it, but existing single-arg call sites (a
|
|
2078
|
+
* plugin's `bindDataStore(store)` in a unit test) still compile. A plugin
|
|
2079
|
+
* that wants the enablers declares `(store, ctx)` and reads
|
|
2080
|
+
* `ctx?.sql` / `ctx?.scheduleCoordinated(...)`; one that ignores `ctx`
|
|
2081
|
+
* keeps working unchanged.
|
|
2082
|
+
*/
|
|
2083
|
+
readonly bindDataStore?: (store: DataStore, ctx?: PluginBindContext) => void;
|
|
2084
|
+
/**
|
|
2085
|
+
* Post-commit ChangeEvent tap — an `Effect<void, E>` the runtime SUPERVISES.
|
|
2086
|
+
* Fires for EVERY committed store change (insert/update/delete on any table),
|
|
2087
|
+
* AFTER the transaction commits and AFTER the framework's own subscribers.
|
|
2088
|
+
*
|
|
2089
|
+
* The runtime forks the returned Effect under the plugin's supervision scope
|
|
2090
|
+
* and routes its failure channel to the plugin-scoped logger — so a change-tap
|
|
2091
|
+
* gets a REAL typed error channel (`Effect.retry`, `Effect.timeout`,
|
|
2092
|
+
* `Effect.catchTag`, durable enqueue) instead of fire-and-forget
|
|
2093
|
+
* `.catch(warn)` glue. The fork keeps the tap non-blocking: a slow or failing
|
|
2094
|
+
* tap can't back-pressure or break the change stream, and a failure is logged
|
|
2095
|
+
* scoped to the plugin, never a defect that crosses into the stream.
|
|
2096
|
+
*
|
|
2097
|
+
* Still NOT durable at the framework layer — a crash between commit and the
|
|
2098
|
+
* fork loses the event; a durable consumer builds durability INSIDE the
|
|
2099
|
+
* Effect (a `store.insert` into an outbox the way `@voltro/plugin-cdc-out`
|
|
2100
|
+
* does, retried against the typed error channel). This is the seam
|
|
2101
|
+
* `@voltro/plugin-search` uses to mirror tables into an external index
|
|
2102
|
+
* without a user-authored `*.subscribe.ts`. Requires the
|
|
2103
|
+
* `store:changes:read` permission.
|
|
2104
|
+
*
|
|
2105
|
+
* Exactly-once / change-scope semantics are unchanged: read
|
|
2106
|
+
* `event.origin` + `event.changeScope` inside the Effect to act once per
|
|
2107
|
+
* change fleet-wide (skip `origin:'injected'` on `'local'` scope; elect one
|
|
2108
|
+
* worker on `'fleet'`).
|
|
2109
|
+
*/
|
|
2110
|
+
readonly onChangeEvent?: (event: PluginChangeEvent) => Effect.Effect<void, unknown>;
|
|
2111
|
+
/**
|
|
2112
|
+
* Wraps every schedule firing. Composed across plugins in
|
|
2113
|
+
* declaration order (first-in-array = outermost wrapper). Runs
|
|
2114
|
+
* INSIDE the framework's `maxRuntimeMs` watchdog — the watchdog
|
|
2115
|
+
* applies to the whole chain. See `ScheduleFireInterceptor`.
|
|
2116
|
+
*/
|
|
2117
|
+
readonly onScheduleFire?: ScheduleFireInterceptor;
|
|
2118
|
+
/**
|
|
2119
|
+
* Wraps every `step()` (== `Activity.make`) invocation inside a
|
|
2120
|
+
* workflow body. Composed across plugins. The interceptor sees
|
|
2121
|
+
* the user's `execute` Effect AS an Effect — it can `Effect.tap`,
|
|
2122
|
+
* `Effect.retry`, `Effect.withSpan`, etc. See
|
|
2123
|
+
* `WorkflowStepInterceptor`. Bypassed on workflow REPLAY (only
|
|
2124
|
+
* fires on the first execution per step).
|
|
2125
|
+
*/
|
|
2126
|
+
readonly onWorkflowStep?: WorkflowStepInterceptor;
|
|
2127
|
+
/**
|
|
2128
|
+
* Plugin-supplied codegen contribution. The framework calls this
|
|
2129
|
+
* during `voltro dev`'s codegen step and emits the returned string
|
|
2130
|
+
* into `rpcGroup.generated.ts` as a marked section. Use for typed
|
|
2131
|
+
* accessor bindings that wrap the plugin's runtime apis. See
|
|
2132
|
+
* `PluginCodegen`.
|
|
2133
|
+
*/
|
|
2134
|
+
readonly codegen?: PluginCodegen;
|
|
2135
|
+
/**
|
|
2136
|
+
* Templates this plugin contributes to `voltro init` / `voltro add-app`.
|
|
2137
|
+
* Each entry is a self-contained directory tree the CLI seeds into
|
|
2138
|
+
* a new project. The framework lists them under the plugin's name
|
|
2139
|
+
* in `voltro list-templates`.
|
|
2140
|
+
*/
|
|
2141
|
+
readonly templates?: ReadonlyArray<PluginTemplate>;
|
|
2142
|
+
/**
|
|
2143
|
+
* Pre-auth HTTP-pipeline interceptor. Fires at the very top of the
|
|
2144
|
+
* HTTP request handler, BEFORE auth resolution, BEFORE rpc routing,
|
|
2145
|
+
* BEFORE inspect. Use for rate-limit, geo-block, bot-detection,
|
|
2146
|
+
* outbound header injection. See `HttpRequestInterceptor`.
|
|
2147
|
+
*
|
|
2148
|
+
* Requires `'http:intercept'` permission — boot fails loudly if
|
|
2149
|
+
* the hook is declared without the matching permission.
|
|
2150
|
+
*/
|
|
2151
|
+
readonly onHttpRequest?: HttpRequestInterceptor;
|
|
2152
|
+
/**
|
|
2153
|
+
* Dashboard mount contributions — remote-loaded ESM modules the
|
|
2154
|
+
* dashboard host renders alongside framework pages. See
|
|
2155
|
+
* `PluginDashboardMount` for the trade-offs vs iframe.
|
|
2156
|
+
*
|
|
2157
|
+
* Requires `'dashboard:mount'` permission. Surfaces under
|
|
2158
|
+
* `/_voltro/inspect/plugins/dashboard-mounts`; the actual mount
|
|
2159
|
+
* runtime lives in voltro-cloud-dashboard + voltro-devtools.
|
|
2160
|
+
*/
|
|
2161
|
+
readonly dashboard?: ReadonlyArray<PluginDashboardMount>;
|
|
2162
|
+
/**
|
|
2163
|
+
* Schema + migration contribution. `tables` are merged with the
|
|
2164
|
+
* user's table set and run through the same idempotent
|
|
2165
|
+
* `applySchema()` path. `migrations` run after the schema apply
|
|
2166
|
+
* against the live SqlClient and are tracked in
|
|
2167
|
+
* `_voltro_plugin_migrations` so they execute exactly once per
|
|
2168
|
+
* app database.
|
|
2169
|
+
*
|
|
2170
|
+
* `tables` requires `'store:write'`; `migrations` requires
|
|
2171
|
+
* `'migration:run'`. Either one missing → boot fails with the
|
|
2172
|
+
* specific permission name in the error.
|
|
2173
|
+
*/
|
|
2174
|
+
readonly extendSchema?: PluginSchemaContribution;
|
|
2175
|
+
/**
|
|
2176
|
+
* Cross-cutting error schemas merged into every procedure's wire error
|
|
2177
|
+
* union. Lets a plugin interceptor fail with a typed error (e.g.
|
|
2178
|
+
* `RateLimited`) that decodes TYPED on the client instead of as an
|
|
2179
|
+
* untyped defect. The cli applies these to the server rpc group; codegen
|
|
2180
|
+
* emits the matching imports into the generated client group. See
|
|
2181
|
+
* `PluginErrorSchema`.
|
|
2182
|
+
*/
|
|
2183
|
+
readonly errorSchemas?: ReadonlyArray<PluginErrorSchema>;
|
|
2184
|
+
/**
|
|
2185
|
+
* Client-facing RPC route descriptors for `routes` the web client calls
|
|
2186
|
+
* (e.g. `storage.mintUploadTicket` behind `useUpload`). The codegen emits each
|
|
2187
|
+
* into the generated client rpc group so its tag resolves in the browser. Each
|
|
2188
|
+
* `import` MUST resolve to a BROWSER-SAFE module (descriptor/schema only) — see
|
|
2189
|
+
* `PluginRpcClientDescriptor`. Omit for routes only ever called server-side.
|
|
2190
|
+
*/
|
|
2191
|
+
readonly rpcClientDescriptors?: ReadonlyArray<PluginRpcClientDescriptor>;
|
|
2192
|
+
/**
|
|
2193
|
+
* One-time setup invoked the FIRST time this plugin is seen by the
|
|
2194
|
+
* host. Use for schema migrations + resource provisioning that
|
|
2195
|
+
* survive across deactivate/activate cycles. See `PluginInstallHook`.
|
|
2196
|
+
*/
|
|
2197
|
+
readonly onInstall?: PluginInstallHook;
|
|
2198
|
+
/**
|
|
2199
|
+
* One-time teardown invoked when the host removes the plugin
|
|
2200
|
+
* (`voltro plugins uninstall <name>`). Mirror of `onInstall`.
|
|
2201
|
+
*/
|
|
2202
|
+
readonly onUninstall?: PluginUninstallHook;
|
|
2203
|
+
/**
|
|
2204
|
+
* One-shot setup invoked at app boot. Throws abort boot.
|
|
2205
|
+
* Lifecycle hooks run OUTSIDE any request — receive `PluginLifecycleContext`,
|
|
2206
|
+
* not `AppContext`.
|
|
2207
|
+
*/
|
|
2208
|
+
readonly onActivate?: PluginActivateHook;
|
|
2209
|
+
/**
|
|
2210
|
+
* One-shot teardown invoked at app shutdown. The framework waits
|
|
2211
|
+
* up to 5s for it to resolve before SIGKILL.
|
|
2212
|
+
*/
|
|
2213
|
+
readonly onDeactivate?: PluginDeactivateHook;
|
|
2214
|
+
/**
|
|
2215
|
+
* Contribute to the app's OpenTelemetry observability layer — extra
|
|
2216
|
+
* span processors / metric readers / resource attributes / a sampler.
|
|
2217
|
+
* Gathered at boot (before the tracer builds) and merged into the
|
|
2218
|
+
* framework's NodeSdk ALONGSIDE its own exporter + buffer sink (the
|
|
2219
|
+
* framework keeps owning the tracer — vendor SDKs run as OTel
|
|
2220
|
+
* CONSUMERS, never the global provider, so the in-app Traces dashboard
|
|
2221
|
+
* stays intact). This is how `@voltro/plugin-sentry` /
|
|
2222
|
+
* `@voltro/plugin-datadog` route the framework's spans to the vendor
|
|
2223
|
+
* with zero `OTEL_*` env. The hook may be async (lazy-init the vendor
|
|
2224
|
+
* SDK here). Returns of `undefined`/empty are no-ops.
|
|
2225
|
+
*/
|
|
2226
|
+
readonly contributeObservability?: (ctx: PluginLifecycleContext) => Promise<ObservabilityContribution | undefined> | ObservabilityContribution | undefined;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
export declare const workflowCancelDescriptor: ActionProcedureDescriptor<"__voltro.workflow.cancel", Schema.Struct<{
|
|
2230
|
+
workflowName: typeof Schema.String;
|
|
2231
|
+
executionId: typeof Schema.String;
|
|
2232
|
+
}>, Schema.Struct<{
|
|
2233
|
+
ok: typeof Schema.Boolean;
|
|
2234
|
+
}>, typeof Schema.Never>;
|
|
2235
|
+
|
|
2236
|
+
export declare const WorkflowControlInputSchema: Schema.Struct<{
|
|
2237
|
+
workflowName: typeof Schema.String;
|
|
2238
|
+
executionId: typeof Schema.String;
|
|
2239
|
+
}>;
|
|
2240
|
+
|
|
2241
|
+
export declare type WorkflowDomainEventRow = Schema.Schema.Type<typeof WorkflowDomainEventRowSchema>;
|
|
2242
|
+
|
|
2243
|
+
export declare const WorkflowDomainEventRowSchema: Schema.Struct<{
|
|
2244
|
+
id: typeof Schema.String;
|
|
2245
|
+
name: typeof Schema.String;
|
|
2246
|
+
payload: typeof Schema.Unknown;
|
|
2247
|
+
source: typeof Schema.String;
|
|
2248
|
+
subject: Schema.NullOr<typeof Schema.Unknown>;
|
|
2249
|
+
traceId: Schema.NullOr<typeof Schema.String>;
|
|
2250
|
+
occurredAt: typeof Schema.Date;
|
|
2251
|
+
}>;
|
|
2252
|
+
|
|
2253
|
+
export declare const WorkflowDomainEventsInputSchema: Schema.Struct<{
|
|
2254
|
+
name: Schema.optional<typeof Schema.String>;
|
|
2255
|
+
limit: Schema.optional<typeof Schema.Number>;
|
|
2256
|
+
}>;
|
|
2257
|
+
|
|
2258
|
+
export declare const workflowDomainEventsQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.domainEvents", Schema.Struct<{
|
|
2259
|
+
name: Schema.optional<typeof Schema.String>;
|
|
2260
|
+
limit: Schema.optional<typeof Schema.Number>;
|
|
2261
|
+
}>, Schema.Array$<Schema.Struct<{
|
|
2262
|
+
id: typeof Schema.String;
|
|
2263
|
+
name: typeof Schema.String;
|
|
2264
|
+
payload: typeof Schema.Unknown;
|
|
2265
|
+
source: typeof Schema.String;
|
|
2266
|
+
subject: Schema.NullOr<typeof Schema.Unknown>;
|
|
2267
|
+
traceId: Schema.NullOr<typeof Schema.String>;
|
|
2268
|
+
occurredAt: typeof Schema.Date;
|
|
2269
|
+
}>>, typeof Schema.Never>;
|
|
2270
|
+
|
|
2271
|
+
export declare const WorkflowEventDeliveriesInputSchema: Schema.Struct<{
|
|
2272
|
+
eventId: typeof Schema.String;
|
|
2273
|
+
}>;
|
|
2274
|
+
|
|
2275
|
+
export declare const workflowEventDeliveriesQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.event.deliveries", Schema.Struct<{
|
|
2276
|
+
eventId: typeof Schema.String;
|
|
2277
|
+
}>, Schema.Array$<Schema.Struct<{
|
|
2278
|
+
id: typeof Schema.String;
|
|
2279
|
+
eventId: typeof Schema.String;
|
|
2280
|
+
eventName: typeof Schema.String;
|
|
2281
|
+
triggerId: typeof Schema.String;
|
|
2282
|
+
workflowName: typeof Schema.String;
|
|
2283
|
+
executionId: Schema.NullOr<typeof Schema.String>;
|
|
2284
|
+
status: Schema.Literal<["starting", "started", "skipped", "failed"]>;
|
|
2285
|
+
idempotencyKey: typeof Schema.String;
|
|
2286
|
+
skipped: typeof Schema.Boolean;
|
|
2287
|
+
errorMessage: Schema.NullOr<typeof Schema.String>;
|
|
2288
|
+
createdAt: typeof Schema.Date;
|
|
2289
|
+
completedAt: Schema.NullOr<typeof Schema.Date>;
|
|
2290
|
+
}>>, typeof Schema.Never>;
|
|
2291
|
+
|
|
2292
|
+
export declare type WorkflowEventDeliveryRow = Schema.Schema.Type<typeof WorkflowEventDeliveryRowSchema>;
|
|
2293
|
+
|
|
2294
|
+
export declare const WorkflowEventDeliveryRowSchema: Schema.Struct<{
|
|
2295
|
+
id: typeof Schema.String;
|
|
2296
|
+
eventId: typeof Schema.String;
|
|
2297
|
+
eventName: typeof Schema.String;
|
|
2298
|
+
triggerId: typeof Schema.String;
|
|
2299
|
+
workflowName: typeof Schema.String;
|
|
2300
|
+
executionId: Schema.NullOr<typeof Schema.String>;
|
|
2301
|
+
status: Schema.Literal<["starting", "started", "skipped", "failed"]>;
|
|
2302
|
+
idempotencyKey: typeof Schema.String;
|
|
2303
|
+
skipped: typeof Schema.Boolean;
|
|
2304
|
+
errorMessage: Schema.NullOr<typeof Schema.String>;
|
|
2305
|
+
createdAt: typeof Schema.Date;
|
|
2306
|
+
completedAt: Schema.NullOr<typeof Schema.Date>;
|
|
2307
|
+
}>;
|
|
2308
|
+
|
|
2309
|
+
export declare type WorkflowParentClosePolicy = Schema.Schema.Type<typeof WorkflowParentClosePolicySchema>;
|
|
2310
|
+
|
|
2311
|
+
export declare const WorkflowParentClosePolicySchema: Schema.Literal<["cancel", "terminate", "abandon"]>;
|
|
2312
|
+
|
|
2313
|
+
export declare const workflowResumeDescriptor: ActionProcedureDescriptor<"__voltro.workflow.resume", Schema.Struct<{
|
|
2314
|
+
workflowName: typeof Schema.String;
|
|
2315
|
+
executionId: typeof Schema.String;
|
|
2316
|
+
}>, Schema.Struct<{
|
|
2317
|
+
ok: typeof Schema.Boolean;
|
|
2318
|
+
}>, typeof Schema.Never>;
|
|
2319
|
+
|
|
2320
|
+
export declare type WorkflowRunEventRow = Schema.Schema.Type<typeof WorkflowRunEventRowSchema>;
|
|
2321
|
+
|
|
2322
|
+
export declare const WorkflowRunEventRowSchema: Schema.Struct<{
|
|
2323
|
+
id: typeof Schema.String;
|
|
2324
|
+
runId: typeof Schema.String;
|
|
2325
|
+
eventType: typeof Schema.String;
|
|
2326
|
+
payload: Schema.NullOr<typeof Schema.Unknown>;
|
|
2327
|
+
occurredAt: typeof Schema.Date;
|
|
2328
|
+
stepName: Schema.NullOr<typeof Schema.String>;
|
|
2329
|
+
attempt: Schema.NullOr<typeof Schema.Number>;
|
|
2330
|
+
}>;
|
|
2331
|
+
|
|
2332
|
+
export declare const workflowRunEventsQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.run.events", Schema.Struct<{
|
|
2333
|
+
/** `_voltro_workflow_runs.id`; step/event rows reference this id. */
|
|
2334
|
+
runId: typeof Schema.String;
|
|
2335
|
+
}>, Schema.Array$<Schema.Struct<{
|
|
2336
|
+
id: typeof Schema.String;
|
|
2337
|
+
runId: typeof Schema.String;
|
|
2338
|
+
eventType: typeof Schema.String;
|
|
2339
|
+
payload: Schema.NullOr<typeof Schema.Unknown>;
|
|
2340
|
+
occurredAt: typeof Schema.Date;
|
|
2341
|
+
stepName: Schema.NullOr<typeof Schema.String>;
|
|
2342
|
+
attempt: Schema.NullOr<typeof Schema.Number>;
|
|
2343
|
+
}>>, typeof Schema.Never>;
|
|
2344
|
+
|
|
2345
|
+
export declare type WorkflowRunHandle = Schema.Schema.Type<typeof WorkflowRunHandleSchema>;
|
|
2346
|
+
|
|
2347
|
+
export declare const WorkflowRunHandleSchema: Schema.Struct<{
|
|
2348
|
+
/** Stable handle callers pass to `useWorkflowRun(...)`; currently the durable execution id. */
|
|
2349
|
+
id: typeof Schema.String;
|
|
2350
|
+
/** Workflow tag, e.g. `notes.summarise`. */
|
|
2351
|
+
workflowName: typeof Schema.String;
|
|
2352
|
+
/** Deterministic durable id derived from workflow name + idempotency key. */
|
|
2353
|
+
executionId: typeof Schema.String;
|
|
2354
|
+
/** Starting is fire-and-return; waiting is explicit through workflow run APIs. */
|
|
2355
|
+
status: Schema.Literal<["running"]>;
|
|
2356
|
+
}>;
|
|
2357
|
+
|
|
2358
|
+
export declare const workflowRunQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.run", Schema.Struct<{
|
|
2359
|
+
/** `_voltro_workflow_runs.id` OR durable `executionId`. */
|
|
2360
|
+
id: typeof Schema.String;
|
|
2361
|
+
}>, Schema.Array$<Schema.Struct<{
|
|
2362
|
+
id: typeof Schema.String;
|
|
2363
|
+
tag: typeof Schema.String;
|
|
2364
|
+
executionId: typeof Schema.String;
|
|
2365
|
+
status: Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>;
|
|
2366
|
+
payload: typeof Schema.Unknown;
|
|
2367
|
+
workflowVersion: Schema.NullOr<typeof Schema.String>;
|
|
2368
|
+
workflowPatches: Schema.NullOr<typeof Schema.Unknown>;
|
|
2369
|
+
output: Schema.NullOr<typeof Schema.Unknown>;
|
|
2370
|
+
subject: Schema.NullOr<typeof Schema.Unknown>;
|
|
2371
|
+
source: Schema.NullOr<typeof Schema.String>;
|
|
2372
|
+
errorTag: Schema.NullOr<typeof Schema.String>;
|
|
2373
|
+
errorMessage: Schema.NullOr<typeof Schema.String>;
|
|
2374
|
+
cancelled: typeof Schema.Boolean;
|
|
2375
|
+
startedAt: typeof Schema.Date;
|
|
2376
|
+
completedAt: Schema.NullOr<typeof Schema.Date>;
|
|
2377
|
+
durationMs: Schema.NullOr<typeof Schema.Number>;
|
|
2378
|
+
traceId: Schema.NullOr<typeof Schema.String>;
|
|
2379
|
+
parentExecutionId: Schema.NullOr<typeof Schema.String>;
|
|
2380
|
+
parentClosePolicy: Schema.NullOr<Schema.Literal<["cancel", "terminate", "abandon"]>>;
|
|
2381
|
+
}>>, typeof Schema.Never>;
|
|
2382
|
+
|
|
2383
|
+
export declare const WorkflowRunRefSchema: Schema.Struct<{
|
|
2384
|
+
/** `_voltro_workflow_runs.id` OR durable `executionId`. */
|
|
2385
|
+
id: typeof Schema.String;
|
|
2386
|
+
}>;
|
|
2387
|
+
|
|
2388
|
+
export declare type WorkflowRunRow = Schema.Schema.Type<typeof WorkflowRunRowSchema>;
|
|
2389
|
+
|
|
2390
|
+
export declare const WorkflowRunRowSchema: Schema.Struct<{
|
|
2391
|
+
id: typeof Schema.String;
|
|
2392
|
+
tag: typeof Schema.String;
|
|
2393
|
+
executionId: typeof Schema.String;
|
|
2394
|
+
status: Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>;
|
|
2395
|
+
payload: typeof Schema.Unknown;
|
|
2396
|
+
workflowVersion: Schema.NullOr<typeof Schema.String>;
|
|
2397
|
+
workflowPatches: Schema.NullOr<typeof Schema.Unknown>;
|
|
2398
|
+
output: Schema.NullOr<typeof Schema.Unknown>;
|
|
2399
|
+
subject: Schema.NullOr<typeof Schema.Unknown>;
|
|
2400
|
+
source: Schema.NullOr<typeof Schema.String>;
|
|
2401
|
+
errorTag: Schema.NullOr<typeof Schema.String>;
|
|
2402
|
+
errorMessage: Schema.NullOr<typeof Schema.String>;
|
|
2403
|
+
cancelled: typeof Schema.Boolean;
|
|
2404
|
+
startedAt: typeof Schema.Date;
|
|
2405
|
+
completedAt: Schema.NullOr<typeof Schema.Date>;
|
|
2406
|
+
durationMs: Schema.NullOr<typeof Schema.Number>;
|
|
2407
|
+
traceId: Schema.NullOr<typeof Schema.String>;
|
|
2408
|
+
parentExecutionId: Schema.NullOr<typeof Schema.String>;
|
|
2409
|
+
parentClosePolicy: Schema.NullOr<Schema.Literal<["cancel", "terminate", "abandon"]>>;
|
|
2410
|
+
}>;
|
|
2411
|
+
|
|
2412
|
+
export declare type WorkflowRunsInput = Schema.Schema.Type<typeof WorkflowRunsInputSchema>;
|
|
2413
|
+
|
|
2414
|
+
export declare const WorkflowRunsInputSchema: Schema.Struct<{
|
|
2415
|
+
tag: Schema.optional<typeof Schema.String>;
|
|
2416
|
+
status: Schema.optional<Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>>;
|
|
2417
|
+
limit: Schema.optional<typeof Schema.Number>;
|
|
2418
|
+
}>;
|
|
2419
|
+
|
|
2420
|
+
export declare const workflowRunsQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.runs", Schema.Struct<{
|
|
2421
|
+
tag: Schema.optional<typeof Schema.String>;
|
|
2422
|
+
status: Schema.optional<Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>>;
|
|
2423
|
+
limit: Schema.optional<typeof Schema.Number>;
|
|
2424
|
+
}>, Schema.Array$<Schema.Struct<{
|
|
2425
|
+
id: typeof Schema.String;
|
|
2426
|
+
tag: typeof Schema.String;
|
|
2427
|
+
executionId: typeof Schema.String;
|
|
2428
|
+
status: Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>;
|
|
2429
|
+
payload: typeof Schema.Unknown;
|
|
2430
|
+
workflowVersion: Schema.NullOr<typeof Schema.String>;
|
|
2431
|
+
workflowPatches: Schema.NullOr<typeof Schema.Unknown>;
|
|
2432
|
+
output: Schema.NullOr<typeof Schema.Unknown>;
|
|
2433
|
+
subject: Schema.NullOr<typeof Schema.Unknown>;
|
|
2434
|
+
source: Schema.NullOr<typeof Schema.String>;
|
|
2435
|
+
errorTag: Schema.NullOr<typeof Schema.String>;
|
|
2436
|
+
errorMessage: Schema.NullOr<typeof Schema.String>;
|
|
2437
|
+
cancelled: typeof Schema.Boolean;
|
|
2438
|
+
startedAt: typeof Schema.Date;
|
|
2439
|
+
completedAt: Schema.NullOr<typeof Schema.Date>;
|
|
2440
|
+
durationMs: Schema.NullOr<typeof Schema.Number>;
|
|
2441
|
+
traceId: Schema.NullOr<typeof Schema.String>;
|
|
2442
|
+
parentExecutionId: Schema.NullOr<typeof Schema.String>;
|
|
2443
|
+
parentClosePolicy: Schema.NullOr<Schema.Literal<["cancel", "terminate", "abandon"]>>;
|
|
2444
|
+
}>>, typeof Schema.Never>;
|
|
2445
|
+
|
|
2446
|
+
export declare type WorkflowRunStatus = Schema.Schema.Type<typeof WorkflowRunStatusSchema>;
|
|
2447
|
+
|
|
2448
|
+
export declare const WorkflowRunStatusSchema: Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>;
|
|
2449
|
+
|
|
2450
|
+
export declare type WorkflowRunStepRow = Schema.Schema.Type<typeof WorkflowRunStepRowSchema>;
|
|
2451
|
+
|
|
2452
|
+
export declare const WorkflowRunStepRowSchema: Schema.Struct<{
|
|
2453
|
+
id: typeof Schema.String;
|
|
2454
|
+
runId: typeof Schema.String;
|
|
2455
|
+
stepName: typeof Schema.String;
|
|
2456
|
+
attempt: typeof Schema.Number;
|
|
2457
|
+
status: Schema.Literal<["running", "succeeded", "failed"]>;
|
|
2458
|
+
input: Schema.NullOr<typeof Schema.Unknown>;
|
|
2459
|
+
retryPolicy: Schema.NullOr<typeof Schema.Unknown>;
|
|
2460
|
+
output: Schema.NullOr<typeof Schema.Unknown>;
|
|
2461
|
+
errorTag: Schema.NullOr<typeof Schema.String>;
|
|
2462
|
+
errorMessage: Schema.NullOr<typeof Schema.String>;
|
|
2463
|
+
errorCause: Schema.NullOr<typeof Schema.Unknown>;
|
|
2464
|
+
startedAt: typeof Schema.Date;
|
|
2465
|
+
completedAt: Schema.NullOr<typeof Schema.Date>;
|
|
2466
|
+
durationMs: Schema.NullOr<typeof Schema.Number>;
|
|
2467
|
+
}>;
|
|
2468
|
+
|
|
2469
|
+
export declare const workflowRunStepsQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.run.steps", Schema.Struct<{
|
|
2470
|
+
/** `_voltro_workflow_runs.id`; step/event rows reference this id. */
|
|
2471
|
+
runId: typeof Schema.String;
|
|
2472
|
+
}>, Schema.Array$<Schema.Struct<{
|
|
2473
|
+
id: typeof Schema.String;
|
|
2474
|
+
runId: typeof Schema.String;
|
|
2475
|
+
stepName: typeof Schema.String;
|
|
2476
|
+
attempt: typeof Schema.Number;
|
|
2477
|
+
status: Schema.Literal<["running", "succeeded", "failed"]>;
|
|
2478
|
+
input: Schema.NullOr<typeof Schema.Unknown>;
|
|
2479
|
+
retryPolicy: Schema.NullOr<typeof Schema.Unknown>;
|
|
2480
|
+
output: Schema.NullOr<typeof Schema.Unknown>;
|
|
2481
|
+
errorTag: Schema.NullOr<typeof Schema.String>;
|
|
2482
|
+
errorMessage: Schema.NullOr<typeof Schema.String>;
|
|
2483
|
+
errorCause: Schema.NullOr<typeof Schema.Unknown>;
|
|
2484
|
+
startedAt: typeof Schema.Date;
|
|
2485
|
+
completedAt: Schema.NullOr<typeof Schema.Date>;
|
|
2486
|
+
durationMs: Schema.NullOr<typeof Schema.Number>;
|
|
2487
|
+
}>>, typeof Schema.Never>;
|
|
2488
|
+
|
|
2489
|
+
export declare const WorkflowRunTableRefSchema: Schema.Struct<{
|
|
2490
|
+
/** `_voltro_workflow_runs.id`; step/event rows reference this id. */
|
|
2491
|
+
runId: typeof Schema.String;
|
|
2492
|
+
}>;
|
|
2493
|
+
|
|
2494
|
+
export declare const workflowSignalDescriptor: ActionProcedureDescriptor<"__voltro.workflow.signal", Schema.Struct<{
|
|
2495
|
+
id: typeof Schema.String;
|
|
2496
|
+
signalName: typeof Schema.String;
|
|
2497
|
+
payload: Schema.optional<typeof Schema.Unknown>;
|
|
2498
|
+
}>, Schema.Struct<{
|
|
2499
|
+
eventId: typeof Schema.String;
|
|
2500
|
+
}>, typeof Schema.Never>;
|
|
2501
|
+
|
|
2502
|
+
export declare const WorkflowSignalInputSchema: Schema.Struct<{
|
|
2503
|
+
id: typeof Schema.String;
|
|
2504
|
+
signalName: typeof Schema.String;
|
|
2505
|
+
payload: Schema.optional<typeof Schema.Unknown>;
|
|
2506
|
+
}>;
|
|
2507
|
+
|
|
2508
|
+
/**
|
|
2509
|
+
* Per-step plugin interceptor. Wraps every `step()` (==
|
|
2510
|
+
* `Activity.make`) invocation inside a workflow body. Composed
|
|
2511
|
+
* across plugins at boot. The interceptor sees the user's `execute`
|
|
2512
|
+
* Effect AS an Effect — it can wrap with `Effect.tap`,
|
|
2513
|
+
* `Effect.retry`, `Effect.withSpan`, etc.
|
|
2514
|
+
*
|
|
2515
|
+
* Useful for: per-step audit beyond the framework's row-recorder,
|
|
2516
|
+
* step-level retry-policy override, step-level resource scoping,
|
|
2517
|
+
* external observability injection.
|
|
2518
|
+
*
|
|
2519
|
+
* The interceptor runs INSIDE `@effect/workflow`'s replay logic —
|
|
2520
|
+
* on a workflow resume, the framework replays cached Activity
|
|
2521
|
+
* outputs without re-running the user effect, and the interceptor
|
|
2522
|
+
* is bypassed on replay (only fires on the first execution).
|
|
2523
|
+
*/
|
|
2524
|
+
export declare interface WorkflowStepContext {
|
|
2525
|
+
/** Run id from `_voltro_workflow_runs` (`'unrecorded'` outside
|
|
2526
|
+
* the framework's wrapper, e.g. in unit tests). */
|
|
2527
|
+
readonly runId: string;
|
|
2528
|
+
/** Step name (== first arg of `Activity.make({ name })`). */
|
|
2529
|
+
readonly stepName: string;
|
|
2530
|
+
/** Attempt number — 1 on first try, 2+ on retry. */
|
|
2531
|
+
readonly attempt: number;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
export declare type WorkflowStepInterceptor = <Success, ErrorE, R>(next: Effect.Effect<Success, ErrorE, R>, ctx: WorkflowStepContext) => Effect.Effect<Success, ErrorE, R>;
|
|
2535
|
+
|
|
2536
|
+
export declare const workflowUpdateDescriptor: ActionProcedureDescriptor<"__voltro.workflow.update", Schema.Struct<{
|
|
2537
|
+
id: typeof Schema.String;
|
|
2538
|
+
updateName: typeof Schema.String;
|
|
2539
|
+
payload: Schema.optional<typeof Schema.Unknown>;
|
|
2540
|
+
timeoutMs: Schema.optional<typeof Schema.Number>;
|
|
2541
|
+
}>, Schema.Struct<{
|
|
2542
|
+
eventId: typeof Schema.String;
|
|
2543
|
+
updateId: typeof Schema.String;
|
|
2544
|
+
completedEventId: typeof Schema.String;
|
|
2545
|
+
result: typeof Schema.Unknown;
|
|
2546
|
+
}>, typeof Schema.Never>;
|
|
2547
|
+
|
|
2548
|
+
export declare const WorkflowUpdateInputSchema: Schema.Struct<{
|
|
2549
|
+
id: typeof Schema.String;
|
|
2550
|
+
updateName: typeof Schema.String;
|
|
2551
|
+
payload: Schema.optional<typeof Schema.Unknown>;
|
|
2552
|
+
timeoutMs: Schema.optional<typeof Schema.Number>;
|
|
2553
|
+
}>;
|
|
2554
|
+
|
|
2555
|
+
export declare type WorkflowUpdateResult = Schema.Schema.Type<typeof WorkflowUpdateResultSchema>;
|
|
2556
|
+
|
|
2557
|
+
export declare const WorkflowUpdateResultSchema: Schema.Struct<{
|
|
2558
|
+
eventId: typeof Schema.String;
|
|
2559
|
+
updateId: typeof Schema.String;
|
|
2560
|
+
completedEventId: typeof Schema.String;
|
|
2561
|
+
result: typeof Schema.Unknown;
|
|
2562
|
+
}>;
|
|
2563
|
+
|
|
2564
|
+
export { }
|