@voltro/plugin-openapi 0.70.0 → 0.71.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/dist/index.js CHANGED
@@ -1,65 +1,66 @@
1
1
  import { Effect as e, JSONSchema as t } from "effect";
2
2
  import { definePlugin as n } from "@voltro/protocol";
3
3
  import { pluginEnv as r } from "@voltro/env";
4
+ import { publicSurfaceDigest as i } from "@voltro/protocol/rest";
4
5
  //#region src/rpc.ts
5
- var i = "/rpc", a = "rpc", o = (e) => e.ast._tag === "NeverKeyword", s = (e, t) => `${e} ${t}`, c = (e) => `Voltro rpc ${e}. This \`/rpc/<name>\` path is a documentation projection of the procedure's input/output schemas — live calls go through ${e === "query" || e === "stream" ? "the batched `POST /rpc` endpoint (first snapshot / element) or the WebSocket rpc channel (reactive/streaming)" : "the batched `POST /rpc` endpoint"}, NOT a REST request to this path. The request body below is the rpc payload.`, l = (e, t) => {
6
+ var a = "/rpc", o = "rpc", s = (e) => e.ast._tag === "NeverKeyword", c = (e, t) => `${e} ${t}`, l = (e) => `Voltro rpc ${e}. This \`/rpc/<name>\` path is a documentation projection of the procedure's input/output schemas — live calls go through ${e === "query" || e === "stream" ? "the batched `POST /rpc` endpoint (first snapshot / element) or the WebSocket rpc channel (reactive/streaming)" : "the batched `POST /rpc` endpoint"}, NOT a REST request to this path. The request body below is the rpc payload.`, u = (e, t) => {
6
7
  let { kind: n, name: r } = e, i = {
7
8
  required: !0,
8
- content: { "application/json": { schema: f(e.input, t) } }
9
- }, l = (() => {
9
+ content: { "application/json": { schema: p(e.input, t) } }
10
+ }, a = (() => {
10
11
  if (n === "stream") return {
11
12
  description: "Stream of elements. The rpc transport pushes each element as it is produced (WebSocket) or drains to the first element over `POST /rpc`. The schema below is ONE element, not the whole response.",
12
- content: { "application/x-ndjson": { schema: f(e.element, t) } }
13
+ content: { "application/x-ndjson": { schema: p(e.element, t) } }
13
14
  };
14
- let r = f(e.output, t);
15
+ let r = p(e.output, t);
15
16
  return {
16
17
  description: n === "query" ? "OK — the query snapshot (the first/initial snapshot over `POST /rpc`; the WebSocket channel then pushes reactive updates)." : "OK",
17
18
  content: { "application/json": { schema: r } }
18
19
  };
19
- })(), u = o(e.error) ? {} : { 422: {
20
+ })(), u = s(e.error) ? {} : { 422: {
20
21
  description: "A typed error from the procedure — one of its declared error variants (the rpc failure channel).",
21
- content: { "application/json": { schema: f(e.error, t) } }
22
+ content: { "application/json": { schema: p(e.error, t) } }
22
23
  } };
23
24
  return {
24
- tags: [a],
25
- summary: s(n, r),
26
- description: c(n),
25
+ tags: [o],
26
+ summary: c(n, r),
27
+ description: l(n),
27
28
  "x-voltro-rpc": {
28
29
  kind: n,
29
30
  procedure: r
30
31
  },
31
32
  requestBody: i,
32
33
  responses: {
33
- 200: l,
34
+ 200: a,
34
35
  ...u,
35
- 500: _("Internal Server Error — an untyped defect.")
36
+ 500: v("Internal Server Error — an untyped defect.")
36
37
  }
37
38
  };
38
- }, u = (e, t) => {
39
+ }, d = (e, t) => {
39
40
  let n = {};
40
41
  for (let r of e) {
41
- let e = `${i}/${r.name}`;
42
- n[e] = { post: l(r, t) };
42
+ let e = `${a}/${r.name}`;
43
+ n[e] = { post: u(r, t) };
43
44
  }
44
45
  return n;
45
- }, d = (e) => e.replace(/:([A-Za-z0-9_]+)/g, "{$1}"), f = (e, n) => {
46
+ }, f = (e) => e.replace(/:([A-Za-z0-9_]+)/g, "{$1}"), p = (e, n) => {
46
47
  let { $schema: r, $defs: i, ...a } = t.make(e);
47
48
  if (i && typeof i == "object") for (let [e, t] of Object.entries(i)) n[e] = t;
48
49
  return a;
49
- }, p = (e) => {
50
- if (Array.isArray(e)) return e.map(p);
50
+ }, m = (e) => {
51
+ if (Array.isArray(e)) return e.map(m);
51
52
  if (e && typeof e == "object") {
52
53
  let t = {};
53
- for (let [n, r] of Object.entries(e)) t[n] = n === "$ref" && typeof r == "string" && r.startsWith("#/$defs/") ? `#/components/schemas/${r.slice(8)}` : p(r);
54
+ for (let [n, r] of Object.entries(e)) t[n] = n === "$ref" && typeof r == "string" && r.startsWith("#/$defs/") ? `#/components/schemas/${r.slice(8)}` : m(r);
54
55
  return t;
55
56
  }
56
57
  return e;
57
- }, m = (e, t) => {
58
+ }, h = (e, t) => {
58
59
  if (!e) return {
59
60
  parameters: [],
60
61
  requestBody: void 0
61
62
  };
62
- let n = f(e, t), r = n.properties ?? {}, i = new Set(n.required ?? []), a = [];
63
+ let n = p(e, t), r = n.properties ?? {}, i = new Set(n.required ?? []), a = [];
63
64
  for (let [e, t] of [["query", "query"], ["params", "path"]]) {
64
65
  let n = r[e];
65
66
  if (!n || typeof n != "object") continue;
@@ -79,7 +80,7 @@ var i = "/rpc", a = "rpc", o = (e) => e.ast._tag === "NeverKeyword", s = (e, t)
79
80
  content: { "application/json": { schema: o } }
80
81
  } : void 0
81
82
  };
82
- }, h = "VoltroHttpError", g = {
83
+ }, g = "VoltroHttpError", _ = {
83
84
  type: "object",
84
85
  description: "The serve pipeline's error envelope.",
85
86
  properties: {
@@ -97,10 +98,10 @@ var i = "/rpc", a = "rpc", o = (e) => e.ast._tag === "NeverKeyword", s = (e, t)
97
98
  }
98
99
  },
99
100
  required: ["error"]
100
- }, _ = (e) => ({
101
+ }, v = (e) => ({
101
102
  description: e,
102
- content: { "application/json": { schema: { $ref: `#/components/schemas/${h}` } } }
103
- }), v = "ProblemDetails", y = {
103
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${g}` } } }
104
+ }), y = "ProblemDetails", b = {
104
105
  type: "object",
105
106
  description: "RFC 9457 Problem Details. Extension members carry the error's own fields.",
106
107
  properties: {
@@ -119,7 +120,76 @@ var i = "/rpc", a = "rpc", o = (e) => e.ast._tag === "NeverKeyword", s = (e, t)
119
120
  "status"
120
121
  ],
121
122
  additionalProperties: !0
122
- }, b = {
123
+ }, x = (e, t, n = 0) => {
124
+ if (n > 6) return null;
125
+ let r = e.$ref;
126
+ if (typeof r == "string") {
127
+ let e = t[r.split("/").pop() ?? ""];
128
+ return e !== void 0 && typeof e == "object" ? x(e, t, n + 1) : null;
129
+ }
130
+ if (e.example !== void 0) return e.example;
131
+ if (Array.isArray(e.examples) && e.examples.length > 0) return e.examples[0];
132
+ if (e.const !== void 0) return e.const;
133
+ if (Array.isArray(e.enum) && e.enum.length > 0) return e.enum[0];
134
+ let i = e.anyOf ?? e.oneOf;
135
+ if (Array.isArray(i) && i.length > 0) return x(i.find((e) => e.type !== "null") ?? i[0], t, n + 1);
136
+ let a = e.allOf;
137
+ if (Array.isArray(a) && a.length > 0) {
138
+ let e = a.map((e) => x(e, t, n + 1));
139
+ return e.every((e) => typeof e == "object" && !!e && !Array.isArray(e)) ? Object.assign({}, ...e) : e[0];
140
+ }
141
+ let o = e.type;
142
+ switch (Array.isArray(o) ? o.find((e) => e !== "null") : o) {
143
+ case "string": {
144
+ let t = e.format;
145
+ if (t === "date-time") return "2026-01-01T00:00:00.000Z";
146
+ if (t === "date") return "2026-01-01";
147
+ if (t === "email") return "user@example.com";
148
+ if (t === "uri" || t === "url") return "https://example.com";
149
+ if (t === "uuid") return "00000000-0000-4000-8000-000000000000";
150
+ let n = typeof e.minLength == "number" ? e.minLength : 0;
151
+ return "string".padEnd(Math.max(n, 6), "x");
152
+ }
153
+ case "integer": return typeof e.minimum == "number" ? e.minimum : 1;
154
+ case "number": return typeof e.minimum == "number" ? e.minimum : 1;
155
+ case "boolean": return !0;
156
+ case "null": return null;
157
+ case "array": {
158
+ let r = e.items;
159
+ return r !== void 0 && typeof r == "object" && !Array.isArray(r) ? [x(r, t, n + 1)] : [];
160
+ }
161
+ case "object": {
162
+ let r = e.properties ?? {}, i = {};
163
+ for (let [e, a] of Object.entries(r)) i[e] = x(a, t, n + 1);
164
+ return i;
165
+ }
166
+ default: return typeof e.properties == "object" ? x({
167
+ ...e,
168
+ type: "object"
169
+ }, t, n) : null;
170
+ }
171
+ }, S = {
172
+ 400: {
173
+ tag: "InvalidRequest",
174
+ detail: "Invalid request"
175
+ },
176
+ 401: {
177
+ tag: "Unauthorized",
178
+ detail: "Authentication required"
179
+ },
180
+ 403: {
181
+ tag: "Forbidden",
182
+ detail: "Missing required scope: <scope>"
183
+ },
184
+ 410: {
185
+ tag: "Gone",
186
+ detail: "Gone"
187
+ },
188
+ 500: {
189
+ tag: "InternalServerError",
190
+ detail: "Internal Server Error"
191
+ }
192
+ }, C = {
123
193
  400: "Bad Request",
124
194
  401: "Unauthorized",
125
195
  403: "Forbidden",
@@ -129,66 +199,98 @@ var i = "/rpc", a = "rpc", o = (e) => e.ast._tag === "NeverKeyword", s = (e, t)
129
199
  422: "Unprocessable Content",
130
200
  429: "Too Many Requests",
131
201
  500: "Internal Server Error"
132
- }, x = (e, t, n) => {
202
+ }, w = (e, t, n) => {
133
203
  let r = t.format.errors === "problem-details" && t.format.encodeError === void 0, i = r ? "application/problem+json" : "application/json", a = /* @__PURE__ */ new Map();
134
204
  for (let e of t.errorResponses) {
135
- let t = a.get(e.status) ?? [];
205
+ let t = a.get(e.status) ?? [], i = p(e.schema, n), o = x(i, n), { _tag: s, ...c } = typeof o == "object" && o ? o : {}, l = r ? {
206
+ type: `urn:voltro:error:${e.tag}`,
207
+ title: e.tag.replace(/([a-z0-9])([A-Z])/g, "$1 $2"),
208
+ status: e.status,
209
+ ...c
210
+ } : {
211
+ error: e.tag,
212
+ ...c
213
+ };
136
214
  t.push({
137
215
  tag: e.tag,
138
- schema: r ? { allOf: [{ $ref: `#/components/schemas/${v}` }, {
216
+ example: l,
217
+ schema: r ? { allOf: [{ $ref: `#/components/schemas/${y}` }, {
139
218
  type: "object",
140
219
  properties: { type: { const: `urn:voltro:error:${e.tag}` } }
141
- }] } : f(e.schema, n)
220
+ }] } : i
142
221
  }), a.set(e.status, t);
143
222
  }
144
- let o = (e, t) => r ? {
145
- description: t,
146
- content: { [i]: { schema: { $ref: `#/components/schemas/${v}` } } }
147
- } : _(t), s = {};
223
+ let o = (e, t) => {
224
+ if (!r) return v(t);
225
+ let n = S[e], a = n === void 0 ? {} : { example: {
226
+ type: `urn:voltro:error:${n.tag}`,
227
+ title: n.tag.replace(/([a-z])([A-Z])/g, "$1 $2"),
228
+ status: e,
229
+ detail: n.detail
230
+ } };
231
+ return {
232
+ description: t,
233
+ content: { [i]: {
234
+ schema: { $ref: `#/components/schemas/${y}` },
235
+ ...a
236
+ } }
237
+ };
238
+ }, s = {};
148
239
  s[400] = o(400, "Invalid request — the input failed schema decode."), t.security !== "none" && (s[401] = o(401, "Unauthorized — no valid bearer.")), (t.security === "required" || (e.guards?.length ?? 0) > 0) && (s[403] = o(403, "Forbidden — the caller lacks a required scope."));
149
240
  for (let [e, t] of [...a.entries()].sort((e, t) => e[0] - t[0])) {
150
241
  let n = t.map((e) => e.tag);
151
242
  s[String(e)] = {
152
- description: `${b[e] ?? "Error"} — ${n.join(" | ")}`,
153
- content: { [i]: { schema: t.length === 1 ? t[0].schema : { oneOf: t.map((e) => e.schema) } } }
243
+ description: `${C[e] ?? "Error"} — ${n.join(" | ")}`,
244
+ content: { [i]: {
245
+ schema: t.length === 1 ? t[0].schema : { oneOf: t.map((e) => e.schema) },
246
+ example: t[0].example
247
+ } }
154
248
  };
155
249
  }
156
250
  return e.sunset !== void 0 && (s[410] = o(410, `Gone — the route is past its sunset date (${e.sunset}).`)), s[500] = o(500, "Internal Server Error."), s;
157
- }, S = (e, t, n) => {
158
- let r = f(t.input, n), i = r.properties ?? {}, a = new Set(r.required ?? []), o = new Set(t.pathParams), s = [], c = {}, l = [];
251
+ }, T = (e, t, n) => {
252
+ let r = p(t.input, n), i = r.properties ?? {}, a = new Set(r.required ?? []), o = new Set(t.pathParams), s = [], c = {}, l = [], u = e.example?.request, d = (e) => typeof u == "object" && u && e in u ? { example: u[e] } : {};
159
253
  for (let [t, n] of Object.entries(i)) o.has(t) ? s.push({
160
254
  name: t,
161
255
  in: "path",
162
256
  required: !0,
163
- schema: n
257
+ schema: n,
258
+ ...d(t)
164
259
  }) : e.method === "GET" ? s.push({
165
260
  name: t,
166
261
  in: "query",
167
262
  required: a.has(t),
168
- schema: n
263
+ schema: n,
264
+ ...d(t)
169
265
  }) : (c[t] = n, a.has(t) && l.push(t));
170
266
  return {
171
267
  parameters: s,
172
268
  requestBody: e.method !== "GET" && Object.keys(c).length > 0 ? {
173
269
  required: l.length > 0,
174
- content: { "application/json": { schema: {
175
- ...r,
176
- properties: c,
177
- required: l,
178
- ...r.additionalProperties === void 0 ? {} : { additionalProperties: r.additionalProperties }
179
- } } }
270
+ content: {
271
+ "application/json": { schema: {
272
+ ...r,
273
+ properties: c,
274
+ required: l
275
+ } },
276
+ "application/x-www-form-urlencoded": { schema: {
277
+ ...r,
278
+ properties: c,
279
+ required: l
280
+ } }
281
+ }
180
282
  } : void 0
181
283
  };
182
- }, C = (e, t, n) => {
284
+ }, E = (e, t, n) => {
183
285
  if (e.streaming === !0) return {
184
286
  description: "Server-Sent Events stream (`snapshot`, then `delta` per change)",
185
287
  content: { "text/event-stream": { schema: { type: "string" } } }
186
288
  };
187
- let r = f(t.output, n), i = t.format, a = i.encodeSuccess === void 0 ? i.envelope === "data" ? {
289
+ let r = p(t.output, n), i = t.format, a = i.pagination === "link-header" && D(r, n, i.cursorField), o = i.encodeSuccess === void 0 ? i.envelope === "data" ? {
188
290
  type: "object",
189
291
  properties: {
190
292
  data: r,
191
- ...i.pagination === "link-header" ? { next: {
293
+ ...a ? { next: {
192
294
  type: "string",
193
295
  description: "The next page's cursor, when there is one."
194
296
  } } : {}
@@ -204,8 +306,8 @@ var i = "/rpc", a = "rpc", o = (e) => e.ast._tag === "NeverKeyword", s = (e, t)
204
306
  }
205
307
  },
206
308
  required: ["data"]
207
- } : r : { description: `Encoded by the '${i.name}' format.` }, o = i.envelope === "jsonapi" ? "application/vnd.api+json" : "application/json", s = {
208
- ...i.pagination === "link-header" ? { Link: {
309
+ } : r : { description: `Encoded by the '${i.name}' format.` }, s = i.envelope === "jsonapi" ? "application/vnd.api+json" : "application/json", c = {
310
+ ...a ? { Link: {
209
311
  description: "RFC 8288 — `<url>; rel=\"next\"` when there is a next page.",
210
312
  schema: { type: "string" }
211
313
  } } : {},
@@ -216,45 +318,63 @@ var i = "/rpc", a = "rpc", o = (e) => e.ast._tag === "NeverKeyword", s = (e, t)
216
318
  };
217
319
  return {
218
320
  description: "OK",
219
- ...Object.keys(s).length > 0 ? { headers: s } : {},
220
- content: { [o]: {
221
- schema: a,
222
- ...e.example?.response === void 0 ? {} : { example: e.example.response }
321
+ ...Object.keys(c).length > 0 ? { headers: c } : {},
322
+ content: { [s]: {
323
+ schema: o,
324
+ ...O(e.example?.response, o, n)
223
325
  } }
224
326
  };
225
- }, w = (e) => ({
226
- ...e.input === void 0 ? {} : { 400: _("Invalid request — the input failed schema decode.") },
327
+ }, D = (e, t, n) => {
328
+ let r = e.$ref, i = (typeof r == "string" ? t[r.split("/").pop() ?? ""] : e)?.properties;
329
+ return typeof i == "object" && !!i && n in i;
330
+ }, O = (e, t, n) => e === void 0 ? {
331
+ example: x(t, n),
332
+ "x-voltro-example": "derived"
333
+ } : { example: e }, k = (e) => ({
334
+ ...e.input === void 0 ? {} : { 400: v("Invalid request — the input failed schema decode.") },
227
335
  ...e.guards !== void 0 && e.guards.length > 0 ? {
228
- 401: _("Unauthorized — a guard rejected the unauthenticated request."),
229
- 403: _("Forbidden — a guard rejected the request (e.g. a required scope is missing).")
336
+ 401: v("Unauthorized — a guard rejected the unauthenticated request."),
337
+ 403: v("Forbidden — a guard rejected the request (e.g. a required scope is missing).")
230
338
  } : {},
231
- ...e.sunset === void 0 ? {} : { 410: _(`Gone — the route is past its sunset date (${e.sunset}).`) },
232
- 500: _("Internal Server Error.")
339
+ ...e.sunset === void 0 ? {} : { 410: v(`Gone — the route is past its sunset date (${e.sunset}).`) },
340
+ 500: v("Internal Server Error.")
233
341
  });
234
- function T(e, t = {}, n = {}, r = {}) {
235
- let i = {}, a = {}, o = e.some((e) => e.guards !== void 0 && e.guards.length > 0), s = e.some((e) => e.projection !== void 0 && e.projection.security !== "none"), c = e.some((e) => e.projection?.format.errors === "problem-details" && e.projection.format.encodeError === void 0), l = /* @__PURE__ */ new Set();
342
+ function A(e, t = {}, n = {}, r = {}) {
343
+ let a = {}, o = {}, s = e.some((e) => e.guards !== void 0 && e.guards.length > 0), c = e.some((e) => e.projection !== void 0 && e.projection.security !== "none"), l = e.some((e) => e.projection?.format.errors === "problem-details" && e.projection.format.encodeError === void 0), u = /* @__PURE__ */ new Set();
236
344
  for (let t of e) {
237
- let e = t.projection, { parameters: n, requestBody: r } = e === void 0 ? m(t.input, a) : S(t, e, a), o = t.guards !== void 0 && t.guards.length > 0;
238
- if (r !== void 0 && t.example?.request !== void 0) {
345
+ let e = t.projection, { parameters: n, requestBody: r } = e === void 0 ? h(t.input, o) : T(t, e, o), i = t.etag === !0 && t.method === "GET" && t.streaming !== !0, s = i ? [...n, {
346
+ name: "If-None-Match",
347
+ in: "header",
348
+ required: !1,
349
+ description: "A previously returned ETag. A matching validator returns 304 without a body.",
350
+ schema: { type: "string" }
351
+ }] : n, c = i ? { 304: {
352
+ description: "Not Modified",
353
+ headers: { ETag: {
354
+ description: "The current representation validator.",
355
+ schema: { type: "string" }
356
+ } }
357
+ } } : {}, l = t.guards !== void 0 && t.guards.length > 0;
358
+ if (r !== void 0) {
239
359
  let e = r.content;
240
360
  e["application/json"] = {
241
361
  ...e["application/json"],
242
- example: t.example.request
362
+ ...O(t.example?.request, e["application/json"].schema, o)
243
363
  };
244
364
  }
245
- let s = e === void 0 ? o ? { security: [{ bearerAuth: [] }] } : {} : e.security === "required" ? { security: [{ bearerAuth: [] }] } : e.security === "optional" ? { security: [{ bearerAuth: [] }, {}] } : {}, c = t.tags ?? (t.version === void 0 ? void 0 : [t.version]);
246
- for (let e of c ?? []) l.add(e);
247
- let u = e?.version ?? t.version, p = {
365
+ let d = e === void 0 ? l ? { security: [{ bearerAuth: [] }] } : {} : e.security === "required" ? { security: [{ bearerAuth: [] }] } : e.security === "optional" ? { security: [{ bearerAuth: [] }, {}] } : {}, m = t.tags ?? (t.version === void 0 ? void 0 : [t.version]);
366
+ for (let e of m ?? []) u.add(e);
367
+ let g = e?.version ?? t.version, _ = {
248
368
  ...t.operationId === void 0 ? {} : { operationId: t.operationId },
249
369
  ...t.summary ? { summary: t.summary } : {},
250
370
  ...t.description ? { description: t.description } : {},
251
371
  ...t.deprecated === void 0 ? {} : { deprecated: !0 },
252
372
  ...t.sunset === void 0 ? {} : { "x-sunset": t.sunset },
253
- ...c === void 0 ? {} : { tags: c },
254
- ...u === void 0 ? {} : { "x-voltro-api-version": u },
255
- ...n.length > 0 ? { parameters: n } : {},
373
+ ...m === void 0 ? {} : { tags: m },
374
+ ...g === void 0 ? {} : { "x-voltro-api-version": g },
375
+ ...s.length > 0 ? { parameters: s } : {},
256
376
  ...r ? { requestBody: r } : {},
257
- ...s,
377
+ ...d,
258
378
  responses: e === void 0 ? {
259
379
  200: t.streaming === !0 ? {
260
380
  description: "Server-Sent Events stream (`snapshot`, then `delta` per change)",
@@ -262,52 +382,67 @@ function T(e, t = {}, n = {}, r = {}) {
262
382
  } : {
263
383
  description: "OK",
264
384
  content: { "application/json": {
265
- schema: f(t.output, a),
266
- ...t.example?.response === void 0 ? {} : { example: t.example.response }
385
+ schema: p(t.output, o),
386
+ ...O(t.example?.response, p(t.output, o), o)
267
387
  } }
268
388
  },
269
- ...w(t)
389
+ ...k(t),
390
+ ...c
270
391
  } : {
271
- 200: C(t, e, a),
272
- ...x(t, e, a)
392
+ 200: E(t, e, o),
393
+ ...c,
394
+ ...w(t, e, o)
273
395
  }
274
396
  };
275
- e?.format.describe !== void 0 && (p = e.format.describe(p, {
397
+ e?.format.describe !== void 0 && (_ = e.format.describe(_, {
276
398
  tag: e.tag,
277
399
  method: t.method,
278
400
  path: t.path
279
401
  }));
280
- let h = d(t.path), g = i[h] ?? {};
281
- g[t.method.toLowerCase()] = p, i[h] = g;
402
+ let v = f(t.path), y = a[v] ?? {};
403
+ y[t.method.toLowerCase()] = _, a[v] = y;
282
404
  }
283
- let _ = n.procedures ?? [];
284
- if (_.length > 0) {
285
- let e = u(_, a);
286
- for (let [t, n] of Object.entries(e)) i[t] = {
405
+ let v = n.procedures ?? [];
406
+ if (v.length > 0) {
407
+ let e = d(v, o);
408
+ for (let [t, n] of Object.entries(e)) a[t] = {
287
409
  ...n,
288
- ...i[t] ?? {}
410
+ ...a[t] ?? {}
289
411
  };
290
412
  }
291
- let b = [...l].sort().map((e) => ({
413
+ let x = [...u].sort().map((e) => ({
292
414
  name: e,
293
- ...r.tagDescriptions?.[e] === void 0 ? {} : { description: r.tagDescriptions[e] }
415
+ ...r.tagDescriptions?.[e] === void 0 ? {} : { description: r.tagDescriptions[e] },
416
+ ...r.tagExternalDocs?.[e] === void 0 ? {} : { externalDocs: r.tagExternalDocs[e] }
417
+ })), S = (r.servers ?? []).map((e) => ({
418
+ url: e.url,
419
+ ...e.description === void 0 ? {} : { description: e.description },
420
+ ...e.environment === void 0 ? {} : { "x-voltro-environment": e.environment }
294
421
  }));
295
422
  return {
296
423
  openapi: "3.1.0",
297
424
  info: {
298
425
  title: t.title ?? "API",
299
426
  version: t.version ?? "1.0.0",
300
- ...t.description ? { description: t.description } : {}
427
+ ...t.description ? { description: t.description } : {},
428
+ ...t.contact === void 0 ? {} : { contact: t.contact },
429
+ ...t.license === void 0 ? {} : { license: t.license },
430
+ ...t.termsOfService === void 0 ? {} : { termsOfService: t.termsOfService },
431
+ ...t.logo === void 0 ? {} : { "x-logo": t.logo }
301
432
  },
302
- ...b.length > 0 ? { tags: b } : {},
303
- paths: p(i),
433
+ ...S.length > 0 ? { servers: S } : {},
434
+ ...r.externalDocs === void 0 ? {} : { externalDocs: r.externalDocs },
435
+ ...r.environment === void 0 ? {} : { "x-voltro-environment": r.environment },
436
+ ...e.some((e) => e.projection !== void 0) ? { "x-voltro-surface-digest": i(e) } : {},
437
+ ...x.length > 0 ? { tags: x } : {},
438
+ paths: m(a),
304
439
  components: {
305
440
  schemas: {
306
- ...p(a),
307
- [h]: g,
308
- ...c ? { [v]: y } : {}
441
+ ...m(o),
442
+ [g]: _,
443
+ ...l ? { [y]: b } : {}
309
444
  },
310
- ...o || s ? { securitySchemes: { bearerAuth: {
445
+ ...s || c ? { securitySchemes: { bearerAuth: {
311
446
  type: "http",
312
447
  scheme: "bearer"
313
448
  } } } : {}
@@ -316,104 +451,128 @@ function T(e, t = {}, n = {}, r = {}) {
316
451
  }
317
452
  //#endregion
318
453
  //#region src/swaggerUi.ts
319
- var E = "5.17.14", D = "sha384-wxLW6kwyHktdDGr6Pv1zgm/VGJh99lfUbzSn6HNHBENZlCN7W602k9VkGdxuFvPn", O = "sha384-wmyclcVGX/WhUkdkATwhaK1X1JtiNrr2EoYJ+diV3vj4v6OC5yCeSu+yW13SYJep", k = `https://unpkg.com/swagger-ui-dist@${E}/swagger-ui.css`, A = `https://unpkg.com/swagger-ui-dist@${E}/swagger-ui-bundle.js`, j = (e, t = "API docs", n = {}) => `<!doctype html>
454
+ var j = "5.17.14", M = "sha384-wxLW6kwyHktdDGr6Pv1zgm/VGJh99lfUbzSn6HNHBENZlCN7W602k9VkGdxuFvPn", N = "sha384-wmyclcVGX/WhUkdkATwhaK1X1JtiNrr2EoYJ+diV3vj4v6OC5yCeSu+yW13SYJep", P = `https://unpkg.com/swagger-ui-dist@${j}/swagger-ui.css`, F = `https://unpkg.com/swagger-ui-dist@${j}/swagger-ui-bundle.js`, I = (e, t = "API docs", n = {}) => `<!doctype html>
320
455
  <html lang="en">
321
456
  <head>
322
457
  <meta charset="utf-8" />
323
458
  <meta name="viewport" content="width=device-width, initial-scale=1" />
324
459
  <title>${t}</title>
325
- <link rel="stylesheet" href="${k}" integrity="${D}" crossorigin="anonymous" />
460
+ <link rel="stylesheet" href="${P}" integrity="${M}" crossorigin="anonymous" />
326
461
  <style>body { margin: 0 }</style>
327
462
  </head>
328
463
  <body>
329
464
  <div id="swagger-ui"></div>
330
- <script src="${A}" integrity="${O}" crossorigin="anonymous"><\/script>
465
+ <script src="${F}" integrity="${N}" crossorigin="anonymous"><\/script>
331
466
  <script>
332
467
  window.ui = SwaggerUIBundle({ ${n.urls !== void 0 && n.urls.length > 0 ? `urls: ${JSON.stringify(n.urls)}, ${n.primaryName === void 0 ? "" : `'urls.primaryName': ${JSON.stringify(n.primaryName)}, `}` : `url: ${JSON.stringify(e)}, `}dom_id: '#swagger-ui', deepLinking: true });
333
468
  <\/script>
334
469
  </body>
335
- </html>`, M = r([{
336
- name: "OPENAPI_DOCS_TOKEN",
337
- required: !1,
338
- secret: !0,
339
- description: "Bearer token required on GET /openapi.json + GET /docs; unset leaves them public."
340
- }]), N = (e, t) => {
470
+ </html>`, L = r([
471
+ {
472
+ name: "OPENAPI_DOCS_TOKEN",
473
+ required: !1,
474
+ secret: !0,
475
+ description: "Bearer token required on GET /openapi.json + GET /docs; unset leaves them public."
476
+ },
477
+ {
478
+ name: "VOLTRO_PUBLIC_API_ORIGIN",
479
+ required: !1,
480
+ secret: !1,
481
+ description: "The public origin of this api (https://api.example.com) — the default servers[0] of the spec."
482
+ },
483
+ {
484
+ name: "VOLTRO_ENVIRONMENT",
485
+ required: !1,
486
+ secret: !1,
487
+ description: "The environment this instance serves (dev | staging | production) — x-voltro-environment on the spec."
488
+ }
489
+ ]), R = (e, t) => {
341
490
  if (e) return (t.headers.authorization ?? t.headers.Authorization) === `Bearer ${e}` ? void 0 : {
342
491
  status: 401,
343
492
  body: "unauthorized",
344
493
  contentType: "text/plain"
345
494
  };
346
- }, P = (e) => e.projection?.version ?? e.version, F = (e) => {
495
+ }, z = (e) => e.projection?.version ?? e.version, B = (e) => {
347
496
  let t = /^v(\d+)$/.exec(e);
348
497
  return t === null ? -1 : Number(t[1]);
349
- }, I = (e) => [...new Set(e.map(P).filter((e) => e !== void 0))].sort((e, t) => F(t) - F(e) || t.localeCompare(e)), L = (e, t) => {
350
- let n = t.filter((t) => P(t) === e && t.sunset !== void 0).map((e) => e.sunset).sort();
498
+ }, V = (e) => [...new Set(e.map(z).filter((e) => e !== void 0))].sort((e, t) => B(t) - B(e) || t.localeCompare(e)), H = (e, t) => {
499
+ let n = t.filter((t) => z(t) === e && t.sunset !== void 0).map((e) => e.sunset).sort();
351
500
  return n.length > 0 ? `${e} — sunset ${n[0]}` : e;
352
- }, R = (t) => {
353
- let r = t.specPath ?? "/openapi.json", i = t.docsPath ?? "/docs", a = M.read("OPENAPI_DOCS_TOKEN", t.token), o = t.name ? `@voltro/plugin-openapi#${t.name}` : "@voltro/plugin-openapi", s = t.routes ?? [], c = t.procedures ?? [], l = c, u = t.includeAppRoutes ?? !0, d = t.includeAppProcedures ?? !1, f = s, p = t.tagDescriptions === void 0 ? {} : { tagDescriptions: t.tagDescriptions }, m = (e) => JSON.stringify(T(e === void 0 ? f : f.filter((t) => P(t) === e), t.info ?? {}, e === void 0 ? { procedures: l } : {}, p), null, 2), h = /* @__PURE__ */ new Map(), g = () => {
354
- h = new Map([["", m()], ...I(f).map((e) => [e, m(e)])]);
501
+ }, U = (t) => {
502
+ let r = t.specPath ?? "/openapi.json", i = t.docsPath ?? "/docs", a = L.read("OPENAPI_DOCS_TOKEN", t.token), o = t.name ? `@voltro/plugin-openapi#${t.name}` : "@voltro/plugin-openapi", s = t.routes ?? [], c = t.procedures ?? [], l = c, u = t.includeAppRoutes ?? !0, d = t.includeAppProcedures ?? !1, f = s, p = t.environment ?? L.read("VOLTRO_ENVIRONMENT", void 0) ?? (process.env.NODE_ENV === "production" ? "production" : "dev"), m = L.read("VOLTRO_PUBLIC_API_ORIGIN", void 0), h = t.servers ?? (m === void 0 ? [] : [{
503
+ url: m,
504
+ description: `${p} — this instance`,
505
+ environment: p
506
+ }]), g = {
507
+ ...t.tagDescriptions === void 0 ? {} : { tagDescriptions: t.tagDescriptions },
508
+ ...t.tagExternalDocs === void 0 ? {} : { tagExternalDocs: t.tagExternalDocs },
509
+ ...t.externalDocs === void 0 ? {} : { externalDocs: t.externalDocs },
510
+ ...h.length > 0 ? { servers: h } : {},
511
+ environment: p
512
+ }, _ = (e) => JSON.stringify(A(e === void 0 ? f : f.filter((t) => z(t) === e), t.info ?? {}, e === void 0 ? { procedures: l } : {}, g), null, 2), v = /* @__PURE__ */ new Map(), y = () => {
513
+ v = new Map([["", _()], ...V(f).map((e) => [e, _(e)])]);
355
514
  };
356
- g();
357
- let _ = (e) => {
515
+ y();
516
+ let b = (e) => {
358
517
  if (!u) return;
359
518
  let t = new Set(s.map((e) => `${e.method} ${e.path}`)), n = e.filter((e) => !t.has(`${e.method} ${e.path}`));
360
- f = [...s, ...n], g();
361
- }, v = (e) => {
519
+ f = [...s, ...n], y();
520
+ }, x = (e) => {
362
521
  if (!d) return;
363
522
  let t = new Set(c.map((e) => e.name));
364
- l = [...c, ...e.filter((e) => !t.has(e.name))], g();
365
- }, y = r.endsWith(".json") ? r.slice(0, -5) : `${r}/versions`, b = (e) => `${y}/${e}.json`, x = (e) => ({
523
+ l = [...c, ...e.filter((e) => !t.has(e.name))], y();
524
+ }, S = r.endsWith(".json") ? r.slice(0, -5) : `${r}/versions`, C = (e) => `${S}/${e}.json`, w = (e) => ({
366
525
  status: 404,
367
526
  contentType: "application/json",
368
527
  body: JSON.stringify({
369
528
  error: `no API version '${e}' is mounted`,
370
- versions: I(f)
529
+ versions: V(f)
371
530
  })
372
- }), S = [{
531
+ }), T = [{
373
532
  method: "GET",
374
533
  path: r,
375
534
  match: "exact",
376
535
  handle: async (e) => {
377
- let t = N(a, e);
536
+ let t = R(a, e);
378
537
  if (t) return t;
379
538
  let n = new URLSearchParams(e.query).get("version") ?? void 0;
380
- return n !== void 0 && !h.has(n) ? x(n) : {
539
+ return n !== void 0 && !v.has(n) ? w(n) : {
381
540
  status: 200,
382
- body: h.get(n ?? ""),
541
+ body: v.get(n ?? ""),
383
542
  contentType: "application/json"
384
543
  };
385
544
  }
386
545
  }, {
387
546
  method: "GET",
388
- path: y,
547
+ path: S,
389
548
  handle: async (e) => {
390
- let t = N(a, e);
549
+ let t = R(a, e);
391
550
  if (t) return t;
392
551
  let n = /\/([^/]+)\.json$/.exec(e.path)?.[1];
393
- return n === void 0 || n === "" || !h.has(n) ? x(n ?? "") : {
552
+ return n === void 0 || n === "" || !v.has(n) ? w(n ?? "") : {
394
553
  status: 200,
395
- body: h.get(n),
554
+ body: v.get(n),
396
555
  contentType: "application/json"
397
556
  };
398
557
  }
399
558
  }];
400
- return i !== !1 && S.push({
559
+ return i !== !1 && T.push({
401
560
  method: "GET",
402
561
  path: i,
403
562
  match: "exact",
404
563
  handle: async (e) => {
405
- let n = N(a, e);
564
+ let n = R(a, e);
406
565
  if (n) return n;
407
- let i = I(f), o = i.length > 1 ? [...i.map((e) => ({
408
- url: b(e),
409
- name: L(e, f)
566
+ let i = V(f), o = i.length > 1 ? [...i.map((e) => ({
567
+ url: C(e),
568
+ name: H(e, f)
410
569
  })), {
411
570
  url: r,
412
571
  name: "all versions"
413
572
  }] : void 0;
414
573
  return {
415
574
  status: 200,
416
- body: j(r, t.info?.title ?? "API docs", o === void 0 ? {} : {
575
+ body: I(r, t.info?.title ?? "API docs", o === void 0 ? {} : {
417
576
  urls: o,
418
577
  primaryName: o[0].name
419
578
  }),
@@ -424,10 +583,10 @@ var E = "5.17.14", D = "sha384-wxLW6kwyHktdDGr6Pv1zgm/VGJh99lfUbzSn6HNHBENZlCN7W
424
583
  name: o,
425
584
  description: "OpenAPI 3.1 spec + Swagger-UI docs from defineRestRoute descriptors + rpc procedures.",
426
585
  permissions: [],
427
- declaredEnv: M.declared,
428
- httpRoutes: S,
429
- onRestSurface: _,
430
- onProcedureSurface: v,
586
+ declaredEnv: L.declared,
587
+ httpRoutes: T,
588
+ onRestSurface: b,
589
+ onProcedureSurface: x,
431
590
  onActivate: (t) => e.sync(() => {
432
591
  t.logger.info("openapi docs active", {
433
592
  specPath: r,
@@ -436,11 +595,13 @@ var E = "5.17.14", D = "sha384-wxLW6kwyHktdDGr6Pv1zgm/VGJh99lfUbzSn6HNHBENZlCN7W
436
595
  appRoutes: u ? "included once mounted" : "excluded",
437
596
  procedures: l.length,
438
597
  appProcedures: d ? "included once discovered" : "excluded",
439
- versions: I(f),
598
+ environment: p,
599
+ servers: h.map((e) => e.url),
600
+ versions: V(f),
440
601
  tokenGated: !!a
441
602
  });
442
603
  })
443
604
  });
444
605
  };
445
606
  //#endregion
446
- export { v as PROBLEM_DETAILS_SCHEMA_NAME, T as generateOpenApiSpec, I as mountedVersions, R as openapiPlugin, j as swaggerUiHtml, L as versionLabel };
607
+ export { y as PROBLEM_DETAILS_SCHEMA_NAME, x as deriveExample, A as generateOpenApiSpec, V as mountedVersions, U as openapiPlugin, I as swaggerUiHtml, H as versionLabel };