@palbase/web 1.9.0 → 2.0.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/README.md +3 -2
- package/dist/{analytics-facade-DsvKx5A5.d.ts → analytics-facade-DMtGDGfd.d.ts} +3 -19
- package/dist/{analytics-facade-DFd5LB3_.d.cts → analytics-facade-ovP0pF2P.d.cts} +3 -19
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/{chunk-I3ZMB7XM.js → chunk-V6VTX65P.js} +23 -36
- package/dist/chunk-V6VTX65P.js.map +1 -0
- package/dist/gen/cli.cjs +1228 -0
- package/dist/gen/cli.cjs.map +1 -0
- package/dist/gen/cli.d.cts +1 -0
- package/dist/gen/cli.d.ts +1 -0
- package/dist/gen/cli.js +1206 -0
- package/dist/gen/cli.js.map +1 -0
- package/dist/index.cjs +18 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/internal.cjs +19 -30
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +2 -1
- package/dist/next/client.cjs +16 -29
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.js +2 -1
- package/dist/next/client.js.map +1 -1
- package/dist/next/index.cjs +19 -30
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +2 -2
- package/dist/next/index.d.ts +2 -2
- package/dist/next/index.js +2 -1
- package/dist/next/index.js.map +1 -1
- package/dist/{pb-CDVMy1l1.d.ts → pb-CvpcQero.d.ts} +1 -1
- package/dist/{pb-D_fuhafL.d.cts → pb-DDM_mCQB.d.cts} +1 -1
- package/dist/react/index.cjs +12 -8
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +2 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +7 -3
- package/dist/chunk-I3ZMB7XM.js.map +0 -1
package/dist/gen/cli.js
ADDED
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../chunk-PZ5AY32C.js";
|
|
3
|
+
|
|
4
|
+
// src/gen/cli.ts
|
|
5
|
+
import process2 from "process";
|
|
6
|
+
|
|
7
|
+
// src/gen/generate.ts
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
import process from "process";
|
|
12
|
+
import { parseArgs } from "util";
|
|
13
|
+
|
|
14
|
+
// src/gen/emitter.ts
|
|
15
|
+
function opSegments(opId) {
|
|
16
|
+
return opId.split(".").filter((p) => p !== "");
|
|
17
|
+
}
|
|
18
|
+
function typeNameOf(s) {
|
|
19
|
+
return sanitize(s, true);
|
|
20
|
+
}
|
|
21
|
+
function typePrefix(opId) {
|
|
22
|
+
return opSegments(opId).map(typeNameOf).join("");
|
|
23
|
+
}
|
|
24
|
+
function sanitize(s, firstUpper) {
|
|
25
|
+
const parts = [];
|
|
26
|
+
let cur = "";
|
|
27
|
+
for (const ch of s) {
|
|
28
|
+
if (/^[A-Za-z0-9]$/.test(ch)) {
|
|
29
|
+
cur += ch;
|
|
30
|
+
} else if (cur.length > 0) {
|
|
31
|
+
parts.push(cur);
|
|
32
|
+
cur = "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (cur.length > 0) parts.push(cur);
|
|
36
|
+
if (parts.length === 0) return firstUpper ? "Op" : "op";
|
|
37
|
+
let out = "";
|
|
38
|
+
parts.forEach((p, i) => {
|
|
39
|
+
out += i === 0 && !firstUpper ? p.charAt(0).toLowerCase() + p.slice(1) : p.charAt(0).toUpperCase() + p.slice(1);
|
|
40
|
+
});
|
|
41
|
+
if (out.charAt(0) >= "0" && out.charAt(0) <= "9") out = `_${out}`;
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function emitTypeScript(ops, cfg) {
|
|
45
|
+
const usable = [];
|
|
46
|
+
const skippedNS = [];
|
|
47
|
+
const seenSkip = /* @__PURE__ */ new Set();
|
|
48
|
+
for (const op of ops) {
|
|
49
|
+
const [ns] = opSegments(op.operationId);
|
|
50
|
+
if (ns === void 0) continue;
|
|
51
|
+
if (tsReservedTopLevel(ns)) {
|
|
52
|
+
const key = ns.toLowerCase();
|
|
53
|
+
if (!seenSkip.has(key)) {
|
|
54
|
+
seenSkip.add(key);
|
|
55
|
+
skippedNS.push(ns);
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
usable.push(op);
|
|
60
|
+
}
|
|
61
|
+
skippedNS.sort();
|
|
62
|
+
const [candidates, opSkips] = filterTSOps(usable);
|
|
63
|
+
const [typesOut, collisionSkips, typeCollisionOps] = emitTSTypes(candidates);
|
|
64
|
+
const registerable = candidates.filter((op) => !typeCollisionOps.has(op.operationId));
|
|
65
|
+
const [errorsOut, classCount] = emitTSTypedErrors(registerable);
|
|
66
|
+
let b = "";
|
|
67
|
+
b += "// AUTO-GENERATED by `palbe-gen` \u2014 DO NOT EDIT.\n";
|
|
68
|
+
b += "// Regenerate: palbe-gen (or automatically via the predev/prebuild script)\n";
|
|
69
|
+
const typeImports = [];
|
|
70
|
+
if (classCount > 0) typeImports.push("BackendError");
|
|
71
|
+
if (registerable.length > 0) typeImports.push("CallOptions");
|
|
72
|
+
if (typeImports.length > 0) {
|
|
73
|
+
b += `import type { ${typeImports.join(", ")} } from '@palbase/web';
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
b += "import { __configure, __registerNamespaces } from '@palbase/web/internal';\n\n";
|
|
77
|
+
b += emitTSConfigure(cfg);
|
|
78
|
+
b += "\n";
|
|
79
|
+
b += typesOut;
|
|
80
|
+
b += errorsOut;
|
|
81
|
+
b += "// \u2500\u2500 Namespaces \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n";
|
|
82
|
+
for (const ns of skippedNS) {
|
|
83
|
+
b += `// codegen: skipped reserved namespace "${ns}"
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
for (const l of opSkips) {
|
|
87
|
+
b += `${l}
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
for (const c of collisionSkips) {
|
|
91
|
+
b += `${c}
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
b += emitTSRegistration(registerable);
|
|
95
|
+
if (registerable.length > 0) {
|
|
96
|
+
b += "\n";
|
|
97
|
+
b += emitTSAugmentation(registerable);
|
|
98
|
+
}
|
|
99
|
+
return b;
|
|
100
|
+
}
|
|
101
|
+
function emitTSConfigure(cfg) {
|
|
102
|
+
const branch = cfg.branch === "" ? "main" : cfg.branch;
|
|
103
|
+
let b = "__configure({\n";
|
|
104
|
+
b += ` url: ${tsStringLit(cfg.url)},
|
|
105
|
+
`;
|
|
106
|
+
b += ` apiKey: ${tsStringLit(cfg.apiKey)},
|
|
107
|
+
`;
|
|
108
|
+
b += ` appId: ${tsStringLit(cfg.appId)},
|
|
109
|
+
`;
|
|
110
|
+
b += ` branch: ${tsStringLit(branch)},
|
|
111
|
+
`;
|
|
112
|
+
const oauth = renderTSOAuth(cfg.oauth);
|
|
113
|
+
if (oauth !== "") {
|
|
114
|
+
b += " oauth: {\n";
|
|
115
|
+
b += oauth;
|
|
116
|
+
b += " },\n";
|
|
117
|
+
}
|
|
118
|
+
b += "});\n";
|
|
119
|
+
return b;
|
|
120
|
+
}
|
|
121
|
+
function renderTSOAuth(o) {
|
|
122
|
+
if (!o) return "";
|
|
123
|
+
let b = "";
|
|
124
|
+
if (o.apple?.enabled) {
|
|
125
|
+
b += " apple: { enabled: true },\n";
|
|
126
|
+
}
|
|
127
|
+
if (o.google?.enabled && o.google.clientId !== "") {
|
|
128
|
+
b += ` google: { enabled: true, clientId: ${tsStringLit(o.google.clientId)} },
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
return b;
|
|
132
|
+
}
|
|
133
|
+
function filterTSOps(ops) {
|
|
134
|
+
const out = [];
|
|
135
|
+
const skips = [];
|
|
136
|
+
const root = new TSNode();
|
|
137
|
+
for (const op of ops) {
|
|
138
|
+
const segs = opSegments(op.operationId);
|
|
139
|
+
const lastSeg = segs[segs.length - 1];
|
|
140
|
+
if (lastSeg === void 0) continue;
|
|
141
|
+
let skip = false;
|
|
142
|
+
for (const seg of segs.slice(1)) {
|
|
143
|
+
if (tsReservedNested(seg)) {
|
|
144
|
+
skips.push(`// codegen: skipped operation "${op.operationId}" (reserved segment "${seg}")`);
|
|
145
|
+
skip = true;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (skip) continue;
|
|
150
|
+
if (op.input && op.query) {
|
|
151
|
+
skips.push(
|
|
152
|
+
`// codegen: skipped operation "${op.operationId}" (both body and query declared \u2014 unsupported)`
|
|
153
|
+
);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (op.query) {
|
|
157
|
+
const bad = firstNonPrimitiveQueryProp(op.query);
|
|
158
|
+
if (bad !== void 0) {
|
|
159
|
+
skips.push(
|
|
160
|
+
`// codegen: skipped operation "${op.operationId}" (non-primitive query parameter "${bad}")`
|
|
161
|
+
);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
let node = root;
|
|
166
|
+
for (const seg of segs.slice(0, -1)) {
|
|
167
|
+
node = node.childNode(seg);
|
|
168
|
+
if (!node) break;
|
|
169
|
+
}
|
|
170
|
+
if (!node?.addMethod(lastSeg, op)) {
|
|
171
|
+
skips.push(
|
|
172
|
+
`// codegen: skipped operation "${op.operationId}" (key collides with existing namespace/method)`
|
|
173
|
+
);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
out.push(op);
|
|
177
|
+
}
|
|
178
|
+
return [out, skips];
|
|
179
|
+
}
|
|
180
|
+
function firstNonPrimitiveQueryProp(q) {
|
|
181
|
+
for (const p of q.props) {
|
|
182
|
+
switch (p.schema.kind) {
|
|
183
|
+
case "string":
|
|
184
|
+
case "number":
|
|
185
|
+
case "integer":
|
|
186
|
+
case "boolean":
|
|
187
|
+
case "enum":
|
|
188
|
+
break;
|
|
189
|
+
// primitive — serializeQuery handles it
|
|
190
|
+
default:
|
|
191
|
+
return p.name;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return void 0;
|
|
195
|
+
}
|
|
196
|
+
function emitTSTypes(ops) {
|
|
197
|
+
const collisionOps = /* @__PURE__ */ new Set();
|
|
198
|
+
const seenPrefix = /* @__PURE__ */ new Map();
|
|
199
|
+
const collisionSkips = [];
|
|
200
|
+
const lines = [];
|
|
201
|
+
for (const op of ops) {
|
|
202
|
+
const pfx = typePrefix(op.operationId);
|
|
203
|
+
const first = seenPrefix.get(pfx);
|
|
204
|
+
if (first !== void 0) {
|
|
205
|
+
collisionSkips.push(`// codegen: skipped type "${pfx}" (collides with operation "${first}")`);
|
|
206
|
+
collisionOps.add(op.operationId);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
seenPrefix.set(pfx, op.operationId);
|
|
210
|
+
if (op.input) {
|
|
211
|
+
lines.push(...tsNamedTypeLines(`${pfx}Request`, op.input));
|
|
212
|
+
}
|
|
213
|
+
if (op.query) {
|
|
214
|
+
lines.push(...tsNamedTypeLines(`${pfx}Query`, op.query));
|
|
215
|
+
}
|
|
216
|
+
if (op.output) {
|
|
217
|
+
lines.push(...tsResponseTypeLines(pfx, op.output));
|
|
218
|
+
}
|
|
219
|
+
const [liftable] = tsLiftableErrors(op);
|
|
220
|
+
for (const e of liftable) {
|
|
221
|
+
if (!e.data) continue;
|
|
222
|
+
const dataName = `${pfx}${typeNameOf(e.name)}Data`;
|
|
223
|
+
lines.push(`/** ${e.code} (${e.status}): ${e.description} */`);
|
|
224
|
+
lines.push(...tsNamedTypeLines(dataName, e.data));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (lines.length === 0) {
|
|
228
|
+
return ["", collisionSkips, collisionOps];
|
|
229
|
+
}
|
|
230
|
+
let b = "// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n";
|
|
231
|
+
for (const l of lines) {
|
|
232
|
+
b += `${l}
|
|
233
|
+
`;
|
|
234
|
+
}
|
|
235
|
+
return [b, collisionSkips, collisionOps];
|
|
236
|
+
}
|
|
237
|
+
function tsNamedTypeLines(name, s) {
|
|
238
|
+
if (s.kind === "object") {
|
|
239
|
+
return tsInterfaceLines(name, s);
|
|
240
|
+
}
|
|
241
|
+
return [`export type ${name} = ${tsTypeOf(s)};`, ""];
|
|
242
|
+
}
|
|
243
|
+
function tsResponseTypeLines(pfx, s) {
|
|
244
|
+
if (s.kind === "array" && s.elem && s.elem.kind === "object") {
|
|
245
|
+
return [
|
|
246
|
+
...tsInterfaceLines(`${pfx}ResponseItem`, s.elem),
|
|
247
|
+
`export type ${pfx}Response = ${pfx}ResponseItem[];`,
|
|
248
|
+
""
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
return tsNamedTypeLines(`${pfx}Response`, s);
|
|
252
|
+
}
|
|
253
|
+
function tsInterfaceLines(name, s) {
|
|
254
|
+
const lines = [`export interface ${name} {`];
|
|
255
|
+
for (const p of s.props) {
|
|
256
|
+
if (p.name === "__proto__") {
|
|
257
|
+
lines.push(' // codegen: skipped property "__proto__"');
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const opt = p.required ? "" : "?";
|
|
261
|
+
lines.push(` ${tsPropKey(p.name)}${opt}: ${tsTypeOf(p.schema)};`);
|
|
262
|
+
}
|
|
263
|
+
lines.push("}", "");
|
|
264
|
+
return lines;
|
|
265
|
+
}
|
|
266
|
+
function tsTypeOf(s) {
|
|
267
|
+
switch (s.kind) {
|
|
268
|
+
case "string":
|
|
269
|
+
return s.nullable ? "string | null" : "string";
|
|
270
|
+
case "number":
|
|
271
|
+
case "integer":
|
|
272
|
+
return s.nullable ? "number | null" : "number";
|
|
273
|
+
case "boolean":
|
|
274
|
+
return s.nullable ? "boolean | null" : "boolean";
|
|
275
|
+
case "enum": {
|
|
276
|
+
const union = s.enumVals.map(tsStringLit).join(" | ");
|
|
277
|
+
return s.nullable ? `(${union}) | null` : union;
|
|
278
|
+
}
|
|
279
|
+
case "array": {
|
|
280
|
+
if (!s.elem) {
|
|
281
|
+
return s.nullable ? "unknown[] | null" : "unknown[]";
|
|
282
|
+
}
|
|
283
|
+
let elem = tsTypeOf(s.elem);
|
|
284
|
+
if (elem.includes(" | ") || elem.startsWith("(")) {
|
|
285
|
+
elem = `(${elem})`;
|
|
286
|
+
}
|
|
287
|
+
const arr = `${elem}[]`;
|
|
288
|
+
return s.nullable ? `${arr} | null` : arr;
|
|
289
|
+
}
|
|
290
|
+
case "object": {
|
|
291
|
+
const inline = tsInlineObject(s);
|
|
292
|
+
return s.nullable ? `${inline} | null` : inline;
|
|
293
|
+
}
|
|
294
|
+
default:
|
|
295
|
+
return s.nullable ? "unknown | null" : "unknown";
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function tsInlineObject(s) {
|
|
299
|
+
const parts = [];
|
|
300
|
+
for (const p of s.props) {
|
|
301
|
+
if (p.name === "__proto__") continue;
|
|
302
|
+
const opt = p.required ? "" : "?";
|
|
303
|
+
parts.push(`${tsPropKey(p.name)}${opt}: ${tsTypeOf(p.schema)}`);
|
|
304
|
+
}
|
|
305
|
+
if (parts.length === 0) return "Record<string, unknown>";
|
|
306
|
+
return `{ ${parts.join("; ")} }`;
|
|
307
|
+
}
|
|
308
|
+
var tsInfraReservedCodes = /* @__PURE__ */ new Set([
|
|
309
|
+
"not_configured",
|
|
310
|
+
"network_error",
|
|
311
|
+
"decode_error",
|
|
312
|
+
"validation_error",
|
|
313
|
+
"unauthorized",
|
|
314
|
+
"rate_limited",
|
|
315
|
+
"invalid_endpoint_name",
|
|
316
|
+
"unsupported_get_input",
|
|
317
|
+
"missing_path_param",
|
|
318
|
+
"unexpected_argument",
|
|
319
|
+
"invalid_query_value",
|
|
320
|
+
"reserved_namespace",
|
|
321
|
+
"invalid_namespace_tree",
|
|
322
|
+
"aborted",
|
|
323
|
+
"http_error"
|
|
324
|
+
]);
|
|
325
|
+
function tsLiftableErrors(op) {
|
|
326
|
+
if (op.errors.length === 0) return [[], []];
|
|
327
|
+
const sorted = [...op.errors].sort((a, b) => a.code < b.code ? -1 : a.code > b.code ? 1 : 0);
|
|
328
|
+
const seenCode = /* @__PURE__ */ new Set();
|
|
329
|
+
const seenClass = /* @__PURE__ */ new Map();
|
|
330
|
+
const liftable = [];
|
|
331
|
+
const comments = [];
|
|
332
|
+
for (const e of sorted) {
|
|
333
|
+
if (e.code === "__proto__") {
|
|
334
|
+
comments.push('// codegen: skipped error code "__proto__"');
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (tsInfraReservedCodes.has(e.code)) {
|
|
338
|
+
comments.push(`// codegen: skipped error code "${e.code}" (reserved infra code)`);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (seenCode.has(e.code)) {
|
|
342
|
+
comments.push(`// codegen: skipped duplicate error code "${e.code}"`);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const stem = typeNameOf(e.name);
|
|
346
|
+
const first = seenClass.get(stem);
|
|
347
|
+
if (first !== void 0) {
|
|
348
|
+
comments.push(`// codegen: skipped error "${e.name}" (class name collides with "${first}")`);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
seenCode.add(e.code);
|
|
352
|
+
seenClass.set(stem, e.name);
|
|
353
|
+
liftable.push(e);
|
|
354
|
+
}
|
|
355
|
+
return [liftable, comments];
|
|
356
|
+
}
|
|
357
|
+
function emitTSTypedErrors(ops) {
|
|
358
|
+
let count = 0;
|
|
359
|
+
const lines = [];
|
|
360
|
+
for (const op of ops) {
|
|
361
|
+
const pfx = typePrefix(op.operationId);
|
|
362
|
+
const [liftable] = tsLiftableErrors(op);
|
|
363
|
+
for (const e of liftable) {
|
|
364
|
+
const className = `${pfx}${typeNameOf(e.name)}Error`;
|
|
365
|
+
const hasData = e.data !== void 0;
|
|
366
|
+
lines.push(`export class ${className} extends Error {`);
|
|
367
|
+
lines.push(` readonly name = '${className}';`);
|
|
368
|
+
lines.push(` readonly code = '${e.code}';`);
|
|
369
|
+
lines.push(` readonly status = ${e.status};`);
|
|
370
|
+
if (hasData) {
|
|
371
|
+
lines.push(` readonly data: ${pfx}${typeNameOf(e.name)}Data;`);
|
|
372
|
+
}
|
|
373
|
+
lines.push(" readonly cause: BackendError;");
|
|
374
|
+
lines.push(" constructor(cause: BackendError) {");
|
|
375
|
+
lines.push(" super(cause.message);");
|
|
376
|
+
lines.push(" this.cause = cause;");
|
|
377
|
+
if (hasData) {
|
|
378
|
+
lines.push(` this.data = cause.data as ${pfx}${typeNameOf(e.name)}Data;`);
|
|
379
|
+
}
|
|
380
|
+
lines.push(" }");
|
|
381
|
+
lines.push("}");
|
|
382
|
+
lines.push("");
|
|
383
|
+
count++;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (count === 0) return ["", 0];
|
|
387
|
+
let b = "// \u2500\u2500 Typed errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n";
|
|
388
|
+
for (const l of lines) {
|
|
389
|
+
b += `${l}
|
|
390
|
+
`;
|
|
391
|
+
}
|
|
392
|
+
return [b, count];
|
|
393
|
+
}
|
|
394
|
+
var TSNode = class _TSNode {
|
|
395
|
+
entries = [];
|
|
396
|
+
index = /* @__PURE__ */ new Map();
|
|
397
|
+
// childNode returns (creating if needed) the nested namespace node for key.
|
|
398
|
+
// Returns undefined if a METHOD entry already occupies the key (kind
|
|
399
|
+
// mismatch).
|
|
400
|
+
childNode(key) {
|
|
401
|
+
const e = this.index.get(key);
|
|
402
|
+
if (e) return e.child;
|
|
403
|
+
const child = new _TSNode();
|
|
404
|
+
const entry = { key, child };
|
|
405
|
+
this.entries.push(entry);
|
|
406
|
+
this.index.set(key, entry);
|
|
407
|
+
return child;
|
|
408
|
+
}
|
|
409
|
+
// addMethod registers a method at key. Returns false if the key is already
|
|
410
|
+
// occupied (either by another method or by a namespace node).
|
|
411
|
+
addMethod(key, op) {
|
|
412
|
+
if (this.index.has(key)) return false;
|
|
413
|
+
const entry = { key, op };
|
|
414
|
+
this.entries.push(entry);
|
|
415
|
+
this.index.set(key, entry);
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
function buildTSTrie(ops) {
|
|
420
|
+
const root = new TSNode();
|
|
421
|
+
for (const op of ops) {
|
|
422
|
+
const segs = opSegments(op.operationId);
|
|
423
|
+
const lastSeg = segs[segs.length - 1];
|
|
424
|
+
if (lastSeg === void 0) continue;
|
|
425
|
+
let node = root;
|
|
426
|
+
for (const seg of segs.slice(0, -1)) {
|
|
427
|
+
node = node.childNode(seg);
|
|
428
|
+
if (!node) break;
|
|
429
|
+
}
|
|
430
|
+
if (node) {
|
|
431
|
+
node.addMethod(lastSeg, op);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return root;
|
|
435
|
+
}
|
|
436
|
+
function emitTSRegistration(ops) {
|
|
437
|
+
if (ops.length === 0) {
|
|
438
|
+
return "__registerNamespaces({});\n";
|
|
439
|
+
}
|
|
440
|
+
let b = "__registerNamespaces({\n";
|
|
441
|
+
b += renderTSNode(buildTSTrie(ops), 2);
|
|
442
|
+
b += "});\n";
|
|
443
|
+
return b;
|
|
444
|
+
}
|
|
445
|
+
function renderTSNode(node, indentSpaces) {
|
|
446
|
+
const ind = " ".repeat(indentSpaces);
|
|
447
|
+
let b = "";
|
|
448
|
+
for (const e of node.entries) {
|
|
449
|
+
if (e.child) {
|
|
450
|
+
b += `${ind}${tsPropKey(e.key)}: {
|
|
451
|
+
`;
|
|
452
|
+
b += renderTSNode(e.child, indentSpaces + 2);
|
|
453
|
+
b += `${ind}},
|
|
454
|
+
`;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (e.op) {
|
|
458
|
+
b += renderTSDescriptor(e.op, e.key, indentSpaces);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return b;
|
|
462
|
+
}
|
|
463
|
+
function renderTSDescriptor(op, methodSeg, indentSpaces) {
|
|
464
|
+
const ind = " ".repeat(indentSpaces);
|
|
465
|
+
const key = tsPropKey(methodSeg);
|
|
466
|
+
const [liftable, errComments] = tsLiftableErrors(op);
|
|
467
|
+
const hasErrors = liftable.length > 0;
|
|
468
|
+
const hasPathParams = op.pathParams.length > 0;
|
|
469
|
+
const hasInput = op.input !== void 0;
|
|
470
|
+
const hasQuery = op.query !== void 0;
|
|
471
|
+
let inputVal = "";
|
|
472
|
+
if (!hasInput && !hasQuery) {
|
|
473
|
+
inputVal = "none";
|
|
474
|
+
} else if (hasQuery) {
|
|
475
|
+
inputVal = "query";
|
|
476
|
+
}
|
|
477
|
+
let b = "";
|
|
478
|
+
if (!hasErrors) {
|
|
479
|
+
for (const c of errComments) {
|
|
480
|
+
b += `${ind}${c}
|
|
481
|
+
`;
|
|
482
|
+
}
|
|
483
|
+
const parts = [`method: ${tsStringLit(op.method)}`, `path: ${tsStringLit(op.path)}`];
|
|
484
|
+
if (hasPathParams) {
|
|
485
|
+
parts.push(`pathParams: [${renderTSStringArray(op.pathParams)}]`);
|
|
486
|
+
}
|
|
487
|
+
if (inputVal !== "") {
|
|
488
|
+
parts.push(`input: ${tsStringLit(inputVal)}`);
|
|
489
|
+
}
|
|
490
|
+
b += `${ind}${key}: { ${parts.join(", ")} },
|
|
491
|
+
`;
|
|
492
|
+
return b;
|
|
493
|
+
}
|
|
494
|
+
const innerInd = " ".repeat(indentSpaces + 2);
|
|
495
|
+
b += `${ind}${key}: {
|
|
496
|
+
`;
|
|
497
|
+
b += `${innerInd}method: ${tsStringLit(op.method)},
|
|
498
|
+
`;
|
|
499
|
+
b += `${innerInd}path: ${tsStringLit(op.path)},
|
|
500
|
+
`;
|
|
501
|
+
if (hasPathParams) {
|
|
502
|
+
b += `${innerInd}pathParams: [${renderTSStringArray(op.pathParams)}],
|
|
503
|
+
`;
|
|
504
|
+
}
|
|
505
|
+
if (inputVal !== "") {
|
|
506
|
+
b += `${innerInd}input: ${tsStringLit(inputVal)},
|
|
507
|
+
`;
|
|
508
|
+
}
|
|
509
|
+
for (const c of errComments) {
|
|
510
|
+
b += `${innerInd}${c}
|
|
511
|
+
`;
|
|
512
|
+
}
|
|
513
|
+
b += `${innerInd}errors: { `;
|
|
514
|
+
liftable.forEach((e, i) => {
|
|
515
|
+
if (i > 0) b += ", ";
|
|
516
|
+
const className = `${typePrefix(op.operationId)}${typeNameOf(e.name)}Error`;
|
|
517
|
+
b += `${tsPropKey(e.code)}: (e) => new ${className}(e)`;
|
|
518
|
+
});
|
|
519
|
+
b += " },\n";
|
|
520
|
+
b += `${ind}},
|
|
521
|
+
`;
|
|
522
|
+
return b;
|
|
523
|
+
}
|
|
524
|
+
function renderTSStringArray(ss) {
|
|
525
|
+
return ss.map(tsStringLit).join(", ");
|
|
526
|
+
}
|
|
527
|
+
function emitTSAugmentation(ops) {
|
|
528
|
+
let b = "declare module '@palbase/web' {\n";
|
|
529
|
+
b += " interface PB {\n";
|
|
530
|
+
b += renderTSAugNode(buildTSTrie(ops), 4);
|
|
531
|
+
b += " }\n";
|
|
532
|
+
b += "}\n";
|
|
533
|
+
return b;
|
|
534
|
+
}
|
|
535
|
+
function renderTSAugNode(node, indentSpaces) {
|
|
536
|
+
const ind = " ".repeat(indentSpaces);
|
|
537
|
+
let b = "";
|
|
538
|
+
for (const e of node.entries) {
|
|
539
|
+
if (e.child) {
|
|
540
|
+
b += `${ind}${tsPropKey(e.key)}: {
|
|
541
|
+
`;
|
|
542
|
+
b += renderTSAugNode(e.child, indentSpaces + 2);
|
|
543
|
+
b += `${ind}};
|
|
544
|
+
`;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (e.op) {
|
|
548
|
+
b += `${ind}${tsPropKey(e.key)}${renderTSMethodSignature(e.op)};
|
|
549
|
+
`;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return b;
|
|
553
|
+
}
|
|
554
|
+
function renderTSMethodSignature(op) {
|
|
555
|
+
const pfx = typePrefix(op.operationId);
|
|
556
|
+
const args = [];
|
|
557
|
+
const used = /* @__PURE__ */ new Set();
|
|
558
|
+
for (const p of op.pathParams) {
|
|
559
|
+
args.push(`${tsParamIdent(p, used)}: string`);
|
|
560
|
+
}
|
|
561
|
+
if (op.input) {
|
|
562
|
+
args.push(`input: ${pfx}Request`);
|
|
563
|
+
} else if (op.query) {
|
|
564
|
+
args.push(`query: ${pfx}Query`);
|
|
565
|
+
}
|
|
566
|
+
args.push(buildOptionsArg(op.headers));
|
|
567
|
+
const ret = op.output ? `Promise<${pfx}Response>` : "Promise<void>";
|
|
568
|
+
return `(${args.join(", ")}): ${ret}`;
|
|
569
|
+
}
|
|
570
|
+
function buildOptionsArg(headers) {
|
|
571
|
+
if (!headers || headers.props.length === 0) {
|
|
572
|
+
return "options?: CallOptions";
|
|
573
|
+
}
|
|
574
|
+
const anyRequired = headers.props.some((p) => p.required);
|
|
575
|
+
const hParts = headers.props.map((p) => {
|
|
576
|
+
const opt = p.required ? "" : "?";
|
|
577
|
+
return `${tsPropKey(p.name)}${opt}: ${tsTypeOf(p.schema)}`;
|
|
578
|
+
});
|
|
579
|
+
const headersType = `{ ${hParts.join("; ")} }`;
|
|
580
|
+
if (anyRequired) {
|
|
581
|
+
return `options: CallOptions & { headers: ${headersType} }`;
|
|
582
|
+
}
|
|
583
|
+
return `options?: CallOptions & { headers?: ${headersType} }`;
|
|
584
|
+
}
|
|
585
|
+
function tsParamIdent(name, used) {
|
|
586
|
+
let id = sanitize(name, false);
|
|
587
|
+
if (tsReservedWords.has(id) || id === "input" || id === "query" || id === "options") {
|
|
588
|
+
id += "_";
|
|
589
|
+
}
|
|
590
|
+
let candidate = id;
|
|
591
|
+
for (let n = 2; used.has(candidate); n++) {
|
|
592
|
+
candidate = id + n;
|
|
593
|
+
}
|
|
594
|
+
used.add(candidate);
|
|
595
|
+
return candidate;
|
|
596
|
+
}
|
|
597
|
+
var tsReservedWords = /* @__PURE__ */ new Set([
|
|
598
|
+
"await",
|
|
599
|
+
"break",
|
|
600
|
+
"case",
|
|
601
|
+
"catch",
|
|
602
|
+
"class",
|
|
603
|
+
"const",
|
|
604
|
+
"continue",
|
|
605
|
+
"debugger",
|
|
606
|
+
"default",
|
|
607
|
+
"delete",
|
|
608
|
+
"do",
|
|
609
|
+
"else",
|
|
610
|
+
"enum",
|
|
611
|
+
"export",
|
|
612
|
+
"extends",
|
|
613
|
+
"false",
|
|
614
|
+
"finally",
|
|
615
|
+
"for",
|
|
616
|
+
"function",
|
|
617
|
+
"if",
|
|
618
|
+
"import",
|
|
619
|
+
"in",
|
|
620
|
+
"instanceof",
|
|
621
|
+
"new",
|
|
622
|
+
"null",
|
|
623
|
+
"return",
|
|
624
|
+
"super",
|
|
625
|
+
"switch",
|
|
626
|
+
"this",
|
|
627
|
+
"throw",
|
|
628
|
+
"true",
|
|
629
|
+
"try",
|
|
630
|
+
"typeof",
|
|
631
|
+
"var",
|
|
632
|
+
"void",
|
|
633
|
+
"while",
|
|
634
|
+
"with",
|
|
635
|
+
"yield",
|
|
636
|
+
// strict-mode reserved / restricted binding names
|
|
637
|
+
"implements",
|
|
638
|
+
"interface",
|
|
639
|
+
"let",
|
|
640
|
+
"package",
|
|
641
|
+
"private",
|
|
642
|
+
"protected",
|
|
643
|
+
"public",
|
|
644
|
+
"static",
|
|
645
|
+
"arguments",
|
|
646
|
+
"eval"
|
|
647
|
+
]);
|
|
648
|
+
var tsBareKeyRe = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
649
|
+
function tsPropKey(name) {
|
|
650
|
+
return tsBareKeyRe.test(name) ? name : tsStringLit(name);
|
|
651
|
+
}
|
|
652
|
+
function tsStringLit(s) {
|
|
653
|
+
return `'${s.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\n", "\\n").replaceAll("\r", "\\r")}'`;
|
|
654
|
+
}
|
|
655
|
+
function tsReservedTopLevel(seg) {
|
|
656
|
+
switch (seg.toLowerCase()) {
|
|
657
|
+
case "auth":
|
|
658
|
+
case "analytics":
|
|
659
|
+
case "flags":
|
|
660
|
+
case "realtime":
|
|
661
|
+
return true;
|
|
662
|
+
default:
|
|
663
|
+
return tsReservedWebOnly(seg);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function tsReservedNested(seg) {
|
|
667
|
+
if (seg === "then") return true;
|
|
668
|
+
return tsObjectPrototypeMember(seg);
|
|
669
|
+
}
|
|
670
|
+
function tsReservedWebOnly(seg) {
|
|
671
|
+
switch (seg) {
|
|
672
|
+
case "call":
|
|
673
|
+
case "upload":
|
|
674
|
+
case "then":
|
|
675
|
+
return true;
|
|
676
|
+
default:
|
|
677
|
+
return tsObjectPrototypeMember(seg);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function tsObjectPrototypeMember(seg) {
|
|
681
|
+
switch (seg) {
|
|
682
|
+
case "constructor":
|
|
683
|
+
case "hasOwnProperty":
|
|
684
|
+
case "isPrototypeOf":
|
|
685
|
+
case "propertyIsEnumerable":
|
|
686
|
+
case "toLocaleString":
|
|
687
|
+
case "toString":
|
|
688
|
+
case "valueOf":
|
|
689
|
+
case "__proto__":
|
|
690
|
+
case "__defineGetter__":
|
|
691
|
+
case "__defineSetter__":
|
|
692
|
+
case "__lookupGetter__":
|
|
693
|
+
case "__lookupSetter__":
|
|
694
|
+
return true;
|
|
695
|
+
default:
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// src/gen/parser.ts
|
|
701
|
+
function asObject(v) {
|
|
702
|
+
if (typeof v === "object" && v !== null && !Array.isArray(v)) return v;
|
|
703
|
+
return void 0;
|
|
704
|
+
}
|
|
705
|
+
function asString(v) {
|
|
706
|
+
return typeof v === "string" ? v : "";
|
|
707
|
+
}
|
|
708
|
+
function parseOpenAPI(specText) {
|
|
709
|
+
let root;
|
|
710
|
+
try {
|
|
711
|
+
root = asObject(JSON.parse(specText));
|
|
712
|
+
} catch (e) {
|
|
713
|
+
throw new Error(`openapi.json is not valid JSON: ${e instanceof Error ? e.message : e}`);
|
|
714
|
+
}
|
|
715
|
+
const paths = asObject(root?.paths);
|
|
716
|
+
if (!paths) {
|
|
717
|
+
throw new Error("openapi.json has no `paths`");
|
|
718
|
+
}
|
|
719
|
+
const ops = [];
|
|
720
|
+
for (const [path, item] of Object.entries(paths)) {
|
|
721
|
+
const methods = asObject(item);
|
|
722
|
+
if (!methods) continue;
|
|
723
|
+
for (const [method, raw] of Object.entries(methods)) {
|
|
724
|
+
const op = asObject(raw);
|
|
725
|
+
if (!op) continue;
|
|
726
|
+
const opId = asString(op.operationId);
|
|
727
|
+
if (opId === "") continue;
|
|
728
|
+
ops.push({
|
|
729
|
+
operationId: opId,
|
|
730
|
+
method: method.toUpperCase(),
|
|
731
|
+
path,
|
|
732
|
+
pathParams: pathParamNames(path),
|
|
733
|
+
input: requestSchema(op),
|
|
734
|
+
output: responseSchema(op),
|
|
735
|
+
headers: parametersSchemaIn(op, "header"),
|
|
736
|
+
query: parametersSchemaIn(op, "query"),
|
|
737
|
+
errors: declaredErrors(op)
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
ops.sort((a, b) => a.operationId < b.operationId ? -1 : a.operationId > b.operationId ? 1 : 0);
|
|
742
|
+
return ops;
|
|
743
|
+
}
|
|
744
|
+
function declaredErrors(op) {
|
|
745
|
+
const extRaw = asObject(op["x-palbase-errors"]);
|
|
746
|
+
if (!extRaw) return [];
|
|
747
|
+
const responses = asObject(op.responses);
|
|
748
|
+
const out = [];
|
|
749
|
+
for (const [name, raw] of Object.entries(extRaw)) {
|
|
750
|
+
const entry = asObject(raw);
|
|
751
|
+
if (!entry) continue;
|
|
752
|
+
const status = typeof entry.status === "number" ? Math.trunc(entry.status) : 0;
|
|
753
|
+
const code = asString(entry.code);
|
|
754
|
+
const description = asString(entry.description);
|
|
755
|
+
const hasData = entry.hasData === true;
|
|
756
|
+
if (code === "" || status === 0) continue;
|
|
757
|
+
const def = { name, code, status, description };
|
|
758
|
+
if (hasData) {
|
|
759
|
+
def.data = errorDataSchema(responses, status, code);
|
|
760
|
+
}
|
|
761
|
+
out.push(def);
|
|
762
|
+
}
|
|
763
|
+
out.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
764
|
+
return out;
|
|
765
|
+
}
|
|
766
|
+
function errorDataSchema(responses, status, code) {
|
|
767
|
+
const resp = asObject(responses?.[String(status)]);
|
|
768
|
+
const jsonCt = asObject(asObject(resp?.content)?.["application/json"]);
|
|
769
|
+
const schema = asObject(jsonCt?.schema);
|
|
770
|
+
if (!schema) return void 0;
|
|
771
|
+
const variants = schema.oneOf;
|
|
772
|
+
if (Array.isArray(variants)) {
|
|
773
|
+
for (const v of variants) {
|
|
774
|
+
const vm = asObject(v);
|
|
775
|
+
if (!vm) continue;
|
|
776
|
+
const errProp = asObject(asObject(vm.properties)?.error);
|
|
777
|
+
if (errProp && asString(errProp.const) === code) {
|
|
778
|
+
return extractDataProperty(vm);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return void 0;
|
|
782
|
+
}
|
|
783
|
+
return extractDataProperty(schema);
|
|
784
|
+
}
|
|
785
|
+
function extractDataProperty(schema) {
|
|
786
|
+
const dm = asObject(asObject(schema.properties)?.data);
|
|
787
|
+
if (!dm) return void 0;
|
|
788
|
+
return parseSchema(dm);
|
|
789
|
+
}
|
|
790
|
+
function requestSchema(op) {
|
|
791
|
+
const body = asObject(op.requestBody);
|
|
792
|
+
if (!body) return void 0;
|
|
793
|
+
return schemaFromContent(body.content);
|
|
794
|
+
}
|
|
795
|
+
function parametersSchemaIn(op, where) {
|
|
796
|
+
const paramsRaw = op.parameters;
|
|
797
|
+
if (!Array.isArray(paramsRaw) || paramsRaw.length === 0) return void 0;
|
|
798
|
+
const props = [];
|
|
799
|
+
for (const p of paramsRaw) {
|
|
800
|
+
const pm = asObject(p);
|
|
801
|
+
if (!pm) continue;
|
|
802
|
+
if (asString(pm.in) !== where) continue;
|
|
803
|
+
const name = asString(pm.name);
|
|
804
|
+
if (name === "") continue;
|
|
805
|
+
const required = pm.required === true;
|
|
806
|
+
const sm = asObject(pm.schema);
|
|
807
|
+
const ps = sm ? parseSchema(sm) : emptySchema("string");
|
|
808
|
+
props.push({ name, schema: ps, required });
|
|
809
|
+
}
|
|
810
|
+
if (props.length === 0) return void 0;
|
|
811
|
+
props.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
812
|
+
return { ...emptySchema("object"), props };
|
|
813
|
+
}
|
|
814
|
+
function pathParamNames(path) {
|
|
815
|
+
const out = [];
|
|
816
|
+
let rest = path;
|
|
817
|
+
for (; ; ) {
|
|
818
|
+
const open = rest.indexOf("{");
|
|
819
|
+
if (open < 0) break;
|
|
820
|
+
const close = rest.indexOf("}", open);
|
|
821
|
+
if (close < 0) break;
|
|
822
|
+
const name = rest.slice(open + 1, close);
|
|
823
|
+
if (name !== "") out.push(name);
|
|
824
|
+
rest = rest.slice(close + 1);
|
|
825
|
+
}
|
|
826
|
+
return out;
|
|
827
|
+
}
|
|
828
|
+
function responseSchema(op) {
|
|
829
|
+
const responses = asObject(op.responses);
|
|
830
|
+
if (!responses) return void 0;
|
|
831
|
+
const others = [];
|
|
832
|
+
for (const code of Object.keys(responses)) {
|
|
833
|
+
if (code.startsWith("2") && code !== "200" && code !== "201") others.push(code);
|
|
834
|
+
}
|
|
835
|
+
others.sort();
|
|
836
|
+
for (const code of ["200", "201", ...others]) {
|
|
837
|
+
const resp = asObject(responses[code]);
|
|
838
|
+
if (!resp) continue;
|
|
839
|
+
const s = schemaFromContent(resp.content);
|
|
840
|
+
if (s) return s;
|
|
841
|
+
}
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
function schemaFromContent(content) {
|
|
845
|
+
const jsonCt = asObject(asObject(content)?.["application/json"]);
|
|
846
|
+
const schema = asObject(jsonCt?.schema);
|
|
847
|
+
if (!schema) return void 0;
|
|
848
|
+
if ("$ref" in schema) return void 0;
|
|
849
|
+
return parseSchema(schema);
|
|
850
|
+
}
|
|
851
|
+
function emptySchema(kind) {
|
|
852
|
+
return { kind, nullable: false, props: [], enumVals: [] };
|
|
853
|
+
}
|
|
854
|
+
function parseSchema(s) {
|
|
855
|
+
let nullable = s.nullable === true;
|
|
856
|
+
const enumRaw = s.enum;
|
|
857
|
+
if (Array.isArray(enumRaw)) {
|
|
858
|
+
const cases = [];
|
|
859
|
+
let allStrings = true;
|
|
860
|
+
for (const v of enumRaw) {
|
|
861
|
+
if (typeof v === "string") {
|
|
862
|
+
cases.push(v);
|
|
863
|
+
} else {
|
|
864
|
+
allStrings = false;
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
if (allStrings && cases.length > 0) {
|
|
869
|
+
return { ...emptySchema("enum"), nullable, enumVals: cases };
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
let typ = asString(s.type);
|
|
873
|
+
if (typ === "" && Array.isArray(s.type)) {
|
|
874
|
+
for (const v of s.type) {
|
|
875
|
+
if (typeof v !== "string") continue;
|
|
876
|
+
if (v === "null") {
|
|
877
|
+
nullable = true;
|
|
878
|
+
} else if (typ === "") {
|
|
879
|
+
typ = v;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
switch (typ) {
|
|
884
|
+
case "string":
|
|
885
|
+
case "number":
|
|
886
|
+
case "integer":
|
|
887
|
+
case "boolean":
|
|
888
|
+
return { ...emptySchema(typ), nullable };
|
|
889
|
+
case "array": {
|
|
890
|
+
const items = asObject(s.items);
|
|
891
|
+
const elem = items ? parseSchema(items) : emptySchema("any");
|
|
892
|
+
return { ...emptySchema("array"), nullable, elem };
|
|
893
|
+
}
|
|
894
|
+
case "object":
|
|
895
|
+
return parseObject(s, nullable);
|
|
896
|
+
default:
|
|
897
|
+
if ("properties" in s) return parseObject(s, nullable);
|
|
898
|
+
return { ...emptySchema("any"), nullable };
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function parseObject(s, nullable) {
|
|
902
|
+
const propsRaw = asObject(s.properties) ?? {};
|
|
903
|
+
const requiredSet = /* @__PURE__ */ new Set();
|
|
904
|
+
if (Array.isArray(s.required)) {
|
|
905
|
+
for (const r of s.required) {
|
|
906
|
+
if (typeof r === "string") requiredSet.add(r);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
const names = Object.keys(propsRaw).sort();
|
|
910
|
+
const props = [];
|
|
911
|
+
for (const name of names) {
|
|
912
|
+
const pm = asObject(propsRaw[name]);
|
|
913
|
+
const ps = pm ? parseSchema(pm) : emptySchema("any");
|
|
914
|
+
props.push({ name, schema: ps, required: requiredSet.has(name) });
|
|
915
|
+
}
|
|
916
|
+
return { ...emptySchema("object"), nullable, props };
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/gen/generate.ts
|
|
920
|
+
var USAGE = `Usage: palbe-gen [--dir Palbase] [--out palbe.gen.ts] [--url <specOrigin>] [--soft] [--watch]
|
|
921
|
+
|
|
922
|
+
Generate the typed palbe.gen.ts web client for the @palbase/web SDK.
|
|
923
|
+
|
|
924
|
+
--dir <dir> Directory holding the committed openapi.json and required
|
|
925
|
+
palbase-config.json written by \`palbase web link\`
|
|
926
|
+
(default: Palbase)
|
|
927
|
+
--out <file> Output file (default: palbe.gen.ts)
|
|
928
|
+
--url <origin> Fetch the spec from <origin>/openapi.json instead of --dir
|
|
929
|
+
(local \`palbase serve\` dev flow, e.g. http://localhost:4003)
|
|
930
|
+
--soft Never fail: print a warning and exit 0 on any error
|
|
931
|
+
(for predev/prebuild hooks)
|
|
932
|
+
--watch Poll the --url origin (default http://localhost:4003) every
|
|
933
|
+
second and regenerate whenever the spec changes
|
|
934
|
+
-h, --help Show this help`;
|
|
935
|
+
function errMsg(e) {
|
|
936
|
+
return e instanceof Error ? e.message : String(e);
|
|
937
|
+
}
|
|
938
|
+
var HttpStatusError = class extends Error {
|
|
939
|
+
status;
|
|
940
|
+
constructor(status, url) {
|
|
941
|
+
super(`GET ${url}: HTTP ${status}`);
|
|
942
|
+
this.status = status;
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
async function fetchSpec(url) {
|
|
946
|
+
let res;
|
|
947
|
+
try {
|
|
948
|
+
res = await fetch(url, { signal: AbortSignal.timeout(3e3) });
|
|
949
|
+
} catch (e) {
|
|
950
|
+
throw new Error(`GET ${url}: ${errMsg(e)}`);
|
|
951
|
+
}
|
|
952
|
+
if (!res.ok) throw new HttpStatusError(res.status, url);
|
|
953
|
+
return res.text();
|
|
954
|
+
}
|
|
955
|
+
function readWebConfig(dir) {
|
|
956
|
+
const path = join(dir, "palbase-config.json");
|
|
957
|
+
let text;
|
|
958
|
+
try {
|
|
959
|
+
text = readFileSync(path, "utf8");
|
|
960
|
+
} catch {
|
|
961
|
+
throw new Error(`${path} is required in dir mode \u2014 run \`palbase web link\``);
|
|
962
|
+
}
|
|
963
|
+
let raw;
|
|
964
|
+
try {
|
|
965
|
+
raw = JSON.parse(text);
|
|
966
|
+
} catch (e) {
|
|
967
|
+
throw new Error(`${path} is not valid JSON: ${errMsg(e)}`);
|
|
968
|
+
}
|
|
969
|
+
const obj = raw ?? {};
|
|
970
|
+
const str = (v) => typeof v === "string" ? v : "";
|
|
971
|
+
const required = (field) => {
|
|
972
|
+
const value = str(obj[field]);
|
|
973
|
+
if (value.trim() === "") {
|
|
974
|
+
throw new Error(`${path} is missing nonempty required field ${field}`);
|
|
975
|
+
}
|
|
976
|
+
return value;
|
|
977
|
+
};
|
|
978
|
+
const cfg = {
|
|
979
|
+
url: required("base_url"),
|
|
980
|
+
apiKey: required("api_key"),
|
|
981
|
+
appId: required("app_id"),
|
|
982
|
+
branch: str(obj.branch)
|
|
983
|
+
};
|
|
984
|
+
const oauthRaw = obj.oauth;
|
|
985
|
+
if (oauthRaw && typeof oauthRaw === "object") {
|
|
986
|
+
const oauth = {};
|
|
987
|
+
const apple = oauthRaw.apple;
|
|
988
|
+
if (apple && typeof apple === "object") {
|
|
989
|
+
oauth.apple = { enabled: apple.enabled === true };
|
|
990
|
+
}
|
|
991
|
+
const google = oauthRaw.google;
|
|
992
|
+
if (google && typeof google === "object") {
|
|
993
|
+
oauth.google = {
|
|
994
|
+
enabled: google.enabled === true,
|
|
995
|
+
clientId: str(google.client_id),
|
|
996
|
+
redirectUri: str(google.redirect_uri)
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
cfg.oauth = oauth;
|
|
1000
|
+
}
|
|
1001
|
+
return cfg;
|
|
1002
|
+
}
|
|
1003
|
+
function emitAndWrite(ops, cfg, outFile) {
|
|
1004
|
+
const dir = dirname(outFile);
|
|
1005
|
+
if (dir !== "." && dir !== "") {
|
|
1006
|
+
mkdirSync(dir, { recursive: true });
|
|
1007
|
+
}
|
|
1008
|
+
writeFileSync(outFile, emitTypeScript(ops, cfg));
|
|
1009
|
+
}
|
|
1010
|
+
function existingNonEmpty(outFile) {
|
|
1011
|
+
try {
|
|
1012
|
+
return readFileSync(outFile).length > 0;
|
|
1013
|
+
} catch {
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function writeGenerated(ops, cfg, outFile, out) {
|
|
1018
|
+
if (ops.length === 0) {
|
|
1019
|
+
if (existingNonEmpty(outFile)) {
|
|
1020
|
+
out(
|
|
1021
|
+
`warning: live spec has 0 operations \u2014 keeping existing ${outFile} (fix your controllers and rerun)`
|
|
1022
|
+
);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
out(
|
|
1026
|
+
`warning: live spec has 0 operations \u2014 ${outFile} registers no calls (fix your controllers and rerun)`
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
emitAndWrite(ops, cfg, outFile);
|
|
1030
|
+
out(`\u2713 wrote ${outFile} (${ops.length} operations)`);
|
|
1031
|
+
}
|
|
1032
|
+
async function generateOnce(dir, outFile, url, out) {
|
|
1033
|
+
let specText;
|
|
1034
|
+
let cfg;
|
|
1035
|
+
if (url !== void 0) {
|
|
1036
|
+
const origin = url.replace(/\/+$/, "");
|
|
1037
|
+
specText = await fetchSpec(`${origin}/openapi.json`);
|
|
1038
|
+
cfg = { url: origin, apiKey: "", appId: "", branch: "" };
|
|
1039
|
+
} else {
|
|
1040
|
+
cfg = readWebConfig(dir);
|
|
1041
|
+
const specPath = join(dir, "openapi.json");
|
|
1042
|
+
try {
|
|
1043
|
+
specText = readFileSync(specPath, "utf8");
|
|
1044
|
+
} catch {
|
|
1045
|
+
throw new Error(
|
|
1046
|
+
`no OpenAPI spec at ${specPath} \u2014 run \`palbase spec\` first, or pass --url for a local \`palbase serve\``
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
writeGenerated(parseOpenAPI(specText), cfg, outFile, out);
|
|
1051
|
+
}
|
|
1052
|
+
async function watchLoop(specURL, cfg, outFile, fetchFn, ticks, out) {
|
|
1053
|
+
let lastEmittedHash = "";
|
|
1054
|
+
let lastBadHash = "";
|
|
1055
|
+
let lastDownMsg = "";
|
|
1056
|
+
for await (const _ of ticks) {
|
|
1057
|
+
let specText;
|
|
1058
|
+
try {
|
|
1059
|
+
specText = await fetchFn(specURL);
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
let m = "waiting for palbase serve on :4003\u2026";
|
|
1062
|
+
if (err instanceof HttpStatusError) {
|
|
1063
|
+
m = `local serve responded with an error (HTTP ${err.status}) \u2014 fix \`palbase serve\``;
|
|
1064
|
+
}
|
|
1065
|
+
if (m !== lastDownMsg) {
|
|
1066
|
+
out(m);
|
|
1067
|
+
lastDownMsg = m;
|
|
1068
|
+
}
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
lastDownMsg = "";
|
|
1072
|
+
const h = createHash("sha256").update(specText).digest("hex");
|
|
1073
|
+
if (lastEmittedHash !== "" && h === lastEmittedHash) continue;
|
|
1074
|
+
if (lastBadHash !== "" && h === lastBadHash) continue;
|
|
1075
|
+
let ops;
|
|
1076
|
+
try {
|
|
1077
|
+
ops = parseOpenAPI(specText);
|
|
1078
|
+
} catch (err) {
|
|
1079
|
+
out(`warning: failed to parse spec: ${errMsg(err)}`);
|
|
1080
|
+
lastBadHash = h;
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
if (ops.length === 0 && existingNonEmpty(outFile)) {
|
|
1084
|
+
out(
|
|
1085
|
+
`warning: live spec has 0 operations \u2014 keeping existing ${outFile} (fix your controllers and rerun)`
|
|
1086
|
+
);
|
|
1087
|
+
lastBadHash = h;
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
emitAndWrite(ops, cfg, outFile);
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
out(`warning: codegen error: ${errMsg(err)}`);
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
lastEmittedHash = h;
|
|
1097
|
+
lastBadHash = "";
|
|
1098
|
+
out(`regenerated ${outFile} (${ops.length} operations)`);
|
|
1099
|
+
}
|
|
1100
|
+
out("watch stopped");
|
|
1101
|
+
}
|
|
1102
|
+
function delay(ms, signal) {
|
|
1103
|
+
return new Promise((resolve) => {
|
|
1104
|
+
if (signal.aborted) {
|
|
1105
|
+
resolve();
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
const onAbort = () => {
|
|
1109
|
+
clearTimeout(t);
|
|
1110
|
+
resolve();
|
|
1111
|
+
};
|
|
1112
|
+
const t = setTimeout(() => {
|
|
1113
|
+
signal.removeEventListener("abort", onAbort);
|
|
1114
|
+
resolve();
|
|
1115
|
+
}, ms);
|
|
1116
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
async function* intervalTicks(ms, signal) {
|
|
1120
|
+
while (!signal.aborted) {
|
|
1121
|
+
await delay(ms, signal);
|
|
1122
|
+
if (signal.aborted) return;
|
|
1123
|
+
yield;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
async function runWatch(outFile, url, out) {
|
|
1127
|
+
const origin = (url ?? "http://localhost:4003").replace(/\/+$/, "");
|
|
1128
|
+
const specURL = `${origin}/openapi.json`;
|
|
1129
|
+
const cfg = { url: origin, apiKey: "", appId: "", branch: "" };
|
|
1130
|
+
try {
|
|
1131
|
+
writeGenerated(parseOpenAPI(await fetchSpec(specURL)), cfg, outFile, out);
|
|
1132
|
+
} catch (err) {
|
|
1133
|
+
out(`warning: codegen skipped (${errMsg(err)})`);
|
|
1134
|
+
}
|
|
1135
|
+
const ctrl = new AbortController();
|
|
1136
|
+
const stop = () => ctrl.abort();
|
|
1137
|
+
process.once("SIGINT", stop);
|
|
1138
|
+
process.once("SIGTERM", stop);
|
|
1139
|
+
try {
|
|
1140
|
+
await watchLoop(specURL, cfg, outFile, fetchSpec, intervalTicks(1e3, ctrl.signal), out);
|
|
1141
|
+
} finally {
|
|
1142
|
+
process.removeListener("SIGINT", stop);
|
|
1143
|
+
process.removeListener("SIGTERM", stop);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
var stdoutWriter = (line) => {
|
|
1147
|
+
process.stdout.write(`${line}
|
|
1148
|
+
`);
|
|
1149
|
+
};
|
|
1150
|
+
var stderrWriter = (line) => {
|
|
1151
|
+
process.stderr.write(`${line}
|
|
1152
|
+
`);
|
|
1153
|
+
};
|
|
1154
|
+
function parseCliArgs(argv) {
|
|
1155
|
+
return parseArgs({
|
|
1156
|
+
args: argv,
|
|
1157
|
+
options: {
|
|
1158
|
+
dir: { type: "string", default: "Palbase" },
|
|
1159
|
+
out: { type: "string", default: "palbe.gen.ts" },
|
|
1160
|
+
url: { type: "string" },
|
|
1161
|
+
soft: { type: "boolean", default: false },
|
|
1162
|
+
watch: { type: "boolean", default: false },
|
|
1163
|
+
help: { type: "boolean", short: "h", default: false }
|
|
1164
|
+
}
|
|
1165
|
+
}).values;
|
|
1166
|
+
}
|
|
1167
|
+
async function main(argv, out = stdoutWriter, errOut = stderrWriter) {
|
|
1168
|
+
let values;
|
|
1169
|
+
try {
|
|
1170
|
+
values = parseCliArgs(argv);
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
errOut(`error: ${errMsg(e)}`);
|
|
1173
|
+
errOut(USAGE);
|
|
1174
|
+
return 1;
|
|
1175
|
+
}
|
|
1176
|
+
if (values.help) {
|
|
1177
|
+
out(USAGE);
|
|
1178
|
+
return 0;
|
|
1179
|
+
}
|
|
1180
|
+
try {
|
|
1181
|
+
if (values.watch) {
|
|
1182
|
+
await runWatch(values.out, values.url, out);
|
|
1183
|
+
} else {
|
|
1184
|
+
await generateOnce(values.dir, values.out, values.url, out);
|
|
1185
|
+
}
|
|
1186
|
+
return 0;
|
|
1187
|
+
} catch (err) {
|
|
1188
|
+
if (values.soft) {
|
|
1189
|
+
out(`warning: codegen skipped (${errMsg(err)})`);
|
|
1190
|
+
return 0;
|
|
1191
|
+
}
|
|
1192
|
+
errOut(`error: ${errMsg(err)}`);
|
|
1193
|
+
return 1;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// src/gen/cli.ts
|
|
1198
|
+
main(process2.argv.slice(2)).then(
|
|
1199
|
+
(code) => process2.exit(code),
|
|
1200
|
+
(err) => {
|
|
1201
|
+
process2.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
|
|
1202
|
+
`);
|
|
1203
|
+
process2.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
);
|
|
1206
|
+
//# sourceMappingURL=cli.js.map
|