@voltro/protocol 0.69.0 → 0.70.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 +168 -0
- package/dist/index.d.ts +460 -11
- package/dist/index.js +204 -196
- package/dist/rest.d.ts +392 -12
- package/dist/rest.js +374 -127
- package/dist/restFormat-Bnngx-zI.js +232 -0
- package/dist/session.d.ts +8 -8
- package/package.json +3 -3
- package/dist/serverErrorBus-BTnTpMqj.js +0 -60
package/dist/index.d.ts
CHANGED
|
@@ -761,6 +761,8 @@ export declare interface ClientDescriptor {
|
|
|
761
761
|
readonly kind: 'query' | 'mutation' | 'action' | 'stream' | 'workflow';
|
|
762
762
|
/** Mutation only: connection setup hooks require an explicit connection scope. */
|
|
763
763
|
readonly idempotencyScope?: MutationIdempotencyScope | undefined;
|
|
764
|
+
/** Mutation only: same-key calls run in invocation order on the client. */
|
|
765
|
+
readonly serialize?: MutationSerialization | undefined;
|
|
764
766
|
/** Query only: table(s) the cache should match this subscription against. */
|
|
765
767
|
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
766
768
|
/** Mutation only: tables this mutation writes, with op. */
|
|
@@ -1549,6 +1551,26 @@ export declare const defineMutation: <const Name extends string, Input extends S
|
|
|
1549
1551
|
* never ordinary business writes. Requires a live WebSocket even when dedup
|
|
1550
1552
|
* is disabled; internal/public REST/agent-tool projections are refused. */
|
|
1551
1553
|
readonly idempotencyScope?: MutationIdempotencyScope;
|
|
1554
|
+
/**
|
|
1555
|
+
* Run this mutation's calls to the SAME resource in invocation order, on the
|
|
1556
|
+
* client — for every caller of the tag, not one hook.
|
|
1557
|
+
*
|
|
1558
|
+
* A row lock serialises transactions, not the order a browser invoked them
|
|
1559
|
+
* in: two quick saves of one snapshot can take the lock in reverse order, and
|
|
1560
|
+
* the older snapshot then overwrites the newer one; server-side idempotency
|
|
1561
|
+
* does not see it, and `.version()` detects the conflict and replays the
|
|
1562
|
+
* OLDER write. `key` names the resource from the input; calls sharing a key
|
|
1563
|
+
* run one at a time, FIFO, per subject, the permit spanning connection
|
|
1564
|
+
* setup, the request, replay and its acknowledgement — optimistic previews
|
|
1565
|
+
* stay immediate. Different keys run independently. An unknown outcome (a
|
|
1566
|
+
* transport failure after the request started) blocks the key for that
|
|
1567
|
+
* subject rather than let a later write pass it; a subject change drops the
|
|
1568
|
+
* queue. `maxPending` bounds active plus waiting calls per subject (default
|
|
1569
|
+
* 32); admission beyond it rejects with `MutationSerialExecutionError`.
|
|
1570
|
+
* Declared here, on the mutation, so every `useMutation` of it orders the
|
|
1571
|
+
* same way. Not a durable outbox, a distributed lock or a cross-tab merge.
|
|
1572
|
+
*/
|
|
1573
|
+
readonly serialize?: MutationSerialization<Schema.Schema.Type<Input>>;
|
|
1552
1574
|
/** Optional declarative target(s) — drives auto-optimistic. */
|
|
1553
1575
|
readonly target?: Target<Schema.Schema.Type<Input>, Schema.Schema.Type<Output>>;
|
|
1554
1576
|
/** Declarative authorization guard(s) — the caller must hold the named
|
|
@@ -1858,6 +1880,17 @@ export declare const effectiveScopes: (subject: Subject) => ReadonlyArray<string
|
|
|
1858
1880
|
*/
|
|
1859
1881
|
export declare const encodeEventKey: (key: unknown) => string;
|
|
1860
1882
|
|
|
1883
|
+
/** Encode a failure under `format`: the tagged body the desugar always wrote
|
|
1884
|
+
* (`{ error, …fields }`), Problem Details, or the format's own encoder. */
|
|
1885
|
+
export declare const encodeRestError: (format: RestFormat, error: RestFormatError, ctx: RestFormatContext) => RestFormatResponse;
|
|
1886
|
+
|
|
1887
|
+
/**
|
|
1888
|
+
* Encode a success value under `format`: the bare value, `{ data, next? }`, or
|
|
1889
|
+
* a JSON:API document; a cursor becomes a `Link` header under `link-header`.
|
|
1890
|
+
* `value` is already in wire form (dates, naming applied by `restViewSchema`).
|
|
1891
|
+
*/
|
|
1892
|
+
export declare const encodeRestSuccess: (format: RestFormat, value: unknown, ctx: RestFormatContext) => RestFormatResponse;
|
|
1893
|
+
|
|
1861
1894
|
/**
|
|
1862
1895
|
* Read the `_tag` off a thrown error value, or `undefined` when it has none.
|
|
1863
1896
|
*
|
|
@@ -2607,6 +2640,8 @@ export declare const isWireReachable: (descriptor: {
|
|
|
2607
2640
|
readonly internal?: boolean | undefined;
|
|
2608
2641
|
}) => boolean;
|
|
2609
2642
|
|
|
2643
|
+
export declare const JSONAPI_MEDIA_TYPE = "application/vnd.api+json; charset=utf-8";
|
|
2644
|
+
|
|
2610
2645
|
/**
|
|
2611
2646
|
* Build a scope cache. Hand it to `composeAuthStrategies({ scopeCache })` and
|
|
2612
2647
|
* keep the handle: `invalidate(scopeCacheKey(subject))` from the mutation that
|
|
@@ -2686,6 +2721,8 @@ export declare interface MutationProcedureDescriptor<Name extends string, Input
|
|
|
2686
2721
|
* re-executes on a new server-owned WebSocket identity and rejects HTTP RPC.
|
|
2687
2722
|
* This does not grant access or restore application membership by itself. */
|
|
2688
2723
|
readonly idempotencyScope?: MutationIdempotencyScope | undefined;
|
|
2724
|
+
/** Same-key calls run in invocation order on the client — see `defineMutation`. */
|
|
2725
|
+
readonly serialize?: MutationSerialization<Schema.Schema.Type<Input>> | undefined;
|
|
2689
2726
|
/** Declarative target(s) — drives auto-optimistic on the client AND
|
|
2690
2727
|
* surfaces which tables this mutation touches (debug, future
|
|
2691
2728
|
* query-invalidation analytics). Mutations without a target run
|
|
@@ -2734,6 +2771,14 @@ export declare interface MutationProcedureDescriptor<Name extends string, Input
|
|
|
2734
2771
|
readonly internal: boolean | undefined;
|
|
2735
2772
|
}
|
|
2736
2773
|
|
|
2774
|
+
/** Same-resource ordering for a mutation's calls on the client — see `defineMutation`'s `serialize`. */
|
|
2775
|
+
export declare interface MutationSerialization<Input = unknown> {
|
|
2776
|
+
/** The resource a call writes, from its input; calls sharing it run in order. */
|
|
2777
|
+
readonly key: (input: Input) => string;
|
|
2778
|
+
/** Bound for active plus waiting calls per subject. Default 32. */
|
|
2779
|
+
readonly maxPending?: number;
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2737
2782
|
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>;
|
|
2738
2783
|
|
|
2739
2784
|
export declare interface NestedTargetFields<Input = unknown> {
|
|
@@ -2751,6 +2796,10 @@ export declare interface NestedTargetFields<Input = unknown> {
|
|
|
2751
2796
|
readonly match?: ((value: unknown, input: Input) => boolean) | undefined;
|
|
2752
2797
|
}
|
|
2753
2798
|
|
|
2799
|
+
/** `Link: <path?cursor=…>; rel="next"` — the current query with the cursor
|
|
2800
|
+
* parameter replaced. */
|
|
2801
|
+
export declare const nextLink: (ctx: RestFormatContext, cursor: string, param: string) => string;
|
|
2802
|
+
|
|
2754
2803
|
/**
|
|
2755
2804
|
* Project a runtime descriptor down to the subset the client needs.
|
|
2756
2805
|
* Functions (`shape`, `identify`) are passed through by reference —
|
|
@@ -3310,6 +3359,20 @@ export declare interface PluginErrorSchema {
|
|
|
3310
3359
|
readonly module: string;
|
|
3311
3360
|
readonly name: string;
|
|
3312
3361
|
};
|
|
3362
|
+
/**
|
|
3363
|
+
* How the `publicApi` REST projection answers this error. The projection
|
|
3364
|
+
* encodes a plugin's cross-cutting error the way it encodes a descriptor's
|
|
3365
|
+
* own declared error — tag and fields as the body — under this status
|
|
3366
|
+
* (`422` when absent, the status an undeclared-status descriptor error
|
|
3367
|
+
* gets), with these headers. A rate limiter names `429` and the
|
|
3368
|
+
* `Retry-After` / `RateLimit-*` headers here; a descriptor's own
|
|
3369
|
+
* `errorStatus` entry for the tag still wins. Without this the same
|
|
3370
|
+
* rejection that decoded typed on the rpc wire was a `500` on `/v1`.
|
|
3371
|
+
*/
|
|
3372
|
+
readonly rest?: {
|
|
3373
|
+
readonly status: number;
|
|
3374
|
+
readonly headers?: (error: unknown) => Readonly<Record<string, string>>;
|
|
3375
|
+
};
|
|
3313
3376
|
}
|
|
3314
3377
|
|
|
3315
3378
|
/** A public raw-HTTP route a plugin serves on the framework listener. */
|
|
@@ -3319,9 +3382,22 @@ export declare interface PluginHttpRoute {
|
|
|
3319
3382
|
* this union lacked PATCH; that reason is gone (the `'*'` mount remains
|
|
3320
3383
|
* for its other job: one dispatcher per shared path + a precise 405). */
|
|
3321
3384
|
readonly method: '*' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
3322
|
-
/** Absolute path
|
|
3323
|
-
*
|
|
3385
|
+
/** Absolute path, e.g. `/_voltro/storage`. Matched byte for byte: the
|
|
3386
|
+
* router folds case and a trailing slash to FIND the route, the dispatcher
|
|
3387
|
+
* then asks whether the request's path is one the route declared — so
|
|
3388
|
+
* `/Metrics` and `/metrics/` are not `/metrics`. A `:name` segment takes one
|
|
3389
|
+
* non-empty segment. Whether the route also owns its sub-paths is
|
|
3390
|
+
* `match`. */
|
|
3324
3391
|
readonly path: string;
|
|
3392
|
+
/**
|
|
3393
|
+
* `'prefix'` (default): the route answers its path AND every sub-path
|
|
3394
|
+
* (`/_voltro/storage/abc123`) — a plugin that branches on the tail mounts
|
|
3395
|
+
* once. `'exact'`: the path alone — no sub-path, no trailing slash — which is
|
|
3396
|
+
* what a single document or a scrape endpoint is (`/openapi.json`,
|
|
3397
|
+
* `/metrics`), and what a path guard at the ingress can cover, because
|
|
3398
|
+
* exactly one spelling answers. A path ending in `/*` is a prefix either way.
|
|
3399
|
+
*/
|
|
3400
|
+
readonly match?: 'prefix' | 'exact';
|
|
3325
3401
|
readonly handle: (req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
|
|
3326
3402
|
/** Per-route body cap override (bytes) — wins over the listener's shared
|
|
3327
3403
|
* `maxBodyBytes`. NOTE: routes SHARING a path share one body read, so the
|
|
@@ -3518,6 +3594,13 @@ export declare interface PluginHttpRouteResult {
|
|
|
3518
3594
|
/** Stream the response (SSE) instead of sending `body`. See
|
|
3519
3595
|
* {@link PluginHttpRouteStream}. */
|
|
3520
3596
|
readonly stream?: PluginHttpRouteStream;
|
|
3597
|
+
/**
|
|
3598
|
+
* The error behind a `5xx` — for the server log line that names the
|
|
3599
|
+
* status, never for the wire. A route that maps a throw into a response
|
|
3600
|
+
* value has swallowed the reason by the time the transport logs the value;
|
|
3601
|
+
* this carries it alongside.
|
|
3602
|
+
*/
|
|
3603
|
+
readonly cause?: unknown;
|
|
3521
3604
|
/** Stream a BINARY response (a download, an export) instead of sending
|
|
3522
3605
|
* `body` — see {@link PluginHttpRouteByteStream}. Never buffered by the
|
|
3523
3606
|
* serve layer; never compressed (flush timing + Content-Length are the
|
|
@@ -4052,6 +4135,16 @@ export declare interface PolicyGuardSpec<Input = unknown> {
|
|
|
4052
4135
|
readonly resource: (input: Input) => string | undefined;
|
|
4053
4136
|
}
|
|
4054
4137
|
|
|
4138
|
+
export declare const PROBLEM_JSON = "application/problem+json; charset=utf-8";
|
|
4139
|
+
|
|
4140
|
+
/** RFC 9457 Problem Details for one failure: `type` from the tag, `title` from
|
|
4141
|
+
* the tag's words, `status`, `detail` from the message, the error's own fields
|
|
4142
|
+
* as extension members. */
|
|
4143
|
+
export declare const problemDetails: (error: RestFormatError) => Record<string, unknown>;
|
|
4144
|
+
|
|
4145
|
+
/** A stable, non-resolving URI for an error type: the tag as a URN. */
|
|
4146
|
+
export declare const problemTypeOf: (tag: string) => string;
|
|
4147
|
+
|
|
4055
4148
|
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>;
|
|
4056
4149
|
|
|
4057
4150
|
/** The shape of one procedure, as the pair of peers must agree on it. */
|
|
@@ -4074,6 +4167,23 @@ export declare const PROTOCOL_VERSION: 1;
|
|
|
4074
4167
|
|
|
4075
4168
|
export declare type ProtocolVersion = typeof PROTOCOL_VERSION;
|
|
4076
4169
|
|
|
4170
|
+
/** The `publicApi:` block of an api `app.config.ts`, and the shape of a
|
|
4171
|
+
* descriptor's `publicApi.format` override (every field optional there). */
|
|
4172
|
+
export declare interface PublicApiProfileConfig {
|
|
4173
|
+
/** The profile every projection speaks. Default `'rpc'` — today's wire. */
|
|
4174
|
+
readonly profile?: PublicApiProfileName | RestFormat;
|
|
4175
|
+
readonly dates?: RestDates;
|
|
4176
|
+
readonly errors?: RestErrors;
|
|
4177
|
+
readonly envelope?: RestEnvelope;
|
|
4178
|
+
readonly pagination?: RestPagination;
|
|
4179
|
+
readonly cursorField?: string;
|
|
4180
|
+
readonly cursorParam?: string;
|
|
4181
|
+
readonly naming?: RestNaming;
|
|
4182
|
+
readonly etag?: boolean;
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
export declare type PublicApiProfileName = 'rpc' | 'standard' | 'jsonapi';
|
|
4186
|
+
|
|
4077
4187
|
export declare interface PublicApiSpec {
|
|
4078
4188
|
/** Derived by kind when omitted: query→GET, mutation/action→POST. */
|
|
4079
4189
|
readonly method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
@@ -4099,6 +4209,48 @@ export declare interface PublicApiSpec {
|
|
|
4099
4209
|
/** Streaming queries over request/response: 'snapshot' (default — first
|
|
4100
4210
|
* snapshot, like POST /rpc) | 'sse' (Server-Sent Events of snapshot+deltas). */
|
|
4101
4211
|
readonly stream?: 'snapshot' | 'sse';
|
|
4212
|
+
/**
|
|
4213
|
+
* HTTP status per DECLARED typed error tag, for the projection. A typed
|
|
4214
|
+
* error the handler fails with — one of the descriptor's `error:` union —
|
|
4215
|
+
* answers with its tag and fields as the body, and this status; a tag not
|
|
4216
|
+
* listed here answers `422`, the status the OpenAPI projection documents
|
|
4217
|
+
* for a typed error. The framework's own `ScopeError` is mapped
|
|
4218
|
+
* before this (`403`, or `401` for an anonymous caller) and needs no entry.
|
|
4219
|
+
*
|
|
4220
|
+
* publicApi: { errorStatus: { AccessDeniedError: 403, NotFoundError: 404 } }
|
|
4221
|
+
*/
|
|
4222
|
+
readonly errorStatus?: Readonly<Record<string, number>>;
|
|
4223
|
+
/**
|
|
4224
|
+
* The spec's tag group(s) for this operation. Default: the rpc tag's prefix
|
|
4225
|
+
* (`teams.list` → `teams`) — the first path segment, what a consumer looks
|
|
4226
|
+
* for, and what an SDK generator turns into a client class.
|
|
4227
|
+
*/
|
|
4228
|
+
readonly tags?: ReadonlyArray<string>;
|
|
4229
|
+
/**
|
|
4230
|
+
* The spec's `operationId`. Default: the rpc tag in camelCase (`teams.list`
|
|
4231
|
+
* → `teamsList`) — the stable identifier a generator names methods from.
|
|
4232
|
+
*/
|
|
4233
|
+
readonly operationId?: string;
|
|
4234
|
+
/**
|
|
4235
|
+
* Whether the operation documents a bearer requirement. Default: `'required'`
|
|
4236
|
+
* unless the descriptor declares `openAccess` — then `'optional'` (the key
|
|
4237
|
+
* strategy authenticates a bearer when one is sent, and an anonymous caller
|
|
4238
|
+
* is allowed through), which Swagger UI and generated clients read as
|
|
4239
|
+
* "send the token when you have one". `false` documents no scheme at all.
|
|
4240
|
+
*/
|
|
4241
|
+
readonly security?: 'required' | 'optional' | false;
|
|
4242
|
+
/** A request/response example for the spec — the same semantics as a
|
|
4243
|
+
* `defineRestRoute` example. */
|
|
4244
|
+
readonly example?: {
|
|
4245
|
+
readonly request?: unknown;
|
|
4246
|
+
readonly response?: unknown;
|
|
4247
|
+
};
|
|
4248
|
+
/**
|
|
4249
|
+
* This projection's wire format, overriding the app's `publicApi` profile
|
|
4250
|
+
* (`app.config.ts`) for the one endpoint that must differ — every field
|
|
4251
|
+
* optional, the rest inherited. See `PublicApiProfileConfig`.
|
|
4252
|
+
*/
|
|
4253
|
+
readonly format?: PublicApiProfileConfig;
|
|
4102
4254
|
}
|
|
4103
4255
|
|
|
4104
4256
|
/**
|
|
@@ -4366,6 +4518,14 @@ export declare const requireScope: (subject: Subject, scope: string) => Effect.E
|
|
|
4366
4518
|
/** Drop every registration. Tests only — production registers once at module load. */
|
|
4367
4519
|
export declare const resetPluginAliases: () => void;
|
|
4368
4520
|
|
|
4521
|
+
/**
|
|
4522
|
+
* The format one projection speaks: the app profile, then its building-block
|
|
4523
|
+
* overrides, then the descriptor's own `format`. A named profile is a
|
|
4524
|
+
* `RestFormat` with a name; an object is taken as-is and its fields overridden
|
|
4525
|
+
* the same way.
|
|
4526
|
+
*/
|
|
4527
|
+
export declare const resolveRestFormat: (config: PublicApiProfileConfig | undefined, override?: Partial<PublicApiProfileConfig> | undefined) => RestFormat;
|
|
4528
|
+
|
|
4369
4529
|
/**
|
|
4370
4530
|
* Validate an author's access options and normalise them into the array every
|
|
4371
4531
|
* enforcement path reads. Throws at DEFINITION time — module load, which is
|
|
@@ -4393,6 +4553,263 @@ export declare interface ResourceScopeRequest {
|
|
|
4393
4553
|
*/
|
|
4394
4554
|
export declare type ResourceScopeResolver = (req: ResourceScopeRequest) => Effect.Effect<boolean, unknown>;
|
|
4395
4555
|
|
|
4556
|
+
export declare const REST_PROFILE_NAMES: ReadonlyArray<PublicApiProfileName>;
|
|
4557
|
+
|
|
4558
|
+
export declare type RestDates = 'iso' | 'epoch-ms';
|
|
4559
|
+
|
|
4560
|
+
export declare type RestEnvelope = 'none' | 'data' | 'jsonapi';
|
|
4561
|
+
|
|
4562
|
+
export declare type RestErrors = 'problem-details' | 'tagged';
|
|
4563
|
+
|
|
4564
|
+
/**
|
|
4565
|
+
* The wire format of one `publicApi` surface. `'standard'` and `'jsonapi'` are
|
|
4566
|
+
* named instances of this; an app's own object is the custom case.
|
|
4567
|
+
*/
|
|
4568
|
+
export declare interface RestFormat {
|
|
4569
|
+
readonly name: string;
|
|
4570
|
+
/** `timestampMs` fields: RFC 3339 strings, or the epoch milliseconds the rpc wire carries. */
|
|
4571
|
+
readonly dates: RestDates;
|
|
4572
|
+
/** Failures: RFC 9457 `application/problem+json`, or the tagged object rpc carries. */
|
|
4573
|
+
readonly errors: RestErrors;
|
|
4574
|
+
/** Success bodies: the bare value, `{ data }`, or the JSON:API document. */
|
|
4575
|
+
readonly envelope: RestEnvelope;
|
|
4576
|
+
/** A cursor the handler returns in `cursorField`: as a `Link: <…>; rel="next"`
|
|
4577
|
+
* header (and `next` beside `data`), or left where the handler put it. */
|
|
4578
|
+
readonly pagination: RestPagination;
|
|
4579
|
+
/** The cursor field a paginated output carries. Default `nextCursor`. */
|
|
4580
|
+
readonly cursorField: string;
|
|
4581
|
+
/** The query parameter a cursor travels back in. Default `cursor`. */
|
|
4582
|
+
readonly cursorParam: string;
|
|
4583
|
+
/** Property names on the wire. */
|
|
4584
|
+
readonly naming: RestNaming;
|
|
4585
|
+
/** Weak `ETag` on GET answers, `If-None-Match` → 304. */
|
|
4586
|
+
readonly etag: boolean;
|
|
4587
|
+
/** Custom success encoding — receives the value already in wire form (dates,
|
|
4588
|
+
* naming applied). Replaces the envelope/pagination steps. */
|
|
4589
|
+
readonly encodeSuccess?: (value: unknown, ctx: RestFormatContext) => RestFormatResponse;
|
|
4590
|
+
/** Custom error encoding. Replaces `errors`. */
|
|
4591
|
+
readonly encodeError?: (error: RestFormatError, ctx: RestFormatContext) => RestFormatResponse;
|
|
4592
|
+
/** A hook over the OpenAPI operation the plugin emits for a projection. */
|
|
4593
|
+
readonly describe?: (operation: Record<string, unknown>, ctx: {
|
|
4594
|
+
readonly tag: string;
|
|
4595
|
+
readonly method: string;
|
|
4596
|
+
readonly path: string;
|
|
4597
|
+
}) => Record<string, unknown>;
|
|
4598
|
+
}
|
|
4599
|
+
|
|
4600
|
+
/** What a format sees when it encodes one response. */
|
|
4601
|
+
export declare interface RestFormatContext {
|
|
4602
|
+
/** The rpc tag, e.g. `teams.list`. */
|
|
4603
|
+
readonly tag: string;
|
|
4604
|
+
readonly method: string;
|
|
4605
|
+
/** The request path, for `Link` headers. */
|
|
4606
|
+
readonly path: string;
|
|
4607
|
+
/** The request's query string (without `?`). */
|
|
4608
|
+
readonly query: string;
|
|
4609
|
+
/** The first `source` table of the procedure, when it declares one — the
|
|
4610
|
+
* JSON:API resource `type`. */
|
|
4611
|
+
readonly source: string | undefined;
|
|
4612
|
+
readonly status: number;
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4615
|
+
/** A failure the desugar or the projection is about to answer with. */
|
|
4616
|
+
export declare interface RestFormatError {
|
|
4617
|
+
readonly status: number;
|
|
4618
|
+
/** The typed error's `_tag` (`AccessDeniedError`), or a stable name for a
|
|
4619
|
+
* transport failure (`InvalidRequest`, `Unauthorized`, `Forbidden`, …). */
|
|
4620
|
+
readonly tag: string;
|
|
4621
|
+
/** A human-readable line. */
|
|
4622
|
+
readonly message: string;
|
|
4623
|
+
/** The error's own fields (a typed error), without `_tag`. */
|
|
4624
|
+
readonly fields: Readonly<Record<string, unknown>>;
|
|
4625
|
+
}
|
|
4626
|
+
|
|
4627
|
+
/** A response a format produced: body (already wire-shaped), headers, and the
|
|
4628
|
+
* media type when it is not `application/json`. */
|
|
4629
|
+
export declare interface RestFormatResponse {
|
|
4630
|
+
readonly body: unknown;
|
|
4631
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
4632
|
+
readonly contentType?: string;
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4635
|
+
declare type RestGuard = (ctx: RestRouteContext) => RestGuardRejection | undefined | Promise<RestGuardRejection | undefined>;
|
|
4636
|
+
|
|
4637
|
+
/**
|
|
4638
|
+
* A guard runs after input decode + ctx resolution, before the handler.
|
|
4639
|
+
* Returning a `RestGuardRejection` short-circuits with that status; any
|
|
4640
|
+
* other return (or `undefined`) lets the request proceed.
|
|
4641
|
+
*/
|
|
4642
|
+
declare interface RestGuardRejection {
|
|
4643
|
+
readonly status: number;
|
|
4644
|
+
readonly message: string;
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
declare type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
4648
|
+
|
|
4649
|
+
export declare type RestNaming = 'camelCase' | 'snake_case';
|
|
4650
|
+
|
|
4651
|
+
export declare type RestPagination = 'link-header' | 'body-cursor';
|
|
4652
|
+
|
|
4653
|
+
/** A schema-encoded REST body with an application-selected HTTP status and
|
|
4654
|
+
* response headers. The marker keeps ordinary domain objects unambiguous.
|
|
4655
|
+
* `output` still validates/encodes `body`; use `Schema.Void` with a 204 when
|
|
4656
|
+
* the response intentionally has no representation. */
|
|
4657
|
+
declare interface RestResponse<O> {
|
|
4658
|
+
readonly __voltroRestResponse: true;
|
|
4659
|
+
readonly status: number;
|
|
4660
|
+
readonly body: O;
|
|
4661
|
+
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
4662
|
+
/** A media type other than `application/json` (a JSON:API document). */
|
|
4663
|
+
readonly contentType?: string;
|
|
4664
|
+
}
|
|
4665
|
+
|
|
4666
|
+
/**
|
|
4667
|
+
* Per-call context handed to a REST route handler. Resolved by the serve
|
|
4668
|
+
* pipeline: `subject` from the framework's auth resolver, `headers`
|
|
4669
|
+
* lowercased, `store` the framework `DataStore` injected the same way
|
|
4670
|
+
* plugins receive it via `bindDataStore` (kept untyped here so protocol
|
|
4671
|
+
* stays free of the database dep).
|
|
4672
|
+
*/
|
|
4673
|
+
declare interface RestRouteContext {
|
|
4674
|
+
readonly subject: Subject;
|
|
4675
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
4676
|
+
/** Request metadata and the byte-exact body as received by the HTTP
|
|
4677
|
+
* transport. JSON routes normally use decoded `input.body`; `rawBody` is
|
|
4678
|
+
* for signature verification, multipart parsing, and other protocols where
|
|
4679
|
+
* parsing first would destroy information the handler must authenticate. */
|
|
4680
|
+
readonly method: RestMethod;
|
|
4681
|
+
readonly path: string;
|
|
4682
|
+
readonly query: string;
|
|
4683
|
+
readonly rawBody: Uint8Array;
|
|
4684
|
+
/** Framework `DataStore`. Untyped in protocol (same discipline as
|
|
4685
|
+
* `PluginHttpRoute` / `bindDataStore`); cast to the concrete store at
|
|
4686
|
+
* the call site. `undefined` only in unit tests that don't inject one. */
|
|
4687
|
+
readonly store: unknown;
|
|
4688
|
+
/** Full framework context for this authenticated request. This keeps custom
|
|
4689
|
+
* REST handlers on the same capability surface as query/mutation/action
|
|
4690
|
+
* handlers without creating a protocol-to-runtime package dependency. */
|
|
4691
|
+
readonly app?: unknown;
|
|
4692
|
+
}
|
|
4693
|
+
|
|
4694
|
+
declare interface RestRouteDescriptor<I, O> {
|
|
4695
|
+
readonly method: RestMethod;
|
|
4696
|
+
/** Absolute path, e.g. `/v1/customers`. Matches that path exactly. */
|
|
4697
|
+
readonly path: string;
|
|
4698
|
+
/** Input schema. Shape is `{ query?, params?, body? }` — the desugar
|
|
4699
|
+
* parses the query string, path params, and JSON body into that shape
|
|
4700
|
+
* before decoding. Omit for routes that take no input. The schema's
|
|
4701
|
+
* decoded `Type` flows to the handler's `input`; its `Encoded` is left
|
|
4702
|
+
* open (`any`) so any `Schema.Struct(...)` is accepted. */
|
|
4703
|
+
readonly input?: Schema.Schema<I, any, never>;
|
|
4704
|
+
/** Output schema. The handler returns the decoded `Type`; the desugar
|
|
4705
|
+
* encodes it to JSON for the `200` response. */
|
|
4706
|
+
readonly output: Schema.Schema<O, any, never>;
|
|
4707
|
+
readonly handler: (input: I, ctx: RestRouteContext) => Promise<O | RestResponse<O>> | O | RestResponse<O>;
|
|
4708
|
+
readonly summary?: string;
|
|
4709
|
+
readonly description?: string;
|
|
4710
|
+
readonly example?: RestRouteExample;
|
|
4711
|
+
/** The spec's `operationId` — set by a `publicApi` projection from its tag. */
|
|
4712
|
+
readonly operationId?: string;
|
|
4713
|
+
/** The spec's tag group(s) — set by a projection from its tag prefix. */
|
|
4714
|
+
readonly tags?: ReadonlyArray<string>;
|
|
4715
|
+
/**
|
|
4716
|
+
* Present on a `publicApi` projection: what the route projects, in the wire
|
|
4717
|
+
* form its profile speaks. The OpenAPI generator documents parameters,
|
|
4718
|
+
* bodies, responses and security from it; the desugar answers every refusal
|
|
4719
|
+
* through its format.
|
|
4720
|
+
*/
|
|
4721
|
+
readonly projection?: RestRouteProjection;
|
|
4722
|
+
/** Replacement hint. Sets a `Deprecation: true` response header. */
|
|
4723
|
+
readonly deprecated?: string;
|
|
4724
|
+
/** ISO date. Sets a `Sunset:` header; past the date the route returns
|
|
4725
|
+
* `410 Gone` with a replacement pointer. */
|
|
4726
|
+
readonly sunset?: string;
|
|
4727
|
+
/**
|
|
4728
|
+
* Opt-in API version. `version: 'v2'` + `path: '/customers'` mounts the route
|
|
4729
|
+
* at `/v2/customers` — the same `/vN/` convention the `publicApi:` projection
|
|
4730
|
+
* has always used (`derivePublicPath`) and the built-in `/v1/api-keys`
|
|
4731
|
+
* surface follows.
|
|
4732
|
+
*
|
|
4733
|
+
* OPT-IN on purpose: a route without `version` keeps its literal `path`
|
|
4734
|
+
* untouched. An automatic prefix would silently move every deployed route —
|
|
4735
|
+
* a second breaking change hiding inside a naming feature.
|
|
4736
|
+
*
|
|
4737
|
+
* Two versions of one resource are TWO descriptors: the old version is
|
|
4738
|
+
* ordinary code — visible, testable, deletable — carrying `deprecated` (the
|
|
4739
|
+
* replacement pointer) and `sunset` (the date it starts answering `410`,
|
|
4740
|
+
* whose body then also names this `version`). There is no transformation
|
|
4741
|
+
* DSL, and the rpc SOCKET is deliberately outside this: the generated client
|
|
4742
|
+
* is versioned with the server it was generated from (a stale browser tab
|
|
4743
|
+
* runs the previous client until reload — that skew window exists and is
|
|
4744
|
+
* documented, it is not solved by URL versioning).
|
|
4745
|
+
*/
|
|
4746
|
+
readonly version?: `v${number}`;
|
|
4747
|
+
readonly guards?: ReadonlyArray<RestGuard>;
|
|
4748
|
+
/**
|
|
4749
|
+
* This route STREAMS (Server-Sent Events) rather than resolving one value — its
|
|
4750
|
+
* handler returns `sse(...)`. Declared on the descriptor, not left implicit in
|
|
4751
|
+
* the handler, so anything that INSPECTS routes without running them (the
|
|
4752
|
+
* OpenAPI generator, the inspect manifest) can tell a stream from a buffered
|
|
4753
|
+
* response. A spec that documents a stream as `application/json` is worse than
|
|
4754
|
+
* no spec, because clients are generated from it.
|
|
4755
|
+
*/
|
|
4756
|
+
readonly streaming?: boolean;
|
|
4757
|
+
/** Per-route body cap override (bytes) — see PluginHttpRoute.maxBodyBytes. */
|
|
4758
|
+
readonly maxBodyBytes?: number;
|
|
4759
|
+
/** GET only: derive a weak ETag from the encoded response and answer a
|
|
4760
|
+
* matching `If-None-Match` with 304. The tag is content-derived (an md5
|
|
4761
|
+
* of the JSON), so it is correct across content-encodings — the
|
|
4762
|
+
* transport's compression varies the bytes, not the representation. */
|
|
4763
|
+
readonly etag?: boolean;
|
|
4764
|
+
}
|
|
4765
|
+
|
|
4766
|
+
declare interface RestRouteExample {
|
|
4767
|
+
readonly request?: unknown;
|
|
4768
|
+
readonly response?: unknown;
|
|
4769
|
+
}
|
|
4770
|
+
|
|
4771
|
+
/**
|
|
4772
|
+
* What a `publicApi` projection is, for the two readers that need more than
|
|
4773
|
+
* the route: the OpenAPI generator and the desugar's refusals. `input` and
|
|
4774
|
+
* `output` are the WIRE schemas — the descriptor's, rewritten by the profile
|
|
4775
|
+
* (`restViewSchema`): a `timestampMs` is a `date-time` string under
|
|
4776
|
+
* `dates: 'iso'`, keys are snake_case under that naming. `input` carries the
|
|
4777
|
+
* path parameters as fields; `pathParams` says which.
|
|
4778
|
+
*/
|
|
4779
|
+
declare interface RestRouteProjection {
|
|
4780
|
+
readonly tag: string;
|
|
4781
|
+
readonly kind: 'query' | 'mutation' | 'action';
|
|
4782
|
+
readonly version: string;
|
|
4783
|
+
readonly security: 'required' | 'optional' | 'none';
|
|
4784
|
+
readonly input: Schema.Schema.All;
|
|
4785
|
+
/** The wire names of the input fields bound from the path. */
|
|
4786
|
+
readonly pathParams: ReadonlyArray<string>;
|
|
4787
|
+
readonly output: Schema.Schema.All;
|
|
4788
|
+
/** The first `source` table, when the procedure declares one. */
|
|
4789
|
+
readonly source: string | undefined;
|
|
4790
|
+
/** Every failure the operation answers with a typed body, and its status:
|
|
4791
|
+
* the descriptor's `errorStatus` entries, `422` for the union's other tags,
|
|
4792
|
+
* and each plugin error that names a REST status. */
|
|
4793
|
+
readonly errorResponses: ReadonlyArray<{
|
|
4794
|
+
readonly status: number;
|
|
4795
|
+
readonly tag: string;
|
|
4796
|
+
readonly schema: Schema.Schema.All;
|
|
4797
|
+
}>;
|
|
4798
|
+
readonly format: RestFormat;
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4801
|
+
/**
|
|
4802
|
+
* Rewrite a descriptor's schema into the shape the profile puts on the wire:
|
|
4803
|
+
* `timestampMs` (`DateFromNumber`) becomes an RFC 3339 string under
|
|
4804
|
+
* `dates: 'iso'`, and every struct's property names become `snake_case` under
|
|
4805
|
+
* `naming: 'snake_case'` — as a transformation, so decoding a wire value yields
|
|
4806
|
+
* the descriptor's own typed value and encoding the typed value yields the
|
|
4807
|
+
* wire form. `JSONSchema.make` of the result documents the wire (a
|
|
4808
|
+
* `format: date-time` string, the snake keys). Classes and other opaque
|
|
4809
|
+
* transformations pass through unchanged; a struct inside one keeps its keys.
|
|
4810
|
+
*/
|
|
4811
|
+
export declare const restViewSchema: (schema: Schema.Schema.All, format: RestFormat) => Schema.Schema.All;
|
|
4812
|
+
|
|
4396
4813
|
/** The id of a row, addressed in op paths as `/<id>`. */
|
|
4397
4814
|
export declare type RowId = string | number;
|
|
4398
4815
|
|
|
@@ -4563,6 +4980,14 @@ export declare interface RpcInterceptorContext {
|
|
|
4563
4980
|
* plugins (Sentry/Datadog) attach an error to the exact span, not just
|
|
4564
4981
|
* the trace. Absent when no tracer is installed. */
|
|
4565
4982
|
readonly spanId?: string;
|
|
4983
|
+
/**
|
|
4984
|
+
* `'rest'` when the call came through the `publicApi` projection (`/v1`);
|
|
4985
|
+
* absent on the rpc wire. A per-endpoint policy on the projection — the
|
|
4986
|
+
* descriptor's `publicApi.rateLimit` — applies to these calls only.
|
|
4987
|
+
*/
|
|
4988
|
+
readonly surface?: 'rest';
|
|
4989
|
+
/** The descriptor's `publicApi` spec, on a projected call. */
|
|
4990
|
+
readonly publicApi?: PublicApiSpec;
|
|
4566
4991
|
}
|
|
4567
4992
|
|
|
4568
4993
|
/**
|
|
@@ -4959,18 +5384,18 @@ export declare const Subject: Schema.Union<[Schema.Struct<{
|
|
|
4959
5384
|
export declare type Subject = typeof Subject.Type;
|
|
4960
5385
|
|
|
4961
5386
|
export declare const SubjectIdentity: Schema.Union<[Schema.Struct<{
|
|
4962
|
-
id: typeof Schema.String;
|
|
4963
5387
|
type: Schema.Literal<["user"]>;
|
|
5388
|
+
id: typeof Schema.String;
|
|
4964
5389
|
tenantId: typeof Schema.String;
|
|
4965
5390
|
metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
|
|
4966
5391
|
}>, Schema.Struct<{
|
|
4967
|
-
id: typeof Schema.String;
|
|
4968
5392
|
type: Schema.Literal<["apiKey"]>;
|
|
5393
|
+
id: typeof Schema.String;
|
|
4969
5394
|
tenantId: typeof Schema.String;
|
|
4970
5395
|
metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
|
|
4971
5396
|
}>, Schema.Struct<{
|
|
4972
|
-
id: typeof Schema.String;
|
|
4973
5397
|
type: Schema.Literal<["serviceAccount"]>;
|
|
5398
|
+
id: typeof Schema.String;
|
|
4974
5399
|
tenantId: typeof Schema.String;
|
|
4975
5400
|
metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
|
|
4976
5401
|
}>, Schema.Struct<{
|
|
@@ -4995,8 +5420,8 @@ export declare const SubjectIdentity: Schema.Union<[Schema.Struct<{
|
|
|
4995
5420
|
*/
|
|
4996
5421
|
credentialRejected: Schema.optional<typeof Schema.String>;
|
|
4997
5422
|
}>, Schema.Struct<{
|
|
4998
|
-
id: typeof Schema.String;
|
|
4999
5423
|
type: Schema.Literal<["system"]>;
|
|
5424
|
+
id: typeof Schema.String;
|
|
5000
5425
|
tenantId: typeof Schema.Null;
|
|
5001
5426
|
metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
|
|
5002
5427
|
}>]>;
|
|
@@ -5034,37 +5459,37 @@ export declare class SubjectService extends SubjectService_base {
|
|
|
5034
5459
|
}
|
|
5035
5460
|
|
|
5036
5461
|
declare const SubjectService_base: Context.TagClass<SubjectService, "@voltro/Subject", {
|
|
5037
|
-
readonly id: string;
|
|
5038
5462
|
readonly type: "user";
|
|
5463
|
+
readonly id: string;
|
|
5039
5464
|
readonly tenantId: string;
|
|
5040
5465
|
readonly scopes?: readonly string[] | undefined;
|
|
5041
5466
|
readonly metadata?: {
|
|
5042
5467
|
readonly [x: string]: unknown;
|
|
5043
5468
|
} | undefined;
|
|
5044
5469
|
} | {
|
|
5045
|
-
readonly id: string;
|
|
5046
5470
|
readonly type: "apiKey";
|
|
5471
|
+
readonly id: string;
|
|
5047
5472
|
readonly tenantId: string;
|
|
5048
5473
|
readonly scopes?: readonly string[] | undefined;
|
|
5049
5474
|
readonly metadata?: {
|
|
5050
5475
|
readonly [x: string]: unknown;
|
|
5051
5476
|
} | undefined;
|
|
5052
5477
|
} | {
|
|
5053
|
-
readonly id: string;
|
|
5054
5478
|
readonly type: "serviceAccount";
|
|
5479
|
+
readonly id: string;
|
|
5055
5480
|
readonly tenantId: string;
|
|
5056
5481
|
readonly scopes?: readonly string[] | undefined;
|
|
5057
5482
|
readonly metadata?: {
|
|
5058
5483
|
readonly [x: string]: unknown;
|
|
5059
5484
|
} | undefined;
|
|
5060
5485
|
} | {
|
|
5061
|
-
readonly id: null;
|
|
5062
5486
|
readonly type: "anonymous";
|
|
5487
|
+
readonly id: null;
|
|
5063
5488
|
readonly tenantId: string | null;
|
|
5064
5489
|
readonly credentialRejected?: string | undefined;
|
|
5065
5490
|
} | {
|
|
5066
|
-
readonly id: string;
|
|
5067
5491
|
readonly type: "system";
|
|
5492
|
+
readonly id: string;
|
|
5068
5493
|
readonly tenantId: null;
|
|
5069
5494
|
readonly scopes?: readonly string[] | undefined;
|
|
5070
5495
|
readonly metadata?: {
|
|
@@ -5404,6 +5829,8 @@ export declare const toRpc: <Name extends string, Input extends Schema.Schema.An
|
|
|
5404
5829
|
revision: Schema.optional<typeof Schema.Number>;
|
|
5405
5830
|
}>]>, 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>;
|
|
5406
5831
|
|
|
5832
|
+
export declare const toSnakeCase: (key: string) => string;
|
|
5833
|
+
|
|
5407
5834
|
/**
|
|
5408
5835
|
* Coerce a timestamp read (`Date` / epoch-ms number / ISO string) to
|
|
5409
5836
|
* epoch milliseconds. Unrecognised inputs collapse to `0`.
|
|
@@ -5991,6 +6418,28 @@ export declare interface VoltroPlugin {
|
|
|
5991
6418
|
* SDK here). Returns of `undefined`/empty are no-ops.
|
|
5992
6419
|
*/
|
|
5993
6420
|
readonly contributeObservability?: (ctx: PluginLifecycleContext) => Promise<ObservabilityContribution | undefined> | ObservabilityContribution | undefined;
|
|
6421
|
+
/**
|
|
6422
|
+
* Told the app's mounted REST surface — hand-authored `restRoutes` AND the
|
|
6423
|
+
* routes projected from `publicApi` annotations — once the boot has built it,
|
|
6424
|
+
* on both boot paths. A documentation plugin reads its API from here instead
|
|
6425
|
+
* of asking the app to import its own descriptors into `app.config.ts`,
|
|
6426
|
+
* which is where an app importing hundreds of descriptor modules before
|
|
6427
|
+
* discovery has hung its own boot. Synchronous: the surface is already
|
|
6428
|
+
* mounted when this runs, so the hook only records.
|
|
6429
|
+
*/
|
|
6430
|
+
readonly onRestSurface?: (routes: ReadonlyArray<RestRouteDescriptor<any, any>>) => void;
|
|
6431
|
+
/**
|
|
6432
|
+
* Told the app's wire-reachable rpc procedures — every query, mutation,
|
|
6433
|
+
* action and stream discovery found, minus `internal` ones — once discovery
|
|
6434
|
+
* has run, on both boot paths. The procedure counterpart of
|
|
6435
|
+
* `onRestSurface`, for the same reason: a documentation plugin that wants
|
|
6436
|
+
* the descriptors otherwise asks the app to import them into
|
|
6437
|
+
* `app.config.ts`, which evaluates every descriptor module before discovery
|
|
6438
|
+
* on every load of the config — and `voltro serve` loads no TypeScript at
|
|
6439
|
+
* runtime, so a plugin cannot read them later either. Synchronous: records
|
|
6440
|
+
* only.
|
|
6441
|
+
*/
|
|
6442
|
+
readonly onProcedureSurface?: (procedures: ReadonlyArray<ProcedureDescriptor>) => void;
|
|
5994
6443
|
}
|
|
5995
6444
|
|
|
5996
6445
|
/**
|