@voltro/protocol 0.64.0 → 0.66.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 +161 -0
- package/dist/index.d.ts +109 -7
- package/dist/index.js +492 -478
- package/dist/rest.d.ts +86 -3
- package/dist/rest.js +135 -87
- package/dist/{scopes-D9t3tYc7.js → scopes-C86BDyNJ.js} +33 -19
- package/dist/{serverErrorBus-QQgyq6fV.js → serverErrorBus-BTnTpMqj.js} +17 -8
- package/dist/session.js +6 -6
- package/package.json +3 -3
package/dist/rest.d.ts
CHANGED
|
@@ -71,13 +71,37 @@ declare interface AnyApprovalPolicy {
|
|
|
71
71
|
readonly reason?: string;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/** A guard is
|
|
75
|
-
declare type AnyGuardSpec<Input = unknown> = GuardSpec<Input> | PolicyGuardSpec<Input
|
|
74
|
+
/** A guard is a scope check, a relationship check, or an identity check. */
|
|
75
|
+
declare type AnyGuardSpec<Input = unknown> = GuardSpec<Input> | PolicyGuardSpec<Input> | AuthenticatedGuardSpec;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* "The caller must have an identity" — and nothing narrower.
|
|
79
|
+
*
|
|
80
|
+
* The third case the vocabulary was missing, and the gap was not theoretical.
|
|
81
|
+
* An app whose authority lives per-team in its executors holds NO raw scopes on
|
|
82
|
+
* the identity, so `{ scope }` refuses every real user; a relationship guard
|
|
83
|
+
* cannot help when the resource does not exist yet (the first message of a new
|
|
84
|
+
* thread); and `openAccess` would be a lie whenever the auth fallback is a
|
|
85
|
+
* deliberate anonymous subject. The one thing left to decide — is anybody
|
|
86
|
+
* there — had no spelling, so apps wrote a resource policy whose entire content
|
|
87
|
+
* was "you are logged in".
|
|
88
|
+
*
|
|
89
|
+
* `checkGuards` has always distinguished this internally: its return type is
|
|
90
|
+
* `ScopeError | Unauthenticated | null`. This makes the distinction sayable.
|
|
91
|
+
*
|
|
92
|
+
* It is a real DECISION, so it satisfies `security.defaultDeny` — unlike
|
|
93
|
+
* `openAccess`, it also enforces something.
|
|
94
|
+
*/
|
|
95
|
+
declare interface AuthenticatedGuardSpec {
|
|
96
|
+
readonly authenticated: true;
|
|
97
|
+
}
|
|
76
98
|
|
|
77
99
|
export declare const bytes: (stream: ReadableStream<Uint8Array> | (() => ReadableStream<Uint8Array>), options?: {
|
|
78
100
|
readonly contentType?: string;
|
|
79
101
|
readonly contentLength?: number;
|
|
80
102
|
readonly contentDisposition?: string;
|
|
103
|
+
readonly status?: number;
|
|
104
|
+
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
81
105
|
}) => RestByteResponse;
|
|
82
106
|
|
|
83
107
|
/**
|
|
@@ -258,6 +282,8 @@ declare interface InsertTarget<Input = unknown, Row = unknown, Item = Record<str
|
|
|
258
282
|
|
|
259
283
|
export declare const isRestByteResponse: (v: unknown) => v is RestByteResponse;
|
|
260
284
|
|
|
285
|
+
export declare const isRestResponse: (value: unknown) => value is RestResponse<unknown>;
|
|
286
|
+
|
|
261
287
|
export declare const isRestStreamResponse: (v: unknown) => v is RestStreamResponse;
|
|
262
288
|
|
|
263
289
|
/** Extract `:name` path params by aligning the route PATTERN with the request
|
|
@@ -267,12 +293,20 @@ export declare const isRestStreamResponse: (v: unknown) => v is RestStreamRespon
|
|
|
267
293
|
* empty and every `:param` route 400s on decode. */
|
|
268
294
|
export declare const matchPathParams: (pattern: string, path: string) => Record<string, string>;
|
|
269
295
|
|
|
296
|
+
/** Ordinary writes deduplicate across reconnects. A connection-side effect
|
|
297
|
+
* (for example joining a room) must execute once for EACH native connection. */
|
|
298
|
+
declare type MutationIdempotencyScope = 'subject' | 'connection';
|
|
299
|
+
|
|
270
300
|
declare interface MutationProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All> {
|
|
271
301
|
readonly kind: 'mutation';
|
|
272
302
|
readonly name: Name;
|
|
273
303
|
readonly input: Input;
|
|
274
304
|
readonly output: Output;
|
|
275
305
|
readonly error: Error;
|
|
306
|
+
/** Default 'subject': retries deduplicate across connections. 'connection'
|
|
307
|
+
* re-executes on a new server-owned WebSocket identity and rejects HTTP RPC.
|
|
308
|
+
* This does not grant access or restore application membership by itself. */
|
|
309
|
+
readonly idempotencyScope?: MutationIdempotencyScope | undefined;
|
|
276
310
|
/** Declarative target(s) — drives auto-optimistic on the client AND
|
|
277
311
|
* surfaces which tables this mutation touches (debug, future
|
|
278
312
|
* query-invalidation analytics). Mutations without a target run
|
|
@@ -771,6 +805,11 @@ declare interface QueryProcedureDescriptor<Name extends string, Input extends Sc
|
|
|
771
805
|
*/
|
|
772
806
|
export declare const requireAnyScope: (scopes: ReadonlyArray<string>) => RestGuard;
|
|
773
807
|
|
|
808
|
+
/** Require a non-anonymous subject on a REST route. Rejected credentials and
|
|
809
|
+
* absent credentials are both authentication failures (401), while scope
|
|
810
|
+
* guards remain authorization failures (403). */
|
|
811
|
+
export declare const requireAuthenticated: () => RestGuard;
|
|
812
|
+
|
|
774
813
|
/**
|
|
775
814
|
* Guard that requires the resolved subject to carry `scope`. Anonymous /
|
|
776
815
|
* scope-less subjects are rejected with `403`. The blanket admin scope
|
|
@@ -780,6 +819,11 @@ export declare const requireAnyScope: (scopes: ReadonlyArray<string>) => RestGua
|
|
|
780
819
|
*/
|
|
781
820
|
export declare const requireScope: (scope: string) => RestGuard;
|
|
782
821
|
|
|
822
|
+
export declare const response: <O>(body: O, options?: {
|
|
823
|
+
readonly status?: number;
|
|
824
|
+
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
825
|
+
}) => RestResponse<O>;
|
|
826
|
+
|
|
783
827
|
/** A BINARY streaming response (download/export). The serve layer pipes the
|
|
784
828
|
* ReadableStream without buffering — a body larger than the heap is fine.
|
|
785
829
|
* The LAZY thunk form defers opening the source until the response streams. */
|
|
@@ -791,8 +835,12 @@ export declare interface RestByteResponse {
|
|
|
791
835
|
readonly contentDisposition?: string;
|
|
792
836
|
};
|
|
793
837
|
readonly contentType?: string;
|
|
838
|
+
readonly status?: number;
|
|
839
|
+
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
794
840
|
}
|
|
795
841
|
|
|
842
|
+
export declare const restError: (status: number, body: unknown, headers?: Readonly<Record<string, string | ReadonlyArray<string>>>) => RestRouteError;
|
|
843
|
+
|
|
796
844
|
export declare type RestGuard = (ctx: RestRouteContext) => RestGuardRejection | undefined | Promise<RestGuardRejection | undefined>;
|
|
797
845
|
|
|
798
846
|
/**
|
|
@@ -807,6 +855,17 @@ export declare interface RestGuardRejection {
|
|
|
807
855
|
|
|
808
856
|
export declare type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
809
857
|
|
|
858
|
+
/** A schema-encoded REST body with an application-selected HTTP status and
|
|
859
|
+
* response headers. The marker keeps ordinary domain objects unambiguous.
|
|
860
|
+
* `output` still validates/encodes `body`; use `Schema.Void` with a 204 when
|
|
861
|
+
* the response intentionally has no representation. */
|
|
862
|
+
export declare interface RestResponse<O> {
|
|
863
|
+
readonly __voltroRestResponse: true;
|
|
864
|
+
readonly status: number;
|
|
865
|
+
readonly body: O;
|
|
866
|
+
readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
867
|
+
}
|
|
868
|
+
|
|
810
869
|
/**
|
|
811
870
|
* Per-call context handed to a REST route handler. Resolved by the serve
|
|
812
871
|
* pipeline: `subject` from the framework's auth resolver, `headers`
|
|
@@ -817,10 +876,22 @@ export declare type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | '
|
|
|
817
876
|
export declare interface RestRouteContext {
|
|
818
877
|
readonly subject: Subject;
|
|
819
878
|
readonly headers: Readonly<Record<string, string>>;
|
|
879
|
+
/** Request metadata and the byte-exact body as received by the HTTP
|
|
880
|
+
* transport. JSON routes normally use decoded `input.body`; `rawBody` is
|
|
881
|
+
* for signature verification, multipart parsing, and other protocols where
|
|
882
|
+
* parsing first would destroy information the handler must authenticate. */
|
|
883
|
+
readonly method: RestMethod;
|
|
884
|
+
readonly path: string;
|
|
885
|
+
readonly query: string;
|
|
886
|
+
readonly rawBody: Uint8Array;
|
|
820
887
|
/** Framework `DataStore`. Untyped in protocol (same discipline as
|
|
821
888
|
* `PluginHttpRoute` / `bindDataStore`); cast to the concrete store at
|
|
822
889
|
* the call site. `undefined` only in unit tests that don't inject one. */
|
|
823
890
|
readonly store: unknown;
|
|
891
|
+
/** Full framework context for this authenticated request. This keeps custom
|
|
892
|
+
* REST handlers on the same capability surface as query/mutation/action
|
|
893
|
+
* handlers without creating a protocol-to-runtime package dependency. */
|
|
894
|
+
readonly app?: unknown;
|
|
824
895
|
}
|
|
825
896
|
|
|
826
897
|
export declare interface RestRouteDescriptor<I, O> {
|
|
@@ -836,7 +907,7 @@ export declare interface RestRouteDescriptor<I, O> {
|
|
|
836
907
|
/** Output schema. The handler returns the decoded `Type`; the desugar
|
|
837
908
|
* encodes it to JSON for the `200` response. */
|
|
838
909
|
readonly output: Schema.Schema<O, any, never>;
|
|
839
|
-
readonly handler: (input: I, ctx: RestRouteContext) => Promise<O
|
|
910
|
+
readonly handler: (input: I, ctx: RestRouteContext) => Promise<O | RestResponse<O>> | O | RestResponse<O>;
|
|
840
911
|
readonly summary?: string;
|
|
841
912
|
readonly description?: string;
|
|
842
913
|
readonly example?: RestRouteExample;
|
|
@@ -884,6 +955,16 @@ export declare interface RestRouteDescriptor<I, O> {
|
|
|
884
955
|
readonly etag?: boolean;
|
|
885
956
|
}
|
|
886
957
|
|
|
958
|
+
/** Intentional non-2xx REST control flow with an application-defined JSON
|
|
959
|
+
* error contract. Unlike an arbitrary thrown object this is never reported as
|
|
960
|
+
* a server fault, and its body is not rewritten to `{ error: message }`. */
|
|
961
|
+
export declare class RestRouteError extends Error {
|
|
962
|
+
readonly status: number;
|
|
963
|
+
readonly body: unknown;
|
|
964
|
+
readonly headers: Readonly<Record<string, string | ReadonlyArray<string>>> | undefined;
|
|
965
|
+
constructor(status: number, body: unknown, headers?: Readonly<Record<string, string | ReadonlyArray<string>>>);
|
|
966
|
+
}
|
|
967
|
+
|
|
887
968
|
export declare interface RestRouteExample {
|
|
888
969
|
readonly request?: unknown;
|
|
889
970
|
readonly response?: unknown;
|
|
@@ -907,6 +988,8 @@ export declare interface RestServeBindings {
|
|
|
907
988
|
readonly resolveSubject?: (headers: Readonly<Record<string, string>>) => Subject | Promise<Subject>;
|
|
908
989
|
/** The bound framework DataStore. */
|
|
909
990
|
readonly store?: unknown;
|
|
991
|
+
/** Build the full per-request app context after authentication. */
|
|
992
|
+
readonly buildContext?: (subject: Subject, headers: Readonly<Record<string, string>>) => unknown | Promise<unknown>;
|
|
910
993
|
/**
|
|
911
994
|
* HTTP idempotency — when set, a mutating request (POST/PUT/PATCH/DELETE)
|
|
912
995
|
* carrying the `header` is deduplicated: the first call runs + caches its
|
package/dist/rest.js
CHANGED
|
@@ -1,39 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Effect as
|
|
1
|
+
import { F as e, P as t } from "./scopes-C86BDyNJ.js";
|
|
2
|
+
import { c as n, l as r, o as i, s as a, t as o } from "./serverErrorBus-BTnTpMqj.js";
|
|
3
|
+
import { Effect as s, Schema as c } from "effect";
|
|
4
4
|
//#region src/publicApi.ts
|
|
5
|
-
var
|
|
5
|
+
var l = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), u = (e, t) => t.path ?? `/${t.version ?? "v1"}/${e.replace(/\./g, "/")}`, d = (e, t, n) => {
|
|
6
6
|
let r = e.publicApi;
|
|
7
7
|
if (r === void 0) throw Error(`publicApiRoute: descriptor '${e.name}' has no publicApi annotation`);
|
|
8
|
-
let i =
|
|
9
|
-
r(
|
|
8
|
+
let i = l(e.kind, r), a = u(e.name, r), o = i === "GET" ? "query" : "body", s = c.Struct({ [o]: e.input }), d = (r.scopes ?? []).map((e) => m(e)), f = r.stream === "sse" && e.kind === "query" && n !== void 0, p = f ? (e, t) => x((r) => n(e[o], t, (e) => {
|
|
9
|
+
r(S(String(e._tag ?? "message"), e));
|
|
10
10
|
})) : (e, n) => t(e[o], n);
|
|
11
|
-
return
|
|
11
|
+
return b({
|
|
12
12
|
method: i,
|
|
13
13
|
path: a,
|
|
14
|
-
input:
|
|
14
|
+
input: s,
|
|
15
15
|
output: e.output,
|
|
16
|
-
handler:
|
|
17
|
-
...
|
|
16
|
+
handler: p,
|
|
17
|
+
...f ? { streaming: !0 } : {},
|
|
18
18
|
...d.length > 0 ? { guards: d } : {},
|
|
19
19
|
...r.summary === void 0 ? {} : { summary: r.summary },
|
|
20
20
|
...r.description === void 0 ? {} : { description: r.description },
|
|
21
21
|
...r.deprecated === void 0 ? {} : { deprecated: r.deprecated },
|
|
22
22
|
...r.sunset === void 0 ? {} : { sunset: r.sunset }
|
|
23
23
|
});
|
|
24
|
-
},
|
|
24
|
+
}, f = (e) => e.filter((e) => e.descriptor.publicApi !== void 0).map((e) => d(e.descriptor, e.invoke, e.subscribe)).sort((e, t) => e.path.localeCompare(t.path)), p = () => (e) => {
|
|
25
|
+
if (e.subject.type === "anonymous") return {
|
|
26
|
+
status: 401,
|
|
27
|
+
message: e.subject.credentialRejected ?? "Authentication required"
|
|
28
|
+
};
|
|
29
|
+
}, m = (e) => (t) => {
|
|
25
30
|
let n = t.subject.scopes;
|
|
26
31
|
if (!(n && (n.includes(e) || n.includes("admin:full")))) return {
|
|
27
32
|
status: 403,
|
|
28
33
|
message: `Missing required scope: ${e}`
|
|
29
34
|
};
|
|
30
|
-
},
|
|
35
|
+
}, h = (e) => (t) => {
|
|
31
36
|
let n = t.subject.scopes;
|
|
32
37
|
if (!(n && (n.includes("admin:full") || e.some((e) => n.includes(e))))) return {
|
|
33
38
|
status: 403,
|
|
34
39
|
message: `Missing required scope: one of ${e.join(", ")}`
|
|
35
40
|
};
|
|
36
|
-
},
|
|
41
|
+
}, g = (e, t = {}) => ({
|
|
42
|
+
__voltroRestResponse: !0,
|
|
43
|
+
status: t.status ?? 200,
|
|
44
|
+
body: e,
|
|
45
|
+
...t.headers === void 0 ? {} : { headers: t.headers }
|
|
46
|
+
}), _ = (e) => typeof e == "object" && !!e && e.__voltroRestResponse === !0, v = class extends Error {
|
|
47
|
+
status;
|
|
48
|
+
body;
|
|
49
|
+
headers;
|
|
50
|
+
constructor(e, t, n) {
|
|
51
|
+
super(`REST ${e}`), this.name = "RestRouteError", this.status = e, this.body = t, this.headers = n;
|
|
52
|
+
}
|
|
53
|
+
}, y = (e, t, n) => new v(e, t, n), b = (e) => {
|
|
37
54
|
if (e.version === void 0) return e;
|
|
38
55
|
if (!/^v\d+$/.test(e.version)) throw Error(`defineRestRoute(${e.method} ${e.path}): version "${e.version}" must be 'v' followed by digits — 'v1', 'v2', …`);
|
|
39
56
|
if ((/* @__PURE__ */ RegExp("^/v\\d+(/|$)")).test(e.path)) throw Error(`defineRestRoute(${e.method} ${e.path}): the path already starts with a version segment AND declares version: '${e.version}'. Pick one spelling — either bake the version into the path, or declare version: and keep the path bare ('/customers'). Both would mount '/${e.version}${e.path}', which is never what the author meant.`);
|
|
@@ -41,37 +58,39 @@ var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.
|
|
|
41
58
|
...e,
|
|
42
59
|
path: `/${e.version}${e.path}`
|
|
43
60
|
};
|
|
44
|
-
},
|
|
61
|
+
}, x = (e, t = {}) => ({
|
|
45
62
|
__voltroRestStream: !0,
|
|
46
63
|
stream: {
|
|
47
64
|
subscribe: e,
|
|
48
65
|
...t.keepAliveMs === void 0 ? {} : { keepAliveMs: t.keepAliveMs }
|
|
49
66
|
}
|
|
50
|
-
}),
|
|
67
|
+
}), S = (e, t) => `event: ${e}\n${(typeof t == "string" ? t : JSON.stringify(t)).split("\n").map((e) => `data: ${e}`).join("\n")}\n\n`, C = (e) => typeof e == "object" && !!e && e.__voltroRestStream === !0, w = (e, t = {}) => ({
|
|
51
68
|
__voltroRestBytes: !0,
|
|
52
69
|
byteStream: {
|
|
53
70
|
stream: e,
|
|
54
71
|
...t.contentLength === void 0 ? {} : { contentLength: t.contentLength },
|
|
55
72
|
...t.contentDisposition === void 0 ? {} : { contentDisposition: t.contentDisposition }
|
|
56
73
|
},
|
|
57
|
-
...t.contentType === void 0 ? {} : { contentType: t.contentType }
|
|
58
|
-
|
|
74
|
+
...t.contentType === void 0 ? {} : { contentType: t.contentType },
|
|
75
|
+
...t.status === void 0 ? {} : { status: t.status },
|
|
76
|
+
...t.headers === void 0 ? {} : { headers: t.headers }
|
|
77
|
+
}), T = (e) => typeof e == "object" && !!e && e.__voltroRestBytes === !0, E = async (e) => {
|
|
59
78
|
let t = await globalThis.crypto.subtle.digest("SHA-1", new TextEncoder().encode(e));
|
|
60
79
|
return [...new Uint8Array(t)].map((e) => e.toString(16).padStart(2, "0")).join("");
|
|
61
|
-
},
|
|
80
|
+
}, D = (e, t, n) => ({
|
|
62
81
|
status: e,
|
|
63
82
|
contentType: "application/json; charset=utf-8",
|
|
64
83
|
body: JSON.stringify(t),
|
|
65
84
|
...n ? { headers: n } : {}
|
|
66
|
-
}),
|
|
85
|
+
}), O = (e) => {
|
|
67
86
|
if (e.length === 0) return;
|
|
68
87
|
let t = new TextDecoder().decode(e);
|
|
69
88
|
if (t.trim() !== "") return JSON.parse(t);
|
|
70
|
-
},
|
|
89
|
+
}, k = (e) => {
|
|
71
90
|
let t = {};
|
|
72
91
|
for (let [n, r] of new URLSearchParams(e)) t[n] = r;
|
|
73
92
|
return t;
|
|
74
|
-
},
|
|
93
|
+
}, A = (e, t) => {
|
|
75
94
|
let n = e.split("/").filter((e) => e.length > 0), r = t.split("/").filter((e) => e.length > 0), i = {};
|
|
76
95
|
for (let e = 0; e < n.length; e++) {
|
|
77
96
|
let t = n[e];
|
|
@@ -85,124 +104,153 @@ var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.
|
|
|
85
104
|
}
|
|
86
105
|
}
|
|
87
106
|
return i;
|
|
88
|
-
},
|
|
89
|
-
query:
|
|
107
|
+
}, j = (e, t) => ({
|
|
108
|
+
query: k(e.query),
|
|
90
109
|
params: t,
|
|
91
|
-
body:
|
|
92
|
-
}),
|
|
110
|
+
body: O(e.rawBody)
|
|
111
|
+
}), M = /* @__PURE__ */ new Set([
|
|
93
112
|
"POST",
|
|
94
113
|
"PUT",
|
|
95
114
|
"PATCH",
|
|
96
115
|
"DELETE"
|
|
97
|
-
]),
|
|
98
|
-
let
|
|
99
|
-
return
|
|
116
|
+
]), N = (l, u) => {
|
|
117
|
+
let d = l.input ? c.decodeUnknown(l.input) : void 0, f = c.encode(l.output), p = {}, m = (e) => e.replace(/\/$/, "").split("/").length, h = m(l.path);
|
|
118
|
+
return l.deprecated !== void 0 && (p.Deprecation = "true"), l.sunset !== void 0 && (p.Sunset = l.sunset), {
|
|
100
119
|
method: "*",
|
|
101
|
-
path:
|
|
102
|
-
...
|
|
103
|
-
handle: async (
|
|
104
|
-
|
|
105
|
-
|
|
120
|
+
path: l.path,
|
|
121
|
+
...l.maxBodyBytes === void 0 ? {} : { maxBodyBytes: l.maxBodyBytes },
|
|
122
|
+
handle: async (c) => {
|
|
123
|
+
if (!l.path.endsWith("/*") && m(c.path) !== h) return D(404, { error: "Not Found" }, p);
|
|
124
|
+
let y = c.method.toUpperCase();
|
|
125
|
+
if (!(y === l.method || y === "HEAD" && l.method === "GET")) return D(405, {
|
|
106
126
|
error: "Method Not Allowed",
|
|
107
|
-
allow:
|
|
127
|
+
allow: l.method
|
|
108
128
|
}, {
|
|
109
|
-
...
|
|
110
|
-
Allow:
|
|
129
|
+
...p,
|
|
130
|
+
Allow: l.method
|
|
111
131
|
});
|
|
112
|
-
if (
|
|
113
|
-
let e = Date.parse(
|
|
114
|
-
if (!Number.isNaN(e) && Date.now() >= e) return
|
|
132
|
+
if (l.sunset !== void 0) {
|
|
133
|
+
let e = Date.parse(l.sunset);
|
|
134
|
+
if (!Number.isNaN(e) && Date.now() >= e) return D(410, {
|
|
115
135
|
error: "Gone",
|
|
116
|
-
...
|
|
117
|
-
...
|
|
118
|
-
},
|
|
136
|
+
...l.version === void 0 ? {} : { version: l.version },
|
|
137
|
+
...l.deprecated === void 0 ? {} : { replacement: l.deprecated }
|
|
138
|
+
}, p);
|
|
119
139
|
}
|
|
120
|
-
let
|
|
121
|
-
if (
|
|
122
|
-
let e =
|
|
140
|
+
let b;
|
|
141
|
+
if (d) {
|
|
142
|
+
let e = j(c, A(l.path, c.path)), t = await s.runPromise(d(e).pipe(s.map((e) => ({
|
|
123
143
|
ok: !0,
|
|
124
144
|
value: e
|
|
125
|
-
})),
|
|
145
|
+
})), s.catchAll((e) => s.succeed({
|
|
126
146
|
ok: !1,
|
|
127
147
|
cause: e
|
|
128
148
|
}))));
|
|
129
|
-
if (!t.ok) return
|
|
149
|
+
if (!t.ok) return D(400, {
|
|
130
150
|
error: "Invalid request",
|
|
131
151
|
detail: String(t.cause)
|
|
132
|
-
},
|
|
133
|
-
|
|
152
|
+
}, p);
|
|
153
|
+
b = t.value;
|
|
154
|
+
}
|
|
155
|
+
let x;
|
|
156
|
+
try {
|
|
157
|
+
x = u.resolveSubject ? await u.resolveSubject(c.headers) : e(c.headers["x-tenant"] ?? null);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
if (e instanceof t || typeof e == "object" && e && e._tag === "Unauthenticated") {
|
|
160
|
+
let t = e.reason;
|
|
161
|
+
return D(401, { error: t ?? "Authentication required" }, p);
|
|
162
|
+
}
|
|
163
|
+
throw e;
|
|
134
164
|
}
|
|
135
|
-
let
|
|
136
|
-
subject:
|
|
137
|
-
headers:
|
|
138
|
-
store:
|
|
165
|
+
let S = u.buildContext ? await u.buildContext(x, c.headers) : void 0, w = typeof S == "object" && S && "store" in S ? S.store : u.store, O = {
|
|
166
|
+
subject: x,
|
|
167
|
+
headers: c.headers,
|
|
168
|
+
store: w,
|
|
169
|
+
method: y,
|
|
170
|
+
path: c.path,
|
|
171
|
+
query: c.query,
|
|
172
|
+
rawBody: c.rawBody,
|
|
173
|
+
...S === void 0 ? {} : { app: S }
|
|
139
174
|
};
|
|
140
|
-
if (
|
|
141
|
-
let t = await e(
|
|
142
|
-
if (t) return
|
|
175
|
+
if (l.guards) for (let e of l.guards) {
|
|
176
|
+
let t = await e(O);
|
|
177
|
+
if (t) return D(t.status, { error: t.message }, p);
|
|
143
178
|
}
|
|
144
|
-
let
|
|
145
|
-
if (
|
|
146
|
-
let e = await
|
|
147
|
-
if (e.kind === "replay") return
|
|
148
|
-
...
|
|
179
|
+
let k = u.idempotency, N = k && M.has(l.method) ? c.headers[k.header.toLowerCase()] : void 0, P = k && N ? r(x.tenantId, l.method, l.path) : "";
|
|
180
|
+
if (k && N) {
|
|
181
|
+
let e = await i(k.store, P, N, k.ttlMs, Date.now());
|
|
182
|
+
if (e.kind === "replay") return D(e.response.status, e.response.body, {
|
|
183
|
+
...p,
|
|
149
184
|
"Idempotency-Replayed": "true"
|
|
150
185
|
});
|
|
151
|
-
if (e.kind === "conflict") return
|
|
186
|
+
if (e.kind === "conflict") return D(409, { error: "A request with this Idempotency-Key is already being processed" }, p);
|
|
152
187
|
}
|
|
153
188
|
try {
|
|
154
|
-
let e = await
|
|
155
|
-
if (
|
|
156
|
-
status: 200,
|
|
157
|
-
headers:
|
|
189
|
+
let e = await l.handler(b, O);
|
|
190
|
+
if (C(e) || T(e)) return k && N && await a(k.store, P, N), T(e) ? {
|
|
191
|
+
status: e.status ?? 200,
|
|
192
|
+
headers: {
|
|
193
|
+
...p,
|
|
194
|
+
...e.headers
|
|
195
|
+
},
|
|
158
196
|
byteStream: e.byteStream,
|
|
159
197
|
...e.contentType === void 0 ? {} : { contentType: e.contentType }
|
|
160
198
|
} : {
|
|
161
199
|
status: 200,
|
|
162
|
-
headers:
|
|
200
|
+
headers: p,
|
|
163
201
|
stream: e.stream
|
|
164
202
|
};
|
|
165
|
-
let t = await
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
203
|
+
let t = _(e) ? e : g(e), r = await s.runPromise(f(t.body)), i = {
|
|
204
|
+
...p,
|
|
205
|
+
...t.headers
|
|
206
|
+
};
|
|
207
|
+
if (k && N && await n(k.store, P, N, {
|
|
208
|
+
status: t.status,
|
|
209
|
+
body: r
|
|
210
|
+
}, Date.now()), l.etag === !0 && l.method === "GET") {
|
|
211
|
+
let e = `W/"${await E(JSON.stringify(r))}"`, n = c.headers["if-none-match"];
|
|
171
212
|
return n !== void 0 && n.split(",").some((t) => t.trim() === e) ? {
|
|
172
213
|
status: 304,
|
|
173
214
|
headers: {
|
|
174
|
-
...
|
|
215
|
+
...p,
|
|
175
216
|
ETag: e
|
|
176
217
|
}
|
|
177
|
-
} :
|
|
178
|
-
...
|
|
218
|
+
} : D(t.status, r, {
|
|
219
|
+
...i,
|
|
179
220
|
ETag: e
|
|
180
221
|
});
|
|
181
222
|
}
|
|
182
|
-
return
|
|
223
|
+
return t.status === 204 || t.status === 304 ? {
|
|
224
|
+
status: t.status,
|
|
225
|
+
headers: i
|
|
226
|
+
} : D(t.status, r, i);
|
|
183
227
|
} catch (e) {
|
|
184
|
-
if (
|
|
228
|
+
if (k && N && await a(k.store, P, N), e instanceof v) return D(e.status, e.body, {
|
|
229
|
+
...p,
|
|
230
|
+
...e.headers
|
|
231
|
+
});
|
|
232
|
+
if (typeof e == "object" && e && typeof e.status == "number") {
|
|
185
233
|
let t = e;
|
|
186
|
-
return
|
|
234
|
+
return D(t.status, { error: t.message ?? "Error" }, p);
|
|
187
235
|
}
|
|
188
|
-
return
|
|
236
|
+
return o({
|
|
189
237
|
error: e,
|
|
190
238
|
source: "rest",
|
|
191
|
-
name: `${
|
|
239
|
+
name: `${l.method} ${l.path}`,
|
|
192
240
|
fields: { status: 500 }
|
|
193
|
-
}),
|
|
241
|
+
}), D(500, { error: "Internal Server Error" }, p);
|
|
194
242
|
}
|
|
195
243
|
}
|
|
196
244
|
};
|
|
197
|
-
},
|
|
245
|
+
}, P = (e, t = {}) => {
|
|
198
246
|
if (t.idempotency !== void 0) {
|
|
199
|
-
for (let t of e) if (t.streaming === !0 &&
|
|
247
|
+
for (let t of e) if (t.streaming === !0 && M.has(t.method)) throw Error(`REST route ${t.method} ${t.path} declares \`streaming: true\` on a method the idempotency binding claims (${[...M].join("/")}). A stream cannot complete an idempotency claim — there is no replayable body to cache — so a retried request would 409 until the claim's TTL. Either serve the stream on GET, or scope the idempotency binding away from this app's streaming routes.`);
|
|
200
248
|
}
|
|
201
|
-
return e.map((e) =>
|
|
202
|
-
},
|
|
249
|
+
return e.map((e) => N(e, t));
|
|
250
|
+
}, F = async (e, t) => {
|
|
203
251
|
let n = await e[0].handle(t);
|
|
204
252
|
for (let r = 1; r < e.length && n.status === 405; r++) n = await e[r].handle(t);
|
|
205
253
|
return n;
|
|
206
254
|
};
|
|
207
255
|
//#endregion
|
|
208
|
-
export { v as bytes,
|
|
256
|
+
export { v as RestRouteError, w as bytes, f as collectPublicApiRoutes, b as defineRestRoute, l as derivePublicMethod, u as derivePublicPath, F as dispatchSharedPath, T as isRestByteResponse, _ as isRestResponse, C as isRestStreamResponse, A as matchPathParams, d as publicApiRoute, h as requireAnyScope, p as requireAuthenticated, m as requireScope, g as response, y as restError, P as restRoutesToHttpRoutes, x as sse, S as sseFrame };
|