@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/rest.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AST from 'effect/SchemaAST';
|
|
1
2
|
import { DataStore } from '@voltro/database';
|
|
2
3
|
import { Schema } from 'effect';
|
|
3
4
|
|
|
@@ -104,12 +105,28 @@ export declare const bytes: (stream: ReadableStream<Uint8Array> | (() => Readabl
|
|
|
104
105
|
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
105
106
|
}) => RestByteResponse;
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Coerce a decoded query-string object toward the ENCODED shape of `input`.
|
|
110
|
+
* Field by field for a struct; the object is returned unchanged for any other
|
|
111
|
+
* shape (the schema then decides). Unknown keys pass through untouched.
|
|
112
|
+
*/
|
|
113
|
+
export declare const coerceQueryObject: (query: unknown, input: Schema.Schema.All) => unknown;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Coerce ONE query-string value toward what `ast` (an ENCODED shape) expects.
|
|
117
|
+
* Only a value that reads unambiguously as the target is converted; anything
|
|
118
|
+
* else is left for the schema to refuse with its own message. Refinements are
|
|
119
|
+
* looked through to their base; a union tries each arm and takes the first
|
|
120
|
+
* that changed the value.
|
|
121
|
+
*/
|
|
122
|
+
export declare const coerceQueryValue: (value: unknown, ast: AST.AST) => unknown;
|
|
123
|
+
|
|
107
124
|
/**
|
|
108
125
|
* Project every descriptor carrying a `publicApi` annotation into a REST
|
|
109
126
|
* route, in stable (path) order. The boot layer feeds these straight into
|
|
110
127
|
* `restRoutesToHttpRoutes` alongside any hand-authored `restRoutes`.
|
|
111
128
|
*/
|
|
112
|
-
export declare const collectPublicApiRoutes: (entries: ReadonlyArray<PublicApiEntry
|
|
129
|
+
export declare const collectPublicApiRoutes: (entries: ReadonlyArray<PublicApiEntry>, options?: PublicApiProjectionOptions) => ReadonlyArray<RestRouteDescriptor<Record<string, unknown>, unknown>>;
|
|
113
130
|
|
|
114
131
|
/**
|
|
115
132
|
* What a descriptor CARRIES: the author's guards, or the erased form of their
|
|
@@ -123,6 +140,9 @@ export declare const collectPublicApiRoutes: (entries: ReadonlyArray<PublicApiEn
|
|
|
123
140
|
*/
|
|
124
141
|
declare type DeclaredAccess<Input = unknown> = ReadonlyArray<AnyGuardSpec<Input> | OpenAccessSpec>;
|
|
125
142
|
|
|
143
|
+
/** The `_tag` literals a descriptor's `error:` union declares (empty for none). */
|
|
144
|
+
export declare const declaredErrorTags: (error: Schema.Schema.All | undefined) => ReadonlySet<string>;
|
|
145
|
+
|
|
126
146
|
/**
|
|
127
147
|
* Identity helper that fixes the descriptor's types (like `defineAction` /
|
|
128
148
|
* `definePlugin`). Returns its argument unchanged at runtime; exists for
|
|
@@ -162,6 +182,21 @@ export declare const derivePublicPath: (tag: string, spec: PublicApiSpec) => str
|
|
|
162
182
|
*/
|
|
163
183
|
export declare const dispatchSharedPath: (group: ReadonlyArray<PluginHttpRoute>, req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
|
|
164
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
|
+
|
|
165
200
|
/** Normalize the `exposeAsTool` shorthand. `true` is only valid when the
|
|
166
201
|
* descriptor carries a top-level `description`; callers pass that in. */
|
|
167
202
|
declare type ExposeAsTool = boolean | ExposeAsToolSpec;
|
|
@@ -286,6 +321,8 @@ export declare const isRestResponse: (value: unknown) => value is RestResponse<u
|
|
|
286
321
|
|
|
287
322
|
export declare const isRestStreamResponse: (v: unknown) => v is RestStreamResponse;
|
|
288
323
|
|
|
324
|
+
export declare const JSONAPI_MEDIA_TYPE = "application/vnd.api+json; charset=utf-8";
|
|
325
|
+
|
|
289
326
|
/** Extract `:name` path params by aligning the route PATTERN with the request
|
|
290
327
|
* path segment-by-segment (`/projects/:project/deploy` + `/projects/acme/deploy`
|
|
291
328
|
* → `{ project: 'acme' }`). `PluginHttpRouteRequest` carries no matched params,
|
|
@@ -307,6 +344,8 @@ declare interface MutationProcedureDescriptor<Name extends string, Input extends
|
|
|
307
344
|
* re-executes on a new server-owned WebSocket identity and rejects HTTP RPC.
|
|
308
345
|
* This does not grant access or restore application membership by itself. */
|
|
309
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;
|
|
310
349
|
/** Declarative target(s) — drives auto-optimistic on the client AND
|
|
311
350
|
* surfaces which tables this mutation touches (debug, future
|
|
312
351
|
* query-invalidation analytics). Mutations without a target run
|
|
@@ -355,6 +394,14 @@ declare interface MutationProcedureDescriptor<Name extends string, Input extends
|
|
|
355
394
|
readonly internal: boolean | undefined;
|
|
356
395
|
}
|
|
357
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
|
+
|
|
358
405
|
declare interface NestedTargetFields<Input = unknown> {
|
|
359
406
|
/** Dot-path to the nested array within the query VALUE to patch (e.g.
|
|
360
407
|
* `'snapshot.projects'`). Absent → patch the top-level row array (default). */
|
|
@@ -370,6 +417,15 @@ declare interface NestedTargetFields<Input = unknown> {
|
|
|
370
417
|
readonly match?: ((value: unknown, input: Input) => boolean) | undefined;
|
|
371
418
|
}
|
|
372
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
|
+
|
|
373
429
|
/**
|
|
374
430
|
* The runtime-erased form of a procedure's `openAccess:` — a DECLARED decision
|
|
375
431
|
* that this procedure needs no authorization check, and the reason.
|
|
@@ -391,6 +447,50 @@ declare interface OpenAccessSpec {
|
|
|
391
447
|
readonly open: string;
|
|
392
448
|
}
|
|
393
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
|
+
|
|
394
494
|
/** A public raw-HTTP route a plugin serves on the framework listener. */
|
|
395
495
|
declare interface PluginHttpRoute {
|
|
396
496
|
/** HTTP method, or `'*'` for any (the handler decides). PATCH/HEAD/OPTIONS
|
|
@@ -398,9 +498,22 @@ declare interface PluginHttpRoute {
|
|
|
398
498
|
* this union lacked PATCH; that reason is gone (the `'*'` mount remains
|
|
399
499
|
* for its other job: one dispatcher per shared path + a precise 405). */
|
|
400
500
|
readonly method: '*' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
401
|
-
/** Absolute path
|
|
402
|
-
*
|
|
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`. */
|
|
403
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';
|
|
404
517
|
readonly handle: (req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
|
|
405
518
|
/** Per-route body cap override (bytes) — wins over the listener's shared
|
|
406
519
|
* `maxBodyBytes`. NOTE: routes SHARING a path share one body read, so the
|
|
@@ -573,6 +686,13 @@ declare interface PluginHttpRouteResult {
|
|
|
573
686
|
/** Stream the response (SSE) instead of sending `body`. See
|
|
574
687
|
* {@link PluginHttpRouteStream}. */
|
|
575
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;
|
|
576
696
|
/** Stream a BINARY response (a download, an export) instead of sending
|
|
577
697
|
* `body` — see {@link PluginHttpRouteByteStream}. Never buffered by the
|
|
578
698
|
* serve layer; never compressed (flush timing + Content-Length are the
|
|
@@ -629,6 +749,28 @@ declare interface PolicyGuardSpec<Input = unknown> {
|
|
|
629
749
|
readonly resource: (input: Input) => string | undefined;
|
|
630
750
|
}
|
|
631
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
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Translate a handler's failure into the response the projection promised:
|
|
764
|
+
* a DECLARED typed error becomes a `RestRouteError` with the tag, the fields
|
|
765
|
+
* and the status `errorStatus` assigns (`422` by default, the status the
|
|
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.
|
|
771
|
+
*/
|
|
772
|
+
export declare const projectedFailure: (err: unknown, descriptor: PublicApiDescriptor, spec: PublicApiSpec, pluginErrors?: ReadonlyArray<PluginErrorSchema>) => unknown;
|
|
773
|
+
|
|
632
774
|
/** A descriptor kind that can be projected to REST (streams are not, in v1). */
|
|
633
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>;
|
|
634
776
|
|
|
@@ -647,16 +789,47 @@ export declare interface PublicApiEntry {
|
|
|
647
789
|
* handler through the SAME AuthMiddleware/RBAC/tenant/audit as the rpc path. */
|
|
648
790
|
export declare type PublicApiInvoke = (input: unknown, ctx: RestRouteContext) => Promise<unknown> | unknown;
|
|
649
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
|
+
|
|
650
817
|
/**
|
|
651
|
-
* Synthesize the public REST route for ONE descriptor.
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
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.
|
|
658
831
|
*/
|
|
659
|
-
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>;
|
|
660
833
|
|
|
661
834
|
declare interface PublicApiSpec {
|
|
662
835
|
/** Derived by kind when omitted: query→GET, mutation/action→POST. */
|
|
@@ -683,6 +856,48 @@ declare interface PublicApiSpec {
|
|
|
683
856
|
/** Streaming queries over request/response: 'snapshot' (default — first
|
|
684
857
|
* snapshot, like POST /rpc) | 'sse' (Server-Sent Events of snapshot+deltas). */
|
|
685
858
|
readonly stream?: 'snapshot' | 'sse';
|
|
859
|
+
/**
|
|
860
|
+
* HTTP status per DECLARED typed error tag, for the projection. A typed
|
|
861
|
+
* error the handler fails with — one of the descriptor's `error:` union —
|
|
862
|
+
* answers with its tag and fields as the body, and this status; a tag not
|
|
863
|
+
* listed here answers `422`, the status the OpenAPI projection documents
|
|
864
|
+
* for a typed error. The framework's own `ScopeError` is mapped
|
|
865
|
+
* before this (`403`, or `401` for an anonymous caller) and needs no entry.
|
|
866
|
+
*
|
|
867
|
+
* publicApi: { errorStatus: { AccessDeniedError: 403, NotFoundError: 404 } }
|
|
868
|
+
*/
|
|
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;
|
|
686
901
|
}
|
|
687
902
|
|
|
688
903
|
/**
|
|
@@ -819,11 +1034,22 @@ export declare const requireAuthenticated: () => RestGuard;
|
|
|
819
1034
|
*/
|
|
820
1035
|
export declare const requireScope: (scope: string) => RestGuard;
|
|
821
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
|
+
|
|
822
1045
|
export declare const response: <O>(body: O, options?: {
|
|
823
1046
|
readonly status?: number;
|
|
824
1047
|
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
1048
|
+
readonly contentType?: string;
|
|
825
1049
|
}) => RestResponse<O>;
|
|
826
1050
|
|
|
1051
|
+
export declare const REST_PROFILE_NAMES: ReadonlyArray<PublicApiProfileName>;
|
|
1052
|
+
|
|
827
1053
|
/** A BINARY streaming response (download/export). The serve layer pipes the
|
|
828
1054
|
* ReadableStream without buffering — a body larger than the heap is fine.
|
|
829
1055
|
* The LAZY thunk form defers opening the source until the response streams. */
|
|
@@ -839,8 +1065,85 @@ export declare interface RestByteResponse {
|
|
|
839
1065
|
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
840
1066
|
}
|
|
841
1067
|
|
|
1068
|
+
export declare type RestDates = 'iso' | 'epoch-ms';
|
|
1069
|
+
|
|
1070
|
+
export declare type RestEnvelope = 'none' | 'data' | 'jsonapi';
|
|
1071
|
+
|
|
842
1072
|
export declare const restError: (status: number, body: unknown, headers?: Readonly<Record<string, string | ReadonlyArray<string>>>) => RestRouteError;
|
|
843
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
|
+
|
|
844
1147
|
export declare type RestGuard = (ctx: RestRouteContext) => RestGuardRejection | undefined | Promise<RestGuardRejection | undefined>;
|
|
845
1148
|
|
|
846
1149
|
/**
|
|
@@ -855,6 +1158,10 @@ export declare interface RestGuardRejection {
|
|
|
855
1158
|
|
|
856
1159
|
export declare type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
857
1160
|
|
|
1161
|
+
export declare type RestNaming = 'camelCase' | 'snake_case';
|
|
1162
|
+
|
|
1163
|
+
export declare type RestPagination = 'link-header' | 'body-cursor';
|
|
1164
|
+
|
|
858
1165
|
/** A schema-encoded REST body with an application-selected HTTP status and
|
|
859
1166
|
* response headers. The marker keeps ordinary domain objects unambiguous.
|
|
860
1167
|
* `output` still validates/encodes `body`; use `Schema.Void` with a 204 when
|
|
@@ -864,6 +1171,8 @@ export declare interface RestResponse<O> {
|
|
|
864
1171
|
readonly status: number;
|
|
865
1172
|
readonly body: O;
|
|
866
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;
|
|
867
1176
|
}
|
|
868
1177
|
|
|
869
1178
|
/**
|
|
@@ -911,6 +1220,17 @@ export declare interface RestRouteDescriptor<I, O> {
|
|
|
911
1220
|
readonly summary?: string;
|
|
912
1221
|
readonly description?: string;
|
|
913
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;
|
|
914
1234
|
/** Replacement hint. Sets a `Deprecation: true` response header. */
|
|
915
1235
|
readonly deprecated?: string;
|
|
916
1236
|
/** ISO date. Sets a `Sunset:` header; past the date the route returns
|
|
@@ -962,7 +1282,9 @@ export declare class RestRouteError extends Error {
|
|
|
962
1282
|
readonly status: number;
|
|
963
1283
|
readonly body: unknown;
|
|
964
1284
|
readonly headers: Readonly<Record<string, string | ReadonlyArray<string>>> | undefined;
|
|
965
|
-
|
|
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);
|
|
966
1288
|
}
|
|
967
1289
|
|
|
968
1290
|
export declare interface RestRouteExample {
|
|
@@ -970,6 +1292,36 @@ export declare interface RestRouteExample {
|
|
|
970
1292
|
readonly response?: unknown;
|
|
971
1293
|
}
|
|
972
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
|
+
|
|
973
1325
|
/**
|
|
974
1326
|
* Desugar a list of REST descriptors into `PluginHttpRoute[]` ready to
|
|
975
1327
|
* spread into a plugin/app's `httpRoutes`. `bindings` is supplied by the
|
|
@@ -1017,6 +1369,32 @@ export declare interface RestStreamResponse {
|
|
|
1017
1369
|
readonly stream: PluginHttpRouteStream;
|
|
1018
1370
|
}
|
|
1019
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
|
+
|
|
1020
1398
|
/**
|
|
1021
1399
|
* Build a streaming (Server-Sent Events) handler result. `subscribe` gets an
|
|
1022
1400
|
* `emit` for one already-framed SSE chunk and MUST return an unsubscribe, which
|
|
@@ -1135,6 +1513,8 @@ declare type TargetRelations = Readonly<Record<string, TargetRelation>>;
|
|
|
1135
1513
|
|
|
1136
1514
|
declare type TargetSpec<Input = unknown, Row = unknown> = InsertTarget<Input, Row> | UpdateTarget<Input, Row> | DeleteTarget<Input>;
|
|
1137
1515
|
|
|
1516
|
+
export declare const toSnakeCase: (key: string) => string;
|
|
1517
|
+
|
|
1138
1518
|
declare interface UpdateTarget<Input = unknown, Row = unknown, Item = Record<string, unknown>> extends NestedTargetFields<Input> {
|
|
1139
1519
|
readonly table: string;
|
|
1140
1520
|
readonly op: 'update';
|