@voltro/protocol 0.69.1 → 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 +80 -0
- package/dist/index.d.ts +320 -11
- package/dist/index.js +204 -196
- package/dist/rest.d.ts +355 -17
- package/dist/rest.js +302 -157
- 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/rest.d.ts
CHANGED
|
@@ -126,7 +126,7 @@ export declare const coerceQueryValue: (value: unknown, ast: AST.AST) => unknown
|
|
|
126
126
|
* route, in stable (path) order. The boot layer feeds these straight into
|
|
127
127
|
* `restRoutesToHttpRoutes` alongside any hand-authored `restRoutes`.
|
|
128
128
|
*/
|
|
129
|
-
export declare const collectPublicApiRoutes: (entries: ReadonlyArray<PublicApiEntry
|
|
129
|
+
export declare const collectPublicApiRoutes: (entries: ReadonlyArray<PublicApiEntry>, options?: PublicApiProjectionOptions) => ReadonlyArray<RestRouteDescriptor<Record<string, unknown>, unknown>>;
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* What a descriptor CARRIES: the author's guards, or the erased form of their
|
|
@@ -182,6 +182,21 @@ export declare const derivePublicPath: (tag: string, spec: PublicApiSpec) => str
|
|
|
182
182
|
*/
|
|
183
183
|
export declare const dispatchSharedPath: (group: ReadonlyArray<PluginHttpRoute>, req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
|
|
184
184
|
|
|
185
|
+
/** Encode a failure under `format`: the tagged body the desugar always wrote
|
|
186
|
+
* (`{ error, …fields }`), Problem Details, or the format's own encoder. */
|
|
187
|
+
export declare const encodeRestError: (format: RestFormat, error: RestFormatError, ctx: RestFormatContext) => RestFormatResponse;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Encode a success value under `format`: the bare value, `{ data, next? }`, or
|
|
191
|
+
* a JSON:API document; a cursor becomes a `Link` header under `link-header`.
|
|
192
|
+
* `value` is already in wire form (dates, naming applied by `restViewSchema`).
|
|
193
|
+
*/
|
|
194
|
+
export declare const encodeRestSuccess: (format: RestFormat, value: unknown, ctx: RestFormatContext) => RestFormatResponse;
|
|
195
|
+
|
|
196
|
+
/** The member of an error union that declares `tag`, as a schema of its own —
|
|
197
|
+
* for the spec's per-status responses. */
|
|
198
|
+
export declare const errorMemberSchema: (error: Schema.Schema.All | undefined, tag: string) => Schema.Schema.All | undefined;
|
|
199
|
+
|
|
185
200
|
/** Normalize the `exposeAsTool` shorthand. `true` is only valid when the
|
|
186
201
|
* descriptor carries a top-level `description`; callers pass that in. */
|
|
187
202
|
declare type ExposeAsTool = boolean | ExposeAsToolSpec;
|
|
@@ -306,6 +321,8 @@ export declare const isRestResponse: (value: unknown) => value is RestResponse<u
|
|
|
306
321
|
|
|
307
322
|
export declare const isRestStreamResponse: (v: unknown) => v is RestStreamResponse;
|
|
308
323
|
|
|
324
|
+
export declare const JSONAPI_MEDIA_TYPE = "application/vnd.api+json; charset=utf-8";
|
|
325
|
+
|
|
309
326
|
/** Extract `:name` path params by aligning the route PATTERN with the request
|
|
310
327
|
* path segment-by-segment (`/projects/:project/deploy` + `/projects/acme/deploy`
|
|
311
328
|
* → `{ project: 'acme' }`). `PluginHttpRouteRequest` carries no matched params,
|
|
@@ -327,6 +344,8 @@ declare interface MutationProcedureDescriptor<Name extends string, Input extends
|
|
|
327
344
|
* re-executes on a new server-owned WebSocket identity and rejects HTTP RPC.
|
|
328
345
|
* This does not grant access or restore application membership by itself. */
|
|
329
346
|
readonly idempotencyScope?: MutationIdempotencyScope | undefined;
|
|
347
|
+
/** Same-key calls run in invocation order on the client — see `defineMutation`. */
|
|
348
|
+
readonly serialize?: MutationSerialization<Schema.Schema.Type<Input>> | undefined;
|
|
330
349
|
/** Declarative target(s) — drives auto-optimistic on the client AND
|
|
331
350
|
* surfaces which tables this mutation touches (debug, future
|
|
332
351
|
* query-invalidation analytics). Mutations without a target run
|
|
@@ -375,6 +394,14 @@ declare interface MutationProcedureDescriptor<Name extends string, Input extends
|
|
|
375
394
|
readonly internal: boolean | undefined;
|
|
376
395
|
}
|
|
377
396
|
|
|
397
|
+
/** Same-resource ordering for a mutation's calls on the client — see `defineMutation`'s `serialize`. */
|
|
398
|
+
declare interface MutationSerialization<Input = unknown> {
|
|
399
|
+
/** The resource a call writes, from its input; calls sharing it run in order. */
|
|
400
|
+
readonly key: (input: Input) => string;
|
|
401
|
+
/** Bound for active plus waiting calls per subject. Default 32. */
|
|
402
|
+
readonly maxPending?: number;
|
|
403
|
+
}
|
|
404
|
+
|
|
378
405
|
declare interface NestedTargetFields<Input = unknown> {
|
|
379
406
|
/** Dot-path to the nested array within the query VALUE to patch (e.g.
|
|
380
407
|
* `'snapshot.projects'`). Absent → patch the top-level row array (default). */
|
|
@@ -390,6 +417,15 @@ declare interface NestedTargetFields<Input = unknown> {
|
|
|
390
417
|
readonly match?: ((value: unknown, input: Input) => boolean) | undefined;
|
|
391
418
|
}
|
|
392
419
|
|
|
420
|
+
/** `Link: <path?cursor=…>; rel="next"` — the current query with the cursor
|
|
421
|
+
* parameter replaced. */
|
|
422
|
+
export declare const nextLink: (ctx: RestFormatContext, cursor: string, param: string) => string;
|
|
423
|
+
|
|
424
|
+
/** `{teamId}` — the form every guideline writes first — is the same route as
|
|
425
|
+
* `:teamId`; the desugar matches the colon form, so the path is normalised
|
|
426
|
+
* once here. */
|
|
427
|
+
export declare const normalizePathParams: (path: string) => string;
|
|
428
|
+
|
|
393
429
|
/**
|
|
394
430
|
* The runtime-erased form of a procedure's `openAccess:` — a DECLARED decision
|
|
395
431
|
* that this procedure needs no authorization check, and the reason.
|
|
@@ -411,6 +447,50 @@ declare interface OpenAccessSpec {
|
|
|
411
447
|
readonly open: string;
|
|
412
448
|
}
|
|
413
449
|
|
|
450
|
+
/** The rpc tag as an `operationId`: `teams.list` → `teamsList`. */
|
|
451
|
+
export declare const operationIdOf: (tag: string) => string;
|
|
452
|
+
|
|
453
|
+
/** The parameter names a route pattern binds, in order. */
|
|
454
|
+
export declare const pathParamNames: (path: string) => ReadonlyArray<string>;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* A cross-cutting error schema a plugin merges into EVERY procedure's
|
|
458
|
+
* wire error union. Use when a plugin interceptor can fail with a typed
|
|
459
|
+
* error (e.g. a rate-limit `RateLimited`) that must decode TYPED on the
|
|
460
|
+
* client rather than crossing as an untyped defect.
|
|
461
|
+
*
|
|
462
|
+
* Two halves because the error must be applied in two places that can't
|
|
463
|
+
* share a value:
|
|
464
|
+
* - `schema`: the live `effect/Schema` — used at runtime when the cli
|
|
465
|
+
* assembles the SERVER rpc group.
|
|
466
|
+
* - `import`: where to import the same schema from — used by codegen to
|
|
467
|
+
* emit the import into the generated CLIENT rpc group, so both ends
|
|
468
|
+
* agree on the wire shape. `name` must be the exported identifier.
|
|
469
|
+
*
|
|
470
|
+
* Requires the plugin to ship the schema as a NAMED export from `module`.
|
|
471
|
+
*/
|
|
472
|
+
declare interface PluginErrorSchema {
|
|
473
|
+
readonly schema: Schema.Schema.All;
|
|
474
|
+
readonly import: {
|
|
475
|
+
readonly module: string;
|
|
476
|
+
readonly name: string;
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* How the `publicApi` REST projection answers this error. The projection
|
|
480
|
+
* encodes a plugin's cross-cutting error the way it encodes a descriptor's
|
|
481
|
+
* own declared error — tag and fields as the body — under this status
|
|
482
|
+
* (`422` when absent, the status an undeclared-status descriptor error
|
|
483
|
+
* gets), with these headers. A rate limiter names `429` and the
|
|
484
|
+
* `Retry-After` / `RateLimit-*` headers here; a descriptor's own
|
|
485
|
+
* `errorStatus` entry for the tag still wins. Without this the same
|
|
486
|
+
* rejection that decoded typed on the rpc wire was a `500` on `/v1`.
|
|
487
|
+
*/
|
|
488
|
+
readonly rest?: {
|
|
489
|
+
readonly status: number;
|
|
490
|
+
readonly headers?: (error: unknown) => Readonly<Record<string, string>>;
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
414
494
|
/** A public raw-HTTP route a plugin serves on the framework listener. */
|
|
415
495
|
declare interface PluginHttpRoute {
|
|
416
496
|
/** HTTP method, or `'*'` for any (the handler decides). PATCH/HEAD/OPTIONS
|
|
@@ -418,9 +498,22 @@ declare interface PluginHttpRoute {
|
|
|
418
498
|
* this union lacked PATCH; that reason is gone (the `'*'` mount remains
|
|
419
499
|
* for its other job: one dispatcher per shared path + a precise 405). */
|
|
420
500
|
readonly method: '*' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
421
|
-
/** Absolute path
|
|
422
|
-
*
|
|
501
|
+
/** Absolute path, e.g. `/_voltro/storage`. Matched byte for byte: the
|
|
502
|
+
* router folds case and a trailing slash to FIND the route, the dispatcher
|
|
503
|
+
* then asks whether the request's path is one the route declared — so
|
|
504
|
+
* `/Metrics` and `/metrics/` are not `/metrics`. A `:name` segment takes one
|
|
505
|
+
* non-empty segment. Whether the route also owns its sub-paths is
|
|
506
|
+
* `match`. */
|
|
423
507
|
readonly path: string;
|
|
508
|
+
/**
|
|
509
|
+
* `'prefix'` (default): the route answers its path AND every sub-path
|
|
510
|
+
* (`/_voltro/storage/abc123`) — a plugin that branches on the tail mounts
|
|
511
|
+
* once. `'exact'`: the path alone — no sub-path, no trailing slash — which is
|
|
512
|
+
* what a single document or a scrape endpoint is (`/openapi.json`,
|
|
513
|
+
* `/metrics`), and what a path guard at the ingress can cover, because
|
|
514
|
+
* exactly one spelling answers. A path ending in `/*` is a prefix either way.
|
|
515
|
+
*/
|
|
516
|
+
readonly match?: 'prefix' | 'exact';
|
|
424
517
|
readonly handle: (req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
|
|
425
518
|
/** Per-route body cap override (bytes) — wins over the listener's shared
|
|
426
519
|
* `maxBodyBytes`. NOTE: routes SHARING a path share one body read, so the
|
|
@@ -593,6 +686,13 @@ declare interface PluginHttpRouteResult {
|
|
|
593
686
|
/** Stream the response (SSE) instead of sending `body`. See
|
|
594
687
|
* {@link PluginHttpRouteStream}. */
|
|
595
688
|
readonly stream?: PluginHttpRouteStream;
|
|
689
|
+
/**
|
|
690
|
+
* The error behind a `5xx` — for the server log line that names the
|
|
691
|
+
* status, never for the wire. A route that maps a throw into a response
|
|
692
|
+
* value has swallowed the reason by the time the transport logs the value;
|
|
693
|
+
* this carries it alongside.
|
|
694
|
+
*/
|
|
695
|
+
readonly cause?: unknown;
|
|
596
696
|
/** Stream a BINARY response (a download, an export) instead of sending
|
|
597
697
|
* `body` — see {@link PluginHttpRouteByteStream}. Never buffered by the
|
|
598
698
|
* serve layer; never compressed (flush timing + Content-Length are the
|
|
@@ -649,15 +749,27 @@ declare interface PolicyGuardSpec<Input = unknown> {
|
|
|
649
749
|
readonly resource: (input: Input) => string | undefined;
|
|
650
750
|
}
|
|
651
751
|
|
|
752
|
+
export declare const PROBLEM_JSON = "application/problem+json; charset=utf-8";
|
|
753
|
+
|
|
754
|
+
/** RFC 9457 Problem Details for one failure: `type` from the tag, `title` from
|
|
755
|
+
* the tag's words, `status`, `detail` from the message, the error's own fields
|
|
756
|
+
* as extension members. */
|
|
757
|
+
export declare const problemDetails: (error: RestFormatError) => Record<string, unknown>;
|
|
758
|
+
|
|
759
|
+
/** A stable, non-resolving URI for an error type: the tag as a URN. */
|
|
760
|
+
export declare const problemTypeOf: (tag: string) => string;
|
|
761
|
+
|
|
652
762
|
/**
|
|
653
763
|
* Translate a handler's failure into the response the projection promised:
|
|
654
764
|
* a DECLARED typed error becomes a `RestRouteError` with the tag, the fields
|
|
655
765
|
* and the status `errorStatus` assigns (`422` by default, the status the
|
|
656
|
-
* OpenAPI projection already documents for a typed error)
|
|
657
|
-
*
|
|
658
|
-
*
|
|
766
|
+
* OpenAPI projection already documents for a typed error); a plugin's
|
|
767
|
+
* cross-cutting error answers under the status the plugin names. Anything
|
|
768
|
+
* else is rethrown for the desugar's shared mapping (`ScopeError` → 403/401, a
|
|
769
|
+
* `status`-carrying error → itself, the rest → 500). The body is the tagged
|
|
770
|
+
* `{ error, …fields }` form; `projectedFailureFor` speaks the profile.
|
|
659
771
|
*/
|
|
660
|
-
export declare const projectedFailure: (err: unknown, descriptor: PublicApiDescriptor, spec: PublicApiSpec) => unknown;
|
|
772
|
+
export declare const projectedFailure: (err: unknown, descriptor: PublicApiDescriptor, spec: PublicApiSpec, pluginErrors?: ReadonlyArray<PluginErrorSchema>) => unknown;
|
|
661
773
|
|
|
662
774
|
/** A descriptor kind that can be projected to REST (streams are not, in v1). */
|
|
663
775
|
export declare type PublicApiDescriptor = 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>;
|
|
@@ -677,17 +789,47 @@ export declare interface PublicApiEntry {
|
|
|
677
789
|
* handler through the SAME AuthMiddleware/RBAC/tenant/audit as the rpc path. */
|
|
678
790
|
export declare type PublicApiInvoke = (input: unknown, ctx: RestRouteContext) => Promise<unknown> | unknown;
|
|
679
791
|
|
|
792
|
+
/** The `publicApi:` block of an api `app.config.ts`, and the shape of a
|
|
793
|
+
* descriptor's `publicApi.format` override (every field optional there). */
|
|
794
|
+
export declare interface PublicApiProfileConfig {
|
|
795
|
+
/** The profile every projection speaks. Default `'rpc'` — today's wire. */
|
|
796
|
+
readonly profile?: PublicApiProfileName | RestFormat;
|
|
797
|
+
readonly dates?: RestDates;
|
|
798
|
+
readonly errors?: RestErrors;
|
|
799
|
+
readonly envelope?: RestEnvelope;
|
|
800
|
+
readonly pagination?: RestPagination;
|
|
801
|
+
readonly cursorField?: string;
|
|
802
|
+
readonly cursorParam?: string;
|
|
803
|
+
readonly naming?: RestNaming;
|
|
804
|
+
readonly etag?: boolean;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
export declare type PublicApiProfileName = 'rpc' | 'standard' | 'jsonapi';
|
|
808
|
+
|
|
809
|
+
/** What a projection needs beyond its descriptor: the errors every mounted
|
|
810
|
+
* plugin may fail a call with, and the app's `publicApi` profile. */
|
|
811
|
+
export declare interface PublicApiProjectionOptions {
|
|
812
|
+
readonly pluginErrors?: ReadonlyArray<PluginErrorSchema>;
|
|
813
|
+
/** The `publicApi:` block of the api's `app.config.ts`. Absent → `'rpc'`. */
|
|
814
|
+
readonly profile?: PublicApiProfileConfig;
|
|
815
|
+
}
|
|
816
|
+
|
|
680
817
|
/**
|
|
681
|
-
* Synthesize the public REST route for ONE descriptor.
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
818
|
+
* Synthesize the public REST route for ONE descriptor.
|
|
819
|
+
*
|
|
820
|
+
* The input arrives as `{ query, params, body }` from the desugar and is
|
|
821
|
+
* assembled into ONE object — the query (GET) or body (else), plus every
|
|
822
|
+
* path parameter bound to the input field of the same name — coerced toward
|
|
823
|
+
* the input's encoded shape (a query string and a path carry strings), then
|
|
824
|
+
* decoded by the input schema in the profile's wire form (`restViewSchema`).
|
|
825
|
+
* The handler receives the descriptor's typed input; its output is encoded to
|
|
826
|
+
* the wire form and enveloped by the profile. A query is drained to its first
|
|
827
|
+
* snapshot by the serve layer — same as POST /rpc — unless `stream: 'sse'`
|
|
828
|
+
* streams it. The guards are `requireAuthenticated()` unless the descriptor
|
|
829
|
+
* declares `openAccess`, then one `requireScope` per `scopes` entry, on top of
|
|
830
|
+
* the handler's own RBAC.
|
|
689
831
|
*/
|
|
690
|
-
export declare const publicApiRoute: (descriptor: PublicApiDescriptor, invoke: PublicApiInvoke, subscribe?: PublicApiSubscribe) => RestRouteDescriptor<Record<string, unknown>, unknown>;
|
|
832
|
+
export declare const publicApiRoute: (descriptor: PublicApiDescriptor, invoke: PublicApiInvoke, subscribe?: PublicApiSubscribe, options?: PublicApiProjectionOptions) => RestRouteDescriptor<Record<string, unknown>, unknown>;
|
|
691
833
|
|
|
692
834
|
declare interface PublicApiSpec {
|
|
693
835
|
/** Derived by kind when omitted: query→GET, mutation/action→POST. */
|
|
@@ -725,6 +867,37 @@ declare interface PublicApiSpec {
|
|
|
725
867
|
* publicApi: { errorStatus: { AccessDeniedError: 403, NotFoundError: 404 } }
|
|
726
868
|
*/
|
|
727
869
|
readonly errorStatus?: Readonly<Record<string, number>>;
|
|
870
|
+
/**
|
|
871
|
+
* The spec's tag group(s) for this operation. Default: the rpc tag's prefix
|
|
872
|
+
* (`teams.list` → `teams`) — the first path segment, what a consumer looks
|
|
873
|
+
* for, and what an SDK generator turns into a client class.
|
|
874
|
+
*/
|
|
875
|
+
readonly tags?: ReadonlyArray<string>;
|
|
876
|
+
/**
|
|
877
|
+
* The spec's `operationId`. Default: the rpc tag in camelCase (`teams.list`
|
|
878
|
+
* → `teamsList`) — the stable identifier a generator names methods from.
|
|
879
|
+
*/
|
|
880
|
+
readonly operationId?: string;
|
|
881
|
+
/**
|
|
882
|
+
* Whether the operation documents a bearer requirement. Default: `'required'`
|
|
883
|
+
* unless the descriptor declares `openAccess` — then `'optional'` (the key
|
|
884
|
+
* strategy authenticates a bearer when one is sent, and an anonymous caller
|
|
885
|
+
* is allowed through), which Swagger UI and generated clients read as
|
|
886
|
+
* "send the token when you have one". `false` documents no scheme at all.
|
|
887
|
+
*/
|
|
888
|
+
readonly security?: 'required' | 'optional' | false;
|
|
889
|
+
/** A request/response example for the spec — the same semantics as a
|
|
890
|
+
* `defineRestRoute` example. */
|
|
891
|
+
readonly example?: {
|
|
892
|
+
readonly request?: unknown;
|
|
893
|
+
readonly response?: unknown;
|
|
894
|
+
};
|
|
895
|
+
/**
|
|
896
|
+
* This projection's wire format, overriding the app's `publicApi` profile
|
|
897
|
+
* (`app.config.ts`) for the one endpoint that must differ — every field
|
|
898
|
+
* optional, the rest inherited. See `PublicApiProfileConfig`.
|
|
899
|
+
*/
|
|
900
|
+
readonly format?: PublicApiProfileConfig;
|
|
728
901
|
}
|
|
729
902
|
|
|
730
903
|
/**
|
|
@@ -861,11 +1034,22 @@ export declare const requireAuthenticated: () => RestGuard;
|
|
|
861
1034
|
*/
|
|
862
1035
|
export declare const requireScope: (scope: string) => RestGuard;
|
|
863
1036
|
|
|
1037
|
+
/**
|
|
1038
|
+
* The format one projection speaks: the app profile, then its building-block
|
|
1039
|
+
* overrides, then the descriptor's own `format`. A named profile is a
|
|
1040
|
+
* `RestFormat` with a name; an object is taken as-is and its fields overridden
|
|
1041
|
+
* the same way.
|
|
1042
|
+
*/
|
|
1043
|
+
export declare const resolveRestFormat: (config: PublicApiProfileConfig | undefined, override?: Partial<PublicApiProfileConfig> | undefined) => RestFormat;
|
|
1044
|
+
|
|
864
1045
|
export declare const response: <O>(body: O, options?: {
|
|
865
1046
|
readonly status?: number;
|
|
866
1047
|
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
1048
|
+
readonly contentType?: string;
|
|
867
1049
|
}) => RestResponse<O>;
|
|
868
1050
|
|
|
1051
|
+
export declare const REST_PROFILE_NAMES: ReadonlyArray<PublicApiProfileName>;
|
|
1052
|
+
|
|
869
1053
|
/** A BINARY streaming response (download/export). The serve layer pipes the
|
|
870
1054
|
* ReadableStream without buffering — a body larger than the heap is fine.
|
|
871
1055
|
* The LAZY thunk form defers opening the source until the response streams. */
|
|
@@ -881,8 +1065,85 @@ export declare interface RestByteResponse {
|
|
|
881
1065
|
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
882
1066
|
}
|
|
883
1067
|
|
|
1068
|
+
export declare type RestDates = 'iso' | 'epoch-ms';
|
|
1069
|
+
|
|
1070
|
+
export declare type RestEnvelope = 'none' | 'data' | 'jsonapi';
|
|
1071
|
+
|
|
884
1072
|
export declare const restError: (status: number, body: unknown, headers?: Readonly<Record<string, string | ReadonlyArray<string>>>) => RestRouteError;
|
|
885
1073
|
|
|
1074
|
+
export declare type RestErrors = 'problem-details' | 'tagged';
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* The wire format of one `publicApi` surface. `'standard'` and `'jsonapi'` are
|
|
1078
|
+
* named instances of this; an app's own object is the custom case.
|
|
1079
|
+
*/
|
|
1080
|
+
export declare interface RestFormat {
|
|
1081
|
+
readonly name: string;
|
|
1082
|
+
/** `timestampMs` fields: RFC 3339 strings, or the epoch milliseconds the rpc wire carries. */
|
|
1083
|
+
readonly dates: RestDates;
|
|
1084
|
+
/** Failures: RFC 9457 `application/problem+json`, or the tagged object rpc carries. */
|
|
1085
|
+
readonly errors: RestErrors;
|
|
1086
|
+
/** Success bodies: the bare value, `{ data }`, or the JSON:API document. */
|
|
1087
|
+
readonly envelope: RestEnvelope;
|
|
1088
|
+
/** A cursor the handler returns in `cursorField`: as a `Link: <…>; rel="next"`
|
|
1089
|
+
* header (and `next` beside `data`), or left where the handler put it. */
|
|
1090
|
+
readonly pagination: RestPagination;
|
|
1091
|
+
/** The cursor field a paginated output carries. Default `nextCursor`. */
|
|
1092
|
+
readonly cursorField: string;
|
|
1093
|
+
/** The query parameter a cursor travels back in. Default `cursor`. */
|
|
1094
|
+
readonly cursorParam: string;
|
|
1095
|
+
/** Property names on the wire. */
|
|
1096
|
+
readonly naming: RestNaming;
|
|
1097
|
+
/** Weak `ETag` on GET answers, `If-None-Match` → 304. */
|
|
1098
|
+
readonly etag: boolean;
|
|
1099
|
+
/** Custom success encoding — receives the value already in wire form (dates,
|
|
1100
|
+
* naming applied). Replaces the envelope/pagination steps. */
|
|
1101
|
+
readonly encodeSuccess?: (value: unknown, ctx: RestFormatContext) => RestFormatResponse;
|
|
1102
|
+
/** Custom error encoding. Replaces `errors`. */
|
|
1103
|
+
readonly encodeError?: (error: RestFormatError, ctx: RestFormatContext) => RestFormatResponse;
|
|
1104
|
+
/** A hook over the OpenAPI operation the plugin emits for a projection. */
|
|
1105
|
+
readonly describe?: (operation: Record<string, unknown>, ctx: {
|
|
1106
|
+
readonly tag: string;
|
|
1107
|
+
readonly method: string;
|
|
1108
|
+
readonly path: string;
|
|
1109
|
+
}) => Record<string, unknown>;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/** What a format sees when it encodes one response. */
|
|
1113
|
+
export declare interface RestFormatContext {
|
|
1114
|
+
/** The rpc tag, e.g. `teams.list`. */
|
|
1115
|
+
readonly tag: string;
|
|
1116
|
+
readonly method: string;
|
|
1117
|
+
/** The request path, for `Link` headers. */
|
|
1118
|
+
readonly path: string;
|
|
1119
|
+
/** The request's query string (without `?`). */
|
|
1120
|
+
readonly query: string;
|
|
1121
|
+
/** The first `source` table of the procedure, when it declares one — the
|
|
1122
|
+
* JSON:API resource `type`. */
|
|
1123
|
+
readonly source: string | undefined;
|
|
1124
|
+
readonly status: number;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/** A failure the desugar or the projection is about to answer with. */
|
|
1128
|
+
export declare interface RestFormatError {
|
|
1129
|
+
readonly status: number;
|
|
1130
|
+
/** The typed error's `_tag` (`AccessDeniedError`), or a stable name for a
|
|
1131
|
+
* transport failure (`InvalidRequest`, `Unauthorized`, `Forbidden`, …). */
|
|
1132
|
+
readonly tag: string;
|
|
1133
|
+
/** A human-readable line. */
|
|
1134
|
+
readonly message: string;
|
|
1135
|
+
/** The error's own fields (a typed error), without `_tag`. */
|
|
1136
|
+
readonly fields: Readonly<Record<string, unknown>>;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/** A response a format produced: body (already wire-shaped), headers, and the
|
|
1140
|
+
* media type when it is not `application/json`. */
|
|
1141
|
+
export declare interface RestFormatResponse {
|
|
1142
|
+
readonly body: unknown;
|
|
1143
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
1144
|
+
readonly contentType?: string;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
886
1147
|
export declare type RestGuard = (ctx: RestRouteContext) => RestGuardRejection | undefined | Promise<RestGuardRejection | undefined>;
|
|
887
1148
|
|
|
888
1149
|
/**
|
|
@@ -897,6 +1158,10 @@ export declare interface RestGuardRejection {
|
|
|
897
1158
|
|
|
898
1159
|
export declare type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
899
1160
|
|
|
1161
|
+
export declare type RestNaming = 'camelCase' | 'snake_case';
|
|
1162
|
+
|
|
1163
|
+
export declare type RestPagination = 'link-header' | 'body-cursor';
|
|
1164
|
+
|
|
900
1165
|
/** A schema-encoded REST body with an application-selected HTTP status and
|
|
901
1166
|
* response headers. The marker keeps ordinary domain objects unambiguous.
|
|
902
1167
|
* `output` still validates/encodes `body`; use `Schema.Void` with a 204 when
|
|
@@ -906,6 +1171,8 @@ export declare interface RestResponse<O> {
|
|
|
906
1171
|
readonly status: number;
|
|
907
1172
|
readonly body: O;
|
|
908
1173
|
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
1174
|
+
/** A media type other than `application/json` (a JSON:API document). */
|
|
1175
|
+
readonly contentType?: string;
|
|
909
1176
|
}
|
|
910
1177
|
|
|
911
1178
|
/**
|
|
@@ -953,6 +1220,17 @@ export declare interface RestRouteDescriptor<I, O> {
|
|
|
953
1220
|
readonly summary?: string;
|
|
954
1221
|
readonly description?: string;
|
|
955
1222
|
readonly example?: RestRouteExample;
|
|
1223
|
+
/** The spec's `operationId` — set by a `publicApi` projection from its tag. */
|
|
1224
|
+
readonly operationId?: string;
|
|
1225
|
+
/** The spec's tag group(s) — set by a projection from its tag prefix. */
|
|
1226
|
+
readonly tags?: ReadonlyArray<string>;
|
|
1227
|
+
/**
|
|
1228
|
+
* Present on a `publicApi` projection: what the route projects, in the wire
|
|
1229
|
+
* form its profile speaks. The OpenAPI generator documents parameters,
|
|
1230
|
+
* bodies, responses and security from it; the desugar answers every refusal
|
|
1231
|
+
* through its format.
|
|
1232
|
+
*/
|
|
1233
|
+
readonly projection?: RestRouteProjection;
|
|
956
1234
|
/** Replacement hint. Sets a `Deprecation: true` response header. */
|
|
957
1235
|
readonly deprecated?: string;
|
|
958
1236
|
/** ISO date. Sets a `Sunset:` header; past the date the route returns
|
|
@@ -1004,7 +1282,9 @@ export declare class RestRouteError extends Error {
|
|
|
1004
1282
|
readonly status: number;
|
|
1005
1283
|
readonly body: unknown;
|
|
1006
1284
|
readonly headers: Readonly<Record<string, string | ReadonlyArray<string>>> | undefined;
|
|
1007
|
-
|
|
1285
|
+
/** A media type other than `application/json` (`application/problem+json`). */
|
|
1286
|
+
readonly contentType: string | undefined;
|
|
1287
|
+
constructor(status: number, body: unknown, headers?: Readonly<Record<string, string | ReadonlyArray<string>>>, contentType?: string);
|
|
1008
1288
|
}
|
|
1009
1289
|
|
|
1010
1290
|
export declare interface RestRouteExample {
|
|
@@ -1012,6 +1292,36 @@ export declare interface RestRouteExample {
|
|
|
1012
1292
|
readonly response?: unknown;
|
|
1013
1293
|
}
|
|
1014
1294
|
|
|
1295
|
+
/**
|
|
1296
|
+
* What a `publicApi` projection is, for the two readers that need more than
|
|
1297
|
+
* the route: the OpenAPI generator and the desugar's refusals. `input` and
|
|
1298
|
+
* `output` are the WIRE schemas — the descriptor's, rewritten by the profile
|
|
1299
|
+
* (`restViewSchema`): a `timestampMs` is a `date-time` string under
|
|
1300
|
+
* `dates: 'iso'`, keys are snake_case under that naming. `input` carries the
|
|
1301
|
+
* path parameters as fields; `pathParams` says which.
|
|
1302
|
+
*/
|
|
1303
|
+
export declare interface RestRouteProjection {
|
|
1304
|
+
readonly tag: string;
|
|
1305
|
+
readonly kind: 'query' | 'mutation' | 'action';
|
|
1306
|
+
readonly version: string;
|
|
1307
|
+
readonly security: 'required' | 'optional' | 'none';
|
|
1308
|
+
readonly input: Schema.Schema.All;
|
|
1309
|
+
/** The wire names of the input fields bound from the path. */
|
|
1310
|
+
readonly pathParams: ReadonlyArray<string>;
|
|
1311
|
+
readonly output: Schema.Schema.All;
|
|
1312
|
+
/** The first `source` table, when the procedure declares one. */
|
|
1313
|
+
readonly source: string | undefined;
|
|
1314
|
+
/** Every failure the operation answers with a typed body, and its status:
|
|
1315
|
+
* the descriptor's `errorStatus` entries, `422` for the union's other tags,
|
|
1316
|
+
* and each plugin error that names a REST status. */
|
|
1317
|
+
readonly errorResponses: ReadonlyArray<{
|
|
1318
|
+
readonly status: number;
|
|
1319
|
+
readonly tag: string;
|
|
1320
|
+
readonly schema: Schema.Schema.All;
|
|
1321
|
+
}>;
|
|
1322
|
+
readonly format: RestFormat;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1015
1325
|
/**
|
|
1016
1326
|
* Desugar a list of REST descriptors into `PluginHttpRoute[]` ready to
|
|
1017
1327
|
* spread into a plugin/app's `httpRoutes`. `bindings` is supplied by the
|
|
@@ -1059,6 +1369,32 @@ export declare interface RestStreamResponse {
|
|
|
1059
1369
|
readonly stream: PluginHttpRouteStream;
|
|
1060
1370
|
}
|
|
1061
1371
|
|
|
1372
|
+
/**
|
|
1373
|
+
* Rewrite a descriptor's schema into the shape the profile puts on the wire:
|
|
1374
|
+
* `timestampMs` (`DateFromNumber`) becomes an RFC 3339 string under
|
|
1375
|
+
* `dates: 'iso'`, and every struct's property names become `snake_case` under
|
|
1376
|
+
* `naming: 'snake_case'` — as a transformation, so decoding a wire value yields
|
|
1377
|
+
* the descriptor's own typed value and encoding the typed value yields the
|
|
1378
|
+
* wire form. `JSONSchema.make` of the result documents the wire (a
|
|
1379
|
+
* `format: date-time` string, the snake keys). Classes and other opaque
|
|
1380
|
+
* transformations pass through unchanged; a struct inside one keeps its keys.
|
|
1381
|
+
*/
|
|
1382
|
+
export declare const restViewSchema: (schema: Schema.Schema.All, format: RestFormat) => Schema.Schema.All;
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* Whether `path` is one the route declared. The router matches case-folded and
|
|
1386
|
+
* ignores a trailing slash; the dispatcher asks this, byte for byte: a literal
|
|
1387
|
+
* segment must be equal, a `:name` segment takes one non-empty segment, and
|
|
1388
|
+
* beyond the declared segments a `'prefix'` route (the default) owns any
|
|
1389
|
+
* sub-path while an `'exact'` route owns nothing. A trailing slash alone is
|
|
1390
|
+
* not a sub-path — `/metrics/` is nobody's, whatever the mode. An explicit
|
|
1391
|
+
* `/*` tail is a prefix by itself.
|
|
1392
|
+
*/
|
|
1393
|
+
export declare const routeAcceptsPath: (route: {
|
|
1394
|
+
readonly path: string;
|
|
1395
|
+
readonly match?: "prefix" | "exact" | undefined;
|
|
1396
|
+
}, path: string) => boolean;
|
|
1397
|
+
|
|
1062
1398
|
/**
|
|
1063
1399
|
* Build a streaming (Server-Sent Events) handler result. `subscribe` gets an
|
|
1064
1400
|
* `emit` for one already-framed SSE chunk and MUST return an unsubscribe, which
|
|
@@ -1177,6 +1513,8 @@ declare type TargetRelations = Readonly<Record<string, TargetRelation>>;
|
|
|
1177
1513
|
|
|
1178
1514
|
declare type TargetSpec<Input = unknown, Row = unknown> = InsertTarget<Input, Row> | UpdateTarget<Input, Row> | DeleteTarget<Input>;
|
|
1179
1515
|
|
|
1516
|
+
export declare const toSnakeCase: (key: string) => string;
|
|
1517
|
+
|
|
1180
1518
|
declare interface UpdateTarget<Input = unknown, Row = unknown, Item = Record<string, unknown>> extends NestedTargetFields<Input> {
|
|
1181
1519
|
readonly table: string;
|
|
1182
1520
|
readonly op: 'update';
|