@voltro/protocol 0.68.0 → 0.69.1
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 +310 -0
- package/dist/index.d.ts +184 -4
- package/dist/index.js +179 -179
- package/dist/rest.d.ts +47 -5
- package/dist/rest.js +201 -99
- package/dist/{scopes-C86BDyNJ.js → scopes-DVtiKKsm.js} +25 -15
- package/dist/session.js +6 -6
- package/package.json +3 -3
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,6 +105,22 @@ 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
|
|
@@ -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
|
|
@@ -629,6 +649,16 @@ declare interface PolicyGuardSpec<Input = unknown> {
|
|
|
629
649
|
readonly resource: (input: Input) => string | undefined;
|
|
630
650
|
}
|
|
631
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Translate a handler's failure into the response the projection promised:
|
|
654
|
+
* a DECLARED typed error becomes a `RestRouteError` with the tag, the fields
|
|
655
|
+
* and the status `errorStatus` assigns (`422` by default, the status the
|
|
656
|
+
* OpenAPI projection already documents for a typed error). Anything else is
|
|
657
|
+
* rethrown for the desugar's shared mapping (`ScopeError` → 403/401, a
|
|
658
|
+
* `status`-carrying error → itself, the rest → 500).
|
|
659
|
+
*/
|
|
660
|
+
export declare const projectedFailure: (err: unknown, descriptor: PublicApiDescriptor, spec: PublicApiSpec) => unknown;
|
|
661
|
+
|
|
632
662
|
/** A descriptor kind that can be projected to REST (streams are not, in v1). */
|
|
633
663
|
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
664
|
|
|
@@ -650,11 +680,12 @@ export declare type PublicApiInvoke = (input: unknown, ctx: RestRouteContext) =>
|
|
|
650
680
|
/**
|
|
651
681
|
* Synthesize the public REST route for ONE descriptor. The descriptor's input
|
|
652
682
|
* schema is wrapped into the REST `{ query }` (GET) / `{ body }` (other) shape
|
|
653
|
-
* the desugar decodes
|
|
654
|
-
* descriptor's output schema verbatim (a
|
|
655
|
-
* by the serve layer — same as POST
|
|
656
|
-
* `
|
|
657
|
-
* `
|
|
683
|
+
* the desugar decodes — for GET behind a query-string coercion — then handed
|
|
684
|
+
* UNWRAPPED to `invoke`. Output is the descriptor's output schema verbatim (a
|
|
685
|
+
* query is drained to its first snapshot by the serve layer — same as POST
|
|
686
|
+
* /rpc — unless `stream: 'sse'` streams it). The guards are
|
|
687
|
+
* `requireAuthenticated()` unless the descriptor declares `openAccess`, then
|
|
688
|
+
* one `requireScope` per `scopes` entry, on top of the handler's own RBAC.
|
|
658
689
|
*/
|
|
659
690
|
export declare const publicApiRoute: (descriptor: PublicApiDescriptor, invoke: PublicApiInvoke, subscribe?: PublicApiSubscribe) => RestRouteDescriptor<Record<string, unknown>, unknown>;
|
|
660
691
|
|
|
@@ -683,6 +714,17 @@ declare interface PublicApiSpec {
|
|
|
683
714
|
/** Streaming queries over request/response: 'snapshot' (default — first
|
|
684
715
|
* snapshot, like POST /rpc) | 'sse' (Server-Sent Events of snapshot+deltas). */
|
|
685
716
|
readonly stream?: 'snapshot' | 'sse';
|
|
717
|
+
/**
|
|
718
|
+
* HTTP status per DECLARED typed error tag, for the projection. A typed
|
|
719
|
+
* error the handler fails with — one of the descriptor's `error:` union —
|
|
720
|
+
* answers with its tag and fields as the body, and this status; a tag not
|
|
721
|
+
* listed here answers `422`, the status the OpenAPI projection documents
|
|
722
|
+
* for a typed error. The framework's own `ScopeError` is mapped
|
|
723
|
+
* before this (`403`, or `401` for an anonymous caller) and needs no entry.
|
|
724
|
+
*
|
|
725
|
+
* publicApi: { errorStatus: { AccessDeniedError: 403, NotFoundError: 404 } }
|
|
726
|
+
*/
|
|
727
|
+
readonly errorStatus?: Readonly<Record<string, number>>;
|
|
686
728
|
}
|
|
687
729
|
|
|
688
730
|
/**
|
package/dist/rest.js
CHANGED
|
@@ -1,56 +1,145 @@
|
|
|
1
|
-
import { F as e, P as t } from "./scopes-
|
|
1
|
+
import { F as e, P as t } from "./scopes-DVtiKKsm.js";
|
|
2
2
|
import { c as n, l as r, o as i, s as a, t as o } from "./serverErrorBus-BTnTpMqj.js";
|
|
3
3
|
import { Effect as s, Schema as c } from "effect";
|
|
4
|
+
import * as l from "effect/SchemaAST";
|
|
4
5
|
//#region src/publicApi.ts
|
|
5
|
-
var
|
|
6
|
+
var u = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), d = (e, t) => t.path ?? `/${t.version ?? "v1"}/${e.replace(/\./g, "/")}`, f = (e) => {
|
|
7
|
+
if (l.isUnion(e)) {
|
|
8
|
+
let t = e.types.filter((e) => !l.isUndefinedKeyword(e));
|
|
9
|
+
if (t.length === 1) return t[0];
|
|
10
|
+
if (t.length !== e.types.length) return l.Union.make(t);
|
|
11
|
+
}
|
|
12
|
+
return e;
|
|
13
|
+
}, p = /^-?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i, m = (e, t) => {
|
|
14
|
+
let n = f(t);
|
|
15
|
+
if (l.isRefinement(n) || l.isTransformation(n)) return m(e, n.from);
|
|
16
|
+
if (l.isUnion(n)) {
|
|
17
|
+
for (let t of n.types) {
|
|
18
|
+
let n = m(e, t);
|
|
19
|
+
if (n !== e) return n;
|
|
20
|
+
}
|
|
21
|
+
return e;
|
|
22
|
+
}
|
|
23
|
+
if (l.isTupleType(n)) {
|
|
24
|
+
let t = Array.isArray(e) ? e : e === void 0 ? void 0 : [e];
|
|
25
|
+
if (t === void 0) return e;
|
|
26
|
+
let r = n.rest[0]?.type ?? n.elements[0]?.type;
|
|
27
|
+
return r === void 0 ? t : t.map((e) => m(e, r));
|
|
28
|
+
}
|
|
29
|
+
if (typeof e != "string") return e;
|
|
30
|
+
if (l.isNumberKeyword(n)) return p.test(e.trim()) ? Number(e) : e;
|
|
31
|
+
if (l.isBooleanKeyword(n)) return e === "true" || e !== "false" && e;
|
|
32
|
+
if (l.isLiteral(n)) {
|
|
33
|
+
let t = n.literal;
|
|
34
|
+
return typeof t == "number" && p.test(e.trim()) && Number(e) === t || typeof t == "boolean" && String(t) === e ? t : e;
|
|
35
|
+
}
|
|
36
|
+
return e;
|
|
37
|
+
}, h = (e, t) => {
|
|
38
|
+
if (typeof e != "object" || !e || Array.isArray(e)) return e;
|
|
39
|
+
let n = f(l.encodedAST(t.ast));
|
|
40
|
+
if (!l.isTypeLiteral(n)) return e;
|
|
41
|
+
let r = { ...e };
|
|
42
|
+
for (let e of n.propertySignatures) {
|
|
43
|
+
let t = String(e.name);
|
|
44
|
+
t in r && (r[t] = m(r[t], e.type));
|
|
45
|
+
}
|
|
46
|
+
return r;
|
|
47
|
+
}, g = (e) => {
|
|
48
|
+
let t = /* @__PURE__ */ new Set();
|
|
49
|
+
if (e === void 0) return t;
|
|
50
|
+
let n = (e) => {
|
|
51
|
+
if (l.isUnion(e)) {
|
|
52
|
+
for (let t of e.types) n(t);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (l.isTransformation(e)) {
|
|
56
|
+
n(e.from), n(e.to);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (l.isRefinement(e)) {
|
|
60
|
+
n(e.from);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (l.isTypeLiteral(e)) {
|
|
64
|
+
let n = e.propertySignatures.find((e) => e.name === "_tag");
|
|
65
|
+
n !== void 0 && l.isLiteral(n.type) && typeof n.type.literal == "string" && t.add(n.type.literal);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return n(e.ast), t;
|
|
69
|
+
}, _ = (e, t, n) => {
|
|
70
|
+
if (n !== void 0) try {
|
|
71
|
+
let r = c.encodeUnknownSync(n)(e);
|
|
72
|
+
if (typeof r == "object" && r) return {
|
|
73
|
+
error: t,
|
|
74
|
+
...r
|
|
75
|
+
};
|
|
76
|
+
} catch {}
|
|
77
|
+
let r = { error: t };
|
|
78
|
+
for (let [t, n] of Object.entries(e)) t !== "stack" && t !== "_tag" && (r[t] = n);
|
|
79
|
+
return r;
|
|
80
|
+
}, v = (e, t, n) => {
|
|
81
|
+
if (typeof e != "object" || !e) return e;
|
|
82
|
+
let r = e._tag;
|
|
83
|
+
return typeof r != "string" || r === "ScopeError" || !g(t.error).has(r) ? e : new E(n.errorStatus?.[r] ?? 422, _(e, r, t.error));
|
|
84
|
+
}, y = (e, t, n) => {
|
|
6
85
|
let r = e.publicApi;
|
|
7
86
|
if (r === void 0) throw Error(`publicApiRoute: descriptor '${e.name}' has no publicApi annotation`);
|
|
8
|
-
let i =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
87
|
+
let i = u(e.kind, r), a = d(e.name, r), o = i === "GET" ? "query" : "body", s = c.Struct({ [o]: e.input }), l = i === "GET" ? c.transform(c.Struct({ query: c.Unknown }), s, {
|
|
88
|
+
strict: !1,
|
|
89
|
+
decode: (t) => ({ query: h(t.query, e.input) }),
|
|
90
|
+
encode: (e) => e
|
|
91
|
+
}) : s, f = [...e.openAccess === void 0 ? [x()] : [], ...(r.scopes ?? []).map((e) => S(e))], p = r.stream === "sse" && e.kind === "query" && n !== void 0, m = p ? (e, t) => k((r) => n(e[o], t, (e) => {
|
|
92
|
+
r(A(String(e._tag ?? "message"), e));
|
|
93
|
+
})) : async (n, i) => {
|
|
94
|
+
try {
|
|
95
|
+
return await t(n[o], i);
|
|
96
|
+
} catch (t) {
|
|
97
|
+
throw v(t, e, r);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
return O({
|
|
12
101
|
method: i,
|
|
13
102
|
path: a,
|
|
14
|
-
input:
|
|
103
|
+
input: l,
|
|
15
104
|
output: e.output,
|
|
16
|
-
handler:
|
|
17
|
-
...
|
|
18
|
-
|
|
105
|
+
handler: m,
|
|
106
|
+
...p ? { streaming: !0 } : {},
|
|
107
|
+
guards: f,
|
|
19
108
|
...r.summary === void 0 ? {} : { summary: r.summary },
|
|
20
109
|
...r.description === void 0 ? {} : { description: r.description },
|
|
21
110
|
...r.deprecated === void 0 ? {} : { deprecated: r.deprecated },
|
|
22
111
|
...r.sunset === void 0 ? {} : { sunset: r.sunset }
|
|
23
112
|
});
|
|
24
|
-
},
|
|
113
|
+
}, b = (e) => e.filter((e) => e.descriptor.publicApi !== void 0).map((e) => y(e.descriptor, e.invoke, e.subscribe)).sort((e, t) => e.path.localeCompare(t.path)), x = () => (e) => {
|
|
25
114
|
if (e.subject.type === "anonymous") return {
|
|
26
115
|
status: 401,
|
|
27
116
|
message: e.subject.credentialRejected ?? "Authentication required"
|
|
28
117
|
};
|
|
29
|
-
},
|
|
118
|
+
}, S = (e) => (t) => {
|
|
30
119
|
let n = t.subject.scopes;
|
|
31
120
|
if (!(n && (n.includes(e) || n.includes("admin:full")))) return {
|
|
32
121
|
status: 403,
|
|
33
122
|
message: `Missing required scope: ${e}`
|
|
34
123
|
};
|
|
35
|
-
},
|
|
124
|
+
}, C = (e) => (t) => {
|
|
36
125
|
let n = t.subject.scopes;
|
|
37
126
|
if (!(n && (n.includes("admin:full") || e.some((e) => n.includes(e))))) return {
|
|
38
127
|
status: 403,
|
|
39
128
|
message: `Missing required scope: one of ${e.join(", ")}`
|
|
40
129
|
};
|
|
41
|
-
},
|
|
130
|
+
}, w = (e, t = {}) => ({
|
|
42
131
|
__voltroRestResponse: !0,
|
|
43
132
|
status: t.status ?? 200,
|
|
44
133
|
body: e,
|
|
45
134
|
...t.headers === void 0 ? {} : { headers: t.headers }
|
|
46
|
-
}),
|
|
135
|
+
}), T = (e) => typeof e == "object" && !!e && e.__voltroRestResponse === !0, E = class extends Error {
|
|
47
136
|
status;
|
|
48
137
|
body;
|
|
49
138
|
headers;
|
|
50
139
|
constructor(e, t, n) {
|
|
51
140
|
super(`REST ${e}`), this.name = "RestRouteError", this.status = e, this.body = t, this.headers = n;
|
|
52
141
|
}
|
|
53
|
-
},
|
|
142
|
+
}, D = (e, t, n) => new E(e, t, n), O = (e) => {
|
|
54
143
|
if (e.version === void 0) return e;
|
|
55
144
|
if (!/^v\d+$/.test(e.version)) throw Error(`defineRestRoute(${e.method} ${e.path}): version "${e.version}" must be 'v' followed by digits — 'v1', 'v2', …`);
|
|
56
145
|
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.`);
|
|
@@ -58,13 +147,13 @@ var l = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), u = (e, t) => t.
|
|
|
58
147
|
...e,
|
|
59
148
|
path: `/${e.version}${e.path}`
|
|
60
149
|
};
|
|
61
|
-
},
|
|
150
|
+
}, k = (e, t = {}) => ({
|
|
62
151
|
__voltroRestStream: !0,
|
|
63
152
|
stream: {
|
|
64
153
|
subscribe: e,
|
|
65
154
|
...t.keepAliveMs === void 0 ? {} : { keepAliveMs: t.keepAliveMs }
|
|
66
155
|
}
|
|
67
|
-
}),
|
|
156
|
+
}), A = (e, t) => `event: ${e}\n${(typeof t == "string" ? t : JSON.stringify(t)).split("\n").map((e) => `data: ${e}`).join("\n")}\n\n`, j = (e) => typeof e == "object" && !!e && e.__voltroRestStream === !0, M = (e, t = {}) => ({
|
|
68
157
|
__voltroRestBytes: !0,
|
|
69
158
|
byteStream: {
|
|
70
159
|
stream: e,
|
|
@@ -74,23 +163,26 @@ var l = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), u = (e, t) => t.
|
|
|
74
163
|
...t.contentType === void 0 ? {} : { contentType: t.contentType },
|
|
75
164
|
...t.status === void 0 ? {} : { status: t.status },
|
|
76
165
|
...t.headers === void 0 ? {} : { headers: t.headers }
|
|
77
|
-
}),
|
|
166
|
+
}), N = (e) => typeof e == "object" && !!e && e.__voltroRestBytes === !0, P = async (e) => {
|
|
78
167
|
let t = await globalThis.crypto.subtle.digest("SHA-1", new TextEncoder().encode(e));
|
|
79
168
|
return [...new Uint8Array(t)].map((e) => e.toString(16).padStart(2, "0")).join("");
|
|
80
|
-
},
|
|
169
|
+
}, F = (e, t, n) => ({
|
|
81
170
|
status: e,
|
|
82
171
|
contentType: "application/json; charset=utf-8",
|
|
83
172
|
body: JSON.stringify(t),
|
|
84
173
|
...n ? { headers: n } : {}
|
|
85
|
-
}),
|
|
174
|
+
}), I = (e) => {
|
|
86
175
|
if (e.length === 0) return;
|
|
87
176
|
let t = new TextDecoder().decode(e);
|
|
88
177
|
if (t.trim() !== "") return JSON.parse(t);
|
|
89
|
-
},
|
|
178
|
+
}, L = (e) => {
|
|
90
179
|
let t = {};
|
|
91
|
-
for (let [n, r] of new URLSearchParams(e))
|
|
180
|
+
for (let [n, r] of new URLSearchParams(e)) {
|
|
181
|
+
let e = t[n];
|
|
182
|
+
e === void 0 ? t[n] = r : Array.isArray(e) ? e.push(r) : t[n] = [e, r];
|
|
183
|
+
}
|
|
92
184
|
return t;
|
|
93
|
-
},
|
|
185
|
+
}, R = (e, t) => {
|
|
94
186
|
let n = e.split("/").filter((e) => e.length > 0), r = t.split("/").filter((e) => e.length > 0), i = {};
|
|
95
187
|
for (let e = 0; e < n.length; e++) {
|
|
96
188
|
let t = n[e];
|
|
@@ -104,118 +196,141 @@ var l = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), u = (e, t) => t.
|
|
|
104
196
|
}
|
|
105
197
|
}
|
|
106
198
|
return i;
|
|
107
|
-
},
|
|
108
|
-
query:
|
|
199
|
+
}, z = (e, t) => ({
|
|
200
|
+
query: L(e.query),
|
|
109
201
|
params: t,
|
|
110
|
-
body:
|
|
111
|
-
}),
|
|
202
|
+
body: I(e.rawBody)
|
|
203
|
+
}), B = /* @__PURE__ */ new Set([
|
|
112
204
|
"POST",
|
|
113
205
|
"PUT",
|
|
114
206
|
"PATCH",
|
|
115
207
|
"DELETE"
|
|
116
|
-
]),
|
|
117
|
-
|
|
118
|
-
|
|
208
|
+
]), V = (e, n, r, i) => {
|
|
209
|
+
if (e instanceof E) return F(e.status, e.body, {
|
|
210
|
+
...i,
|
|
211
|
+
...e.headers
|
|
212
|
+
});
|
|
213
|
+
if (typeof e == "object" && e) {
|
|
214
|
+
let r = e;
|
|
215
|
+
if (typeof r.status == "number") return F(r.status, { error: typeof r.message == "string" ? r.message : "Error" }, i);
|
|
216
|
+
if (r._tag === "Unauthenticated" || e instanceof t) return F(401, { error: typeof r.reason == "string" ? r.reason : "Authentication required" }, i);
|
|
217
|
+
if (r._tag === "ScopeError") return F(n.type === "anonymous" ? 401 : 403, { error: typeof r.message == "string" ? r.message : "Forbidden" }, i);
|
|
218
|
+
}
|
|
219
|
+
return o({
|
|
220
|
+
error: e,
|
|
221
|
+
source: "rest",
|
|
222
|
+
name: `${r.method} ${r.path}`,
|
|
223
|
+
fields: { status: 500 }
|
|
224
|
+
}), F(500, { error: "Internal Server Error" }, i);
|
|
225
|
+
}, H = (o, l) => {
|
|
226
|
+
let u = o.input ? c.decodeUnknown(o.input) : void 0, d = c.encode(o.output), f = {}, p = (e) => e.replace(/\/$/, "").split("/").length, m = p(o.path);
|
|
227
|
+
return o.deprecated !== void 0 && (f.Deprecation = "true"), o.sunset !== void 0 && (f.Sunset = o.sunset), {
|
|
119
228
|
method: "*",
|
|
120
|
-
path:
|
|
121
|
-
...
|
|
229
|
+
path: o.path,
|
|
230
|
+
...o.maxBodyBytes === void 0 ? {} : { maxBodyBytes: o.maxBodyBytes },
|
|
122
231
|
handle: async (c) => {
|
|
123
|
-
if (!
|
|
124
|
-
let
|
|
125
|
-
if (!(
|
|
232
|
+
if (!o.path.endsWith("/*") && p(c.path) !== m) return F(404, { error: "Not Found" }, f);
|
|
233
|
+
let h = c.method.toUpperCase();
|
|
234
|
+
if (!(h === o.method || h === "HEAD" && o.method === "GET")) return F(405, {
|
|
126
235
|
error: "Method Not Allowed",
|
|
127
|
-
allow:
|
|
236
|
+
allow: o.method
|
|
128
237
|
}, {
|
|
129
|
-
...
|
|
130
|
-
Allow:
|
|
238
|
+
...f,
|
|
239
|
+
Allow: o.method
|
|
131
240
|
});
|
|
132
|
-
if (
|
|
133
|
-
let e = Date.parse(
|
|
134
|
-
if (!Number.isNaN(e) && Date.now() >= e) return
|
|
241
|
+
if (o.sunset !== void 0) {
|
|
242
|
+
let e = Date.parse(o.sunset);
|
|
243
|
+
if (!Number.isNaN(e) && Date.now() >= e) return F(410, {
|
|
135
244
|
error: "Gone",
|
|
136
|
-
...
|
|
137
|
-
...
|
|
138
|
-
},
|
|
245
|
+
...o.version === void 0 ? {} : { version: o.version },
|
|
246
|
+
...o.deprecated === void 0 ? {} : { replacement: o.deprecated }
|
|
247
|
+
}, f);
|
|
139
248
|
}
|
|
140
|
-
let
|
|
141
|
-
if (
|
|
142
|
-
let e =
|
|
249
|
+
let g;
|
|
250
|
+
if (u) {
|
|
251
|
+
let e = z(c, R(o.path, c.path)), t = await s.runPromise(u(e).pipe(s.map((e) => ({
|
|
143
252
|
ok: !0,
|
|
144
253
|
value: e
|
|
145
254
|
})), s.catchAll((e) => s.succeed({
|
|
146
255
|
ok: !1,
|
|
147
256
|
cause: e
|
|
148
257
|
}))));
|
|
149
|
-
if (!t.ok) return
|
|
258
|
+
if (!t.ok) return F(400, {
|
|
150
259
|
error: "Invalid request",
|
|
151
260
|
detail: String(t.cause)
|
|
152
|
-
},
|
|
153
|
-
|
|
261
|
+
}, f);
|
|
262
|
+
g = t.value;
|
|
154
263
|
}
|
|
155
|
-
let
|
|
264
|
+
let _;
|
|
156
265
|
try {
|
|
157
|
-
|
|
266
|
+
_ = l.resolveSubject ? await l.resolveSubject(c.headers) : e(c.headers["x-tenant"] ?? null);
|
|
158
267
|
} catch (e) {
|
|
159
268
|
if (e instanceof t || typeof e == "object" && e && e._tag === "Unauthenticated") {
|
|
160
269
|
let t = e.reason;
|
|
161
|
-
return
|
|
270
|
+
return F(401, { error: t ?? "Authentication required" }, f);
|
|
162
271
|
}
|
|
163
272
|
throw e;
|
|
164
273
|
}
|
|
165
|
-
let
|
|
166
|
-
|
|
274
|
+
let v;
|
|
275
|
+
try {
|
|
276
|
+
v = l.buildContext ? await l.buildContext(_, c.headers) : void 0;
|
|
277
|
+
} catch (e) {
|
|
278
|
+
return V(e, _, o, f);
|
|
279
|
+
}
|
|
280
|
+
let y = typeof v == "object" && v && "store" in v ? v.store : l.store, b = {
|
|
281
|
+
subject: _,
|
|
167
282
|
headers: c.headers,
|
|
168
|
-
store:
|
|
169
|
-
method:
|
|
283
|
+
store: y,
|
|
284
|
+
method: h,
|
|
170
285
|
path: c.path,
|
|
171
286
|
query: c.query,
|
|
172
287
|
rawBody: c.rawBody,
|
|
173
|
-
...
|
|
288
|
+
...v === void 0 ? {} : { app: v }
|
|
174
289
|
};
|
|
175
|
-
if (
|
|
176
|
-
let t = await e(
|
|
177
|
-
if (t) return
|
|
290
|
+
if (o.guards) for (let e of o.guards) {
|
|
291
|
+
let t = await e(b);
|
|
292
|
+
if (t) return F(t.status, { error: t.message }, f);
|
|
178
293
|
}
|
|
179
|
-
let
|
|
180
|
-
if (
|
|
181
|
-
let e = await i(
|
|
182
|
-
if (e.kind === "replay") return
|
|
183
|
-
...
|
|
294
|
+
let x = l.idempotency, S = x && B.has(o.method) ? c.headers[x.header.toLowerCase()] : void 0, C = x && S ? r(_.tenantId, o.method, o.path) : "";
|
|
295
|
+
if (x && S) {
|
|
296
|
+
let e = await i(x.store, C, S, x.ttlMs, Date.now());
|
|
297
|
+
if (e.kind === "replay") return F(e.response.status, e.response.body, {
|
|
298
|
+
...f,
|
|
184
299
|
"Idempotency-Replayed": "true"
|
|
185
300
|
});
|
|
186
|
-
if (e.kind === "conflict") return
|
|
301
|
+
if (e.kind === "conflict") return F(409, { error: "A request with this Idempotency-Key is already being processed" }, f);
|
|
187
302
|
}
|
|
188
303
|
try {
|
|
189
|
-
let e = await
|
|
190
|
-
if (
|
|
304
|
+
let e = await o.handler(g, b);
|
|
305
|
+
if (j(e) || N(e)) return x && S && await a(x.store, C, S), N(e) ? {
|
|
191
306
|
status: e.status ?? 200,
|
|
192
307
|
headers: {
|
|
193
|
-
...
|
|
308
|
+
...f,
|
|
194
309
|
...e.headers
|
|
195
310
|
},
|
|
196
311
|
byteStream: e.byteStream,
|
|
197
312
|
...e.contentType === void 0 ? {} : { contentType: e.contentType }
|
|
198
313
|
} : {
|
|
199
314
|
status: 200,
|
|
200
|
-
headers:
|
|
315
|
+
headers: f,
|
|
201
316
|
stream: e.stream
|
|
202
317
|
};
|
|
203
|
-
let t =
|
|
204
|
-
...
|
|
318
|
+
let t = T(e) ? e : w(e), r = await s.runPromise(d(t.body)), i = {
|
|
319
|
+
...f,
|
|
205
320
|
...t.headers
|
|
206
321
|
};
|
|
207
|
-
if (
|
|
322
|
+
if (x && S && await n(x.store, C, S, {
|
|
208
323
|
status: t.status,
|
|
209
324
|
body: r
|
|
210
|
-
}, Date.now()),
|
|
211
|
-
let e = `W/"${await
|
|
325
|
+
}, Date.now()), o.etag === !0 && o.method === "GET") {
|
|
326
|
+
let e = `W/"${await P(JSON.stringify(r))}"`, n = c.headers["if-none-match"];
|
|
212
327
|
return n !== void 0 && n.split(",").some((t) => t.trim() === e) ? {
|
|
213
328
|
status: 304,
|
|
214
329
|
headers: {
|
|
215
|
-
...
|
|
330
|
+
...f,
|
|
216
331
|
ETag: e
|
|
217
332
|
}
|
|
218
|
-
} :
|
|
333
|
+
} : F(t.status, r, {
|
|
219
334
|
...i,
|
|
220
335
|
ETag: e
|
|
221
336
|
});
|
|
@@ -223,34 +338,21 @@ var l = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), u = (e, t) => t.
|
|
|
223
338
|
return t.status === 204 || t.status === 304 ? {
|
|
224
339
|
status: t.status,
|
|
225
340
|
headers: i
|
|
226
|
-
} :
|
|
341
|
+
} : F(t.status, r, i);
|
|
227
342
|
} catch (e) {
|
|
228
|
-
|
|
229
|
-
...p,
|
|
230
|
-
...e.headers
|
|
231
|
-
});
|
|
232
|
-
if (typeof e == "object" && e && typeof e.status == "number") {
|
|
233
|
-
let t = e;
|
|
234
|
-
return D(t.status, { error: t.message ?? "Error" }, p);
|
|
235
|
-
}
|
|
236
|
-
return o({
|
|
237
|
-
error: e,
|
|
238
|
-
source: "rest",
|
|
239
|
-
name: `${l.method} ${l.path}`,
|
|
240
|
-
fields: { status: 500 }
|
|
241
|
-
}), D(500, { error: "Internal Server Error" }, p);
|
|
343
|
+
return x && S && await a(x.store, C, S), V(e, _, o, f);
|
|
242
344
|
}
|
|
243
345
|
}
|
|
244
346
|
};
|
|
245
|
-
},
|
|
347
|
+
}, U = (e, t = {}) => {
|
|
246
348
|
if (t.idempotency !== void 0) {
|
|
247
|
-
for (let t of e) if (t.streaming === !0 &&
|
|
349
|
+
for (let t of e) if (t.streaming === !0 && B.has(t.method)) throw Error(`REST route ${t.method} ${t.path} declares \`streaming: true\` on a method the idempotency binding claims (${[...B].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.`);
|
|
248
350
|
}
|
|
249
|
-
return e.map((e) =>
|
|
250
|
-
},
|
|
351
|
+
return e.map((e) => H(e, t));
|
|
352
|
+
}, W = async (e, t) => {
|
|
251
353
|
let n = await e[0].handle(t);
|
|
252
354
|
for (let r = 1; r < e.length && n.status === 405; r++) n = await e[r].handle(t);
|
|
253
355
|
return n;
|
|
254
356
|
};
|
|
255
357
|
//#endregion
|
|
256
|
-
export {
|
|
358
|
+
export { E as RestRouteError, M as bytes, h as coerceQueryObject, m as coerceQueryValue, b as collectPublicApiRoutes, g as declaredErrorTags, O as defineRestRoute, u as derivePublicMethod, d as derivePublicPath, W as dispatchSharedPath, N as isRestByteResponse, T as isRestResponse, j as isRestStreamResponse, R as matchPathParams, v as projectedFailure, y as publicApiRoute, C as requireAnyScope, x as requireAuthenticated, S as requireScope, w as response, D as restError, U as restRoutesToHttpRoutes, k as sse, A as sseFrame };
|
|
@@ -88,14 +88,14 @@ var i = n.Record({
|
|
|
88
88
|
},
|
|
89
89
|
removed: i
|
|
90
90
|
};
|
|
91
|
-
},
|
|
91
|
+
}, re = 3e4, ie = 1e4, T = (e) => `${e.type}\u0000${e.tenantId ?? ""}\u0000${e.id ?? ""}`, ae = () => {
|
|
92
92
|
if (typeof process > "u") return;
|
|
93
93
|
let e = process.env.VOLTRO_AUTH_SCOPE_CACHE_TTL_MS;
|
|
94
94
|
if (e === void 0 || e === "") return;
|
|
95
95
|
let t = Number(e);
|
|
96
96
|
return Number.isFinite(t) && t >= 0 ? t : void 0;
|
|
97
|
-
},
|
|
98
|
-
let t = e.ttlMs ??
|
|
97
|
+
}, E = (e = {}) => {
|
|
98
|
+
let t = e.ttlMs ?? ae() ?? 3e4, n = e.maxEntries ?? 1e4, r = e.now ?? Date.now, i = /* @__PURE__ */ new Map(), a = () => {
|
|
99
99
|
if (i.size <= n) return;
|
|
100
100
|
let e = r();
|
|
101
101
|
for (let [t, n] of i) n.freshUntil <= e && i.delete(t);
|
|
@@ -131,14 +131,24 @@ var i = n.Record({
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
};
|
|
134
|
-
},
|
|
135
|
-
let
|
|
134
|
+
}, oe = (e) => typeof e.resolve == "function", se = (e) => {
|
|
135
|
+
let t = e.authorization ?? e.Authorization;
|
|
136
|
+
if (t !== void 0) return /^Bearer\s+(.+)$/i.exec(t.trim())?.[1]?.trim();
|
|
137
|
+
}, D = (e, t) => {
|
|
138
|
+
let n = se(t);
|
|
139
|
+
if (n === void 0) return e;
|
|
140
|
+
let r = e.filter((e) => e.claimsBearerPrefix !== void 0 && e.claimsBearerPrefix !== "" && n.startsWith(e.claimsBearerPrefix));
|
|
141
|
+
if (r.length === 0) return e;
|
|
142
|
+
let i = new Set(r);
|
|
143
|
+
return [...r, ...e.filter((e) => !i.has(e))];
|
|
144
|
+
}, O = (e, t) => {
|
|
145
|
+
let n = t?.resolveScopes, r = t?.subjectGuards ?? [], i = n !== void 0 || r.length > 0, a = !i || t?.scopeCache === !1 ? null : t?.scopeCache !== void 0 && oe(t.scopeCache) ? t.scopeCache : E(t?.scopeCache ?? {});
|
|
136
146
|
return async (o) => {
|
|
137
147
|
let s = t?.getStore?.(), c = s === void 0 ? o : {
|
|
138
148
|
...o,
|
|
139
149
|
store: s
|
|
140
150
|
}, l;
|
|
141
|
-
for (let o of e) {
|
|
151
|
+
for (let o of D(e, c.headers)) {
|
|
142
152
|
let e = await o.resolve(c);
|
|
143
153
|
if (e.kind === "matched") {
|
|
144
154
|
let s = e.subject;
|
|
@@ -167,7 +177,7 @@ var i = n.Record({
|
|
|
167
177
|
}, { decision: l, fresh: u } = a === null ? {
|
|
168
178
|
decision: await i(),
|
|
169
179
|
fresh: !0
|
|
170
|
-
} : await a.resolve(
|
|
180
|
+
} : await a.resolve(T(e.subject), i);
|
|
171
181
|
if (l.kind === "vetoed") {
|
|
172
182
|
let e = `${l.code}: ${l.message}`;
|
|
173
183
|
throw t?.onStrategyFailed?.({
|
|
@@ -235,9 +245,9 @@ var i = n.Record({
|
|
|
235
245
|
}, H = (e, n) => B(e, n) ? t.void : t.fail(new F({
|
|
236
246
|
required: n,
|
|
237
247
|
message: `missing required scope '${n}'`
|
|
238
|
-
})), U = (e) => typeof e.action == "string" && typeof e.resourceType == "string", W = (e) => typeof e.open == "string", G = (e) => e.authenticated === !0, K = (e) => ({ open: e }), q = (e) => typeof e == "string" ? [e] : e, J,
|
|
248
|
+
})), U = (e) => typeof e.action == "string" && typeof e.resourceType == "string", W = (e) => typeof e.open == "string", G = (e) => e.authenticated === !0, K = (e) => ({ open: e }), q = (e) => typeof e == "string" ? [e] : e, J, ce = (e) => {
|
|
239
249
|
J = e;
|
|
240
|
-
},
|
|
250
|
+
}, le = (e) => J === void 0 ? e.filter((e) => (e.guards ?? []).some((e) => !U(e) && e.resource !== void 0)).map((e) => e.tag) : [], ue = (e) => [
|
|
241
251
|
`${e.length} procedure${e.length === 1 ? "" : "s"} declare a per-resource guard that nothing enforces:`,
|
|
242
252
|
...e.slice(0, 10).map((e) => ` ${e}`),
|
|
243
253
|
...e.length > 10 ? [` … and ${e.length - 10} more`] : [],
|
|
@@ -251,9 +261,9 @@ var i = n.Record({
|
|
|
251
261
|
" • setResourceScopeResolver((req) => Effect<boolean>) — answers \"does this",
|
|
252
262
|
" subject hold `req.scope` on `req.resource`\" from your own tables.",
|
|
253
263
|
" • Drop the `resource` extractor if a global scope check is what you meant."
|
|
254
|
-
].join("\n"),
|
|
264
|
+
].join("\n"), de = () => J, Y, fe = (e) => {
|
|
255
265
|
Y = e;
|
|
256
|
-
},
|
|
266
|
+
}, X = () => Y, Z = (e) => new F({
|
|
257
267
|
required: "access-decision",
|
|
258
268
|
message: `${e ?? "this procedure"} declares no access decision: it has neither \`guards:\` nor \`openAccess:\`, and the app runs with \`security.defaultDeny\`. Add the scope(s) a caller must hold (\`guards: [{ scope: '…' }]\`), or declare it open on purpose with \`openAccess: '<why anyone may call this>'\`.`
|
|
259
269
|
}), Q = (e, t) => {
|
|
@@ -289,7 +299,7 @@ var i = n.Record({
|
|
|
289
299
|
}
|
|
290
300
|
}
|
|
291
301
|
return null;
|
|
292
|
-
},
|
|
302
|
+
}, pe = (e, n, r) => {
|
|
293
303
|
let i = (e) => new F({
|
|
294
304
|
required: `${n.resourceType}:${n.action}`,
|
|
295
305
|
message: e
|
|
@@ -304,7 +314,7 @@ var i = n.Record({
|
|
|
304
314
|
resourceType: n.resourceType,
|
|
305
315
|
resourceId: o
|
|
306
316
|
})).pipe(t.catchAllCause(() => t.succeed(!1)), t.map((e) => e ? null : i(`no '${n.action}' relation on ${n.resourceType}:${o}`)));
|
|
307
|
-
},
|
|
317
|
+
}, me = (e, n, r, i) => {
|
|
308
318
|
if (!n || n.length === 0) return t.succeed($(e, n, i));
|
|
309
319
|
let a = J;
|
|
310
320
|
return !n.some(U) && (!a || !n.some((e) => e.resource !== void 0)) ? t.succeed($(e, n, i)) : t.gen(function* () {
|
|
@@ -318,7 +328,7 @@ var i = n.Record({
|
|
|
318
328
|
}));
|
|
319
329
|
}
|
|
320
330
|
if (U(i)) {
|
|
321
|
-
let t = yield*
|
|
331
|
+
let t = yield* pe(e, i, r);
|
|
322
332
|
if (t) return t;
|
|
323
333
|
continue;
|
|
324
334
|
}
|
|
@@ -352,4 +362,4 @@ var i = n.Record({
|
|
|
352
362
|
});
|
|
353
363
|
};
|
|
354
364
|
//#endregion
|
|
355
|
-
export { _ as A, g as B,
|
|
365
|
+
export { _ as A, g as B, ce as C, S as D, b as E, h as F, p as G, D as H, w as I, ee as K, A as L, f as M, y as N, ie as O, k as P, O as R, fe as S, x as T, v as U, E as V, T as W, U as _, F as a, H as b, me as c, X as d, de as f, W as g, G as h, M as i, d as j, re as k, z as l, V as m, N as n, ue as o, B as p, te as q, P as r, $ as s, j as t, le as u, Z as v, I as w, R as x, K as y, ne as z };
|