@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/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, then handed UNWRAPPED to `invoke`. Output is the
654
- * descriptor's output schema verbatim (a query is drained to its first snapshot
655
- * by the serve layer — same as POST /rpc — unless `stream: 'sse'` streams it).
656
- * `scopes` become
657
- * `requireScope` guards on top of the handler's own RBAC.
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-C86BDyNJ.js";
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 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
+ 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 = 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
- })) : (e, n) => t(e[o], n);
11
- return b({
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: s,
103
+ input: l,
15
104
  output: e.output,
16
- handler: p,
17
- ...f ? { streaming: !0 } : {},
18
- ...d.length > 0 ? { guards: d } : {},
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
- }, 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) => {
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
- }, m = (e) => (t) => {
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
- }, h = (e) => (t) => {
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
- }, g = (e, t = {}) => ({
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
- }), _ = (e) => typeof e == "object" && !!e && e.__voltroRestResponse === !0, v = class extends Error {
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
- }, y = (e, t, n) => new v(e, t, n), b = (e) => {
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
- }, x = (e, t = {}) => ({
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
- }), 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 = {}) => ({
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
- }), T = (e) => typeof e == "object" && !!e && e.__voltroRestBytes === !0, E = async (e) => {
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
- }, D = (e, t, n) => ({
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
- }), O = (e) => {
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
- }, k = (e) => {
178
+ }, L = (e) => {
90
179
  let t = {};
91
- for (let [n, r] of new URLSearchParams(e)) t[n] = r;
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
- }, A = (e, t) => {
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
- }, j = (e, t) => ({
108
- query: k(e.query),
199
+ }, z = (e, t) => ({
200
+ query: L(e.query),
109
201
  params: t,
110
- body: O(e.rawBody)
111
- }), M = /* @__PURE__ */ new Set([
202
+ body: I(e.rawBody)
203
+ }), B = /* @__PURE__ */ new Set([
112
204
  "POST",
113
205
  "PUT",
114
206
  "PATCH",
115
207
  "DELETE"
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), {
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: l.path,
121
- ...l.maxBodyBytes === void 0 ? {} : { maxBodyBytes: l.maxBodyBytes },
229
+ path: o.path,
230
+ ...o.maxBodyBytes === void 0 ? {} : { maxBodyBytes: o.maxBodyBytes },
122
231
  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, {
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: l.method
236
+ allow: o.method
128
237
  }, {
129
- ...p,
130
- Allow: l.method
238
+ ...f,
239
+ Allow: o.method
131
240
  });
132
- if (l.sunset !== void 0) {
133
- let e = Date.parse(l.sunset);
134
- if (!Number.isNaN(e) && Date.now() >= e) return D(410, {
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
- ...l.version === void 0 ? {} : { version: l.version },
137
- ...l.deprecated === void 0 ? {} : { replacement: l.deprecated }
138
- }, p);
245
+ ...o.version === void 0 ? {} : { version: o.version },
246
+ ...o.deprecated === void 0 ? {} : { replacement: o.deprecated }
247
+ }, f);
139
248
  }
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) => ({
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 D(400, {
258
+ if (!t.ok) return F(400, {
150
259
  error: "Invalid request",
151
260
  detail: String(t.cause)
152
- }, p);
153
- b = t.value;
261
+ }, f);
262
+ g = t.value;
154
263
  }
155
- let x;
264
+ let _;
156
265
  try {
157
- x = u.resolveSubject ? await u.resolveSubject(c.headers) : e(c.headers["x-tenant"] ?? null);
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 D(401, { error: t ?? "Authentication required" }, p);
270
+ return F(401, { error: t ?? "Authentication required" }, f);
162
271
  }
163
272
  throw e;
164
273
  }
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,
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: w,
169
- method: y,
283
+ store: y,
284
+ method: h,
170
285
  path: c.path,
171
286
  query: c.query,
172
287
  rawBody: c.rawBody,
173
- ...S === void 0 ? {} : { app: S }
288
+ ...v === void 0 ? {} : { app: v }
174
289
  };
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);
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 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,
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 D(409, { error: "A request with this Idempotency-Key is already being processed" }, p);
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 l.handler(b, O);
190
- if (C(e) || T(e)) return k && N && await a(k.store, P, N), T(e) ? {
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
- ...p,
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: p,
315
+ headers: f,
201
316
  stream: e.stream
202
317
  };
203
- let t = _(e) ? e : g(e), r = await s.runPromise(f(t.body)), i = {
204
- ...p,
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 (k && N && await n(k.store, P, N, {
322
+ if (x && S && await n(x.store, C, S, {
208
323
  status: t.status,
209
324
  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"];
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
- ...p,
330
+ ...f,
216
331
  ETag: e
217
332
  }
218
- } : D(t.status, r, {
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
- } : D(t.status, r, i);
341
+ } : F(t.status, r, i);
227
342
  } catch (e) {
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") {
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
- }, P = (e, t = {}) => {
347
+ }, U = (e, t = {}) => {
246
348
  if (t.idempotency !== void 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.`);
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) => N(e, t));
250
- }, F = async (e, t) => {
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 { 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 };
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
- }, T = 3e4, re = 1e4, E = (e) => `${e.type}\u0000${e.tenantId ?? ""}\u0000${e.id ?? ""}`, ie = () => {
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
- }, D = (e = {}) => {
98
- let t = e.ttlMs ?? ie() ?? 3e4, n = e.maxEntries ?? 1e4, r = e.now ?? Date.now, i = /* @__PURE__ */ new Map(), a = () => {
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
- }, ae = (e) => typeof e.resolve == "function", O = (e, t) => {
135
- let n = t?.resolveScopes, r = t?.subjectGuards ?? [], i = n !== void 0 || r.length > 0, a = !i || t?.scopeCache === !1 ? null : t?.scopeCache !== void 0 && ae(t.scopeCache) ? t.scopeCache : D(t?.scopeCache ?? {});
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(E(e.subject), i);
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, oe = (e) => {
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
- }, se = (e) => J === void 0 ? e.filter((e) => (e.guards ?? []).some((e) => !U(e) && e.resource !== void 0)).map((e) => e.tag) : [], ce = (e) => [
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"), le = () => J, Y, X = (e) => {
264
+ ].join("\n"), de = () => J, Y, fe = (e) => {
255
265
  Y = e;
256
- }, ue = () => Y, Z = (e) => new F({
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
- }, de = (e, n, r) => {
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
- }, fe = (e, n, r, i) => {
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* de(e, i, r);
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, oe as C, S as D, b as E, h as F, ee as G, v as H, w as I, te as K, A as L, f as M, y as N, re as O, k as P, O as R, X as S, x as T, E as U, D as V, p as W, U as _, F as a, H as b, fe as c, ue as d, le as f, W as g, G as h, M as i, d as j, T as k, z as l, V as m, N as n, ce as o, B as p, P as r, $ as s, j as t, se as u, Z as v, I as w, R as x, K as y, ne as z };
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 };