@yrest/cli 0.13.0 → 0.14.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 +235 -37
- package/dist/cli/index.js +424 -35
- package/dist/cli/index.mjs +424 -35
- package/dist/index.d.mts +140 -8
- package/dist/index.d.ts +140 -8
- package/dist/index.js +399 -72
- package/dist/index.mjs +396 -72
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -153,6 +153,62 @@ var init_parseRelations = __esm({
|
|
|
153
153
|
}
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
+
// src/storage/parseRoutes.ts
|
|
157
|
+
function parseRoutes(raw) {
|
|
158
|
+
if (!Array.isArray(raw)) return [];
|
|
159
|
+
return raw.map(normaliseRoute).filter((r) => r !== null);
|
|
160
|
+
}
|
|
161
|
+
function normaliseRoute(raw) {
|
|
162
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
163
|
+
const v = raw;
|
|
164
|
+
const method = v["_method"];
|
|
165
|
+
if (typeof method === "string" && method.toUpperCase() === "SSE") return null;
|
|
166
|
+
if ("_sse" in v) return null;
|
|
167
|
+
const path = v["_path"];
|
|
168
|
+
if (typeof method !== "string" || typeof path !== "string") return null;
|
|
169
|
+
const route = { method, path };
|
|
170
|
+
if (typeof v["_handler"] === "string") route.handler = v["_handler"];
|
|
171
|
+
if (typeof v["_delay"] === "number") route.delay = v["_delay"];
|
|
172
|
+
if (typeof v["_error"] === "number") route.error = v["_error"];
|
|
173
|
+
if (v["_errorBody"] !== void 0) route.errorBody = v["_errorBody"];
|
|
174
|
+
const response = normaliseResponse(v["_response"]);
|
|
175
|
+
if (response) route.response = response;
|
|
176
|
+
const otherwise = normaliseResponse(v["_otherwise"]);
|
|
177
|
+
if (otherwise) route.otherwise = otherwise;
|
|
178
|
+
const scenarios = normaliseScenarios(v["_scenarios"]);
|
|
179
|
+
if (scenarios.length > 0) route.scenarios = scenarios;
|
|
180
|
+
return route;
|
|
181
|
+
}
|
|
182
|
+
function normaliseResponse(raw) {
|
|
183
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
184
|
+
const v = raw;
|
|
185
|
+
const response = {};
|
|
186
|
+
if (typeof v["_status"] === "number") response.status = v["_status"];
|
|
187
|
+
if (v["_body"] !== void 0) response.body = v["_body"];
|
|
188
|
+
if (v["_headers"] && typeof v["_headers"] === "object" && !Array.isArray(v["_headers"]))
|
|
189
|
+
response.headers = v["_headers"];
|
|
190
|
+
return Object.keys(response).length > 0 ? response : void 0;
|
|
191
|
+
}
|
|
192
|
+
function normaliseScenarios(raw) {
|
|
193
|
+
if (!Array.isArray(raw)) return [];
|
|
194
|
+
return raw.map(normaliseScenario).filter((s) => s !== null);
|
|
195
|
+
}
|
|
196
|
+
function normaliseScenario(raw) {
|
|
197
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
198
|
+
const v = raw;
|
|
199
|
+
const when = v["_when"];
|
|
200
|
+
if (!when || typeof when !== "object") return null;
|
|
201
|
+
const response = normaliseResponse(v["_response"]);
|
|
202
|
+
if (!response) return null;
|
|
203
|
+
return { when, response };
|
|
204
|
+
}
|
|
205
|
+
var init_parseRoutes = __esm({
|
|
206
|
+
"src/storage/parseRoutes.ts"() {
|
|
207
|
+
"use strict";
|
|
208
|
+
init_cjs_shims();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
156
212
|
// src/storage/parseSseRoutes.ts
|
|
157
213
|
function parseSseRoutes(raw) {
|
|
158
214
|
if (!Array.isArray(raw)) return [];
|
|
@@ -169,11 +225,12 @@ function normaliseSseRoute(raw) {
|
|
|
169
225
|
return null;
|
|
170
226
|
}
|
|
171
227
|
const s = sse;
|
|
228
|
+
const handler = typeof s["_handler"] === "string" && s["_handler"].trim() ? s["_handler"].trim() : void 0;
|
|
172
229
|
const rawEvents = s["_events"];
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (!events.length) return null;
|
|
230
|
+
const events = Array.isArray(rawEvents) ? rawEvents.map(normaliseEvent).filter((e) => e !== null) : [];
|
|
231
|
+
if (!handler && !events.length) return null;
|
|
176
232
|
return {
|
|
233
|
+
...handler ? { handler } : {},
|
|
177
234
|
path,
|
|
178
235
|
interval: typeof s["_interval"] === "number" ? s["_interval"] : 1e3,
|
|
179
236
|
loop: s["_loop"] !== false,
|
|
@@ -275,62 +332,6 @@ var init_deepCopy = __esm({
|
|
|
275
332
|
}
|
|
276
333
|
});
|
|
277
334
|
|
|
278
|
-
// src/storage/parseRoutes.ts
|
|
279
|
-
function parseRoutes(raw) {
|
|
280
|
-
if (!Array.isArray(raw)) return [];
|
|
281
|
-
return raw.map(normaliseRoute).filter((r) => r !== null);
|
|
282
|
-
}
|
|
283
|
-
function normaliseRoute(raw) {
|
|
284
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
285
|
-
const v = raw;
|
|
286
|
-
const method = v["_method"];
|
|
287
|
-
if (typeof method === "string" && method.toUpperCase() === "SSE") return null;
|
|
288
|
-
if ("_sse" in v) return null;
|
|
289
|
-
const path = v["_path"];
|
|
290
|
-
if (typeof method !== "string" || typeof path !== "string") return null;
|
|
291
|
-
const route = { method, path };
|
|
292
|
-
if (typeof v["_handler"] === "string") route.handler = v["_handler"];
|
|
293
|
-
if (typeof v["_delay"] === "number") route.delay = v["_delay"];
|
|
294
|
-
if (typeof v["_error"] === "number") route.error = v["_error"];
|
|
295
|
-
if (v["_errorBody"] !== void 0) route.errorBody = v["_errorBody"];
|
|
296
|
-
const response = normaliseResponse(v["_response"]);
|
|
297
|
-
if (response) route.response = response;
|
|
298
|
-
const otherwise = normaliseResponse(v["_otherwise"]);
|
|
299
|
-
if (otherwise) route.otherwise = otherwise;
|
|
300
|
-
const scenarios = normaliseScenarios(v["_scenarios"]);
|
|
301
|
-
if (scenarios.length > 0) route.scenarios = scenarios;
|
|
302
|
-
return route;
|
|
303
|
-
}
|
|
304
|
-
function normaliseResponse(raw) {
|
|
305
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
306
|
-
const v = raw;
|
|
307
|
-
const response = {};
|
|
308
|
-
if (typeof v["_status"] === "number") response.status = v["_status"];
|
|
309
|
-
if (v["_body"] !== void 0) response.body = v["_body"];
|
|
310
|
-
if (v["_headers"] && typeof v["_headers"] === "object" && !Array.isArray(v["_headers"]))
|
|
311
|
-
response.headers = v["_headers"];
|
|
312
|
-
return Object.keys(response).length > 0 ? response : void 0;
|
|
313
|
-
}
|
|
314
|
-
function normaliseScenarios(raw) {
|
|
315
|
-
if (!Array.isArray(raw)) return [];
|
|
316
|
-
return raw.map(normaliseScenario).filter((s) => s !== null);
|
|
317
|
-
}
|
|
318
|
-
function normaliseScenario(raw) {
|
|
319
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
320
|
-
const v = raw;
|
|
321
|
-
const when = v["_when"];
|
|
322
|
-
if (!when || typeof when !== "object") return null;
|
|
323
|
-
const response = normaliseResponse(v["_response"]);
|
|
324
|
-
if (!response) return null;
|
|
325
|
-
return { when, response };
|
|
326
|
-
}
|
|
327
|
-
var init_parseRoutes = __esm({
|
|
328
|
-
"src/storage/parseRoutes.ts"() {
|
|
329
|
-
"use strict";
|
|
330
|
-
init_cjs_shims();
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
|
|
334
335
|
// src/storage/parseData.ts
|
|
335
336
|
function parseData(raw) {
|
|
336
337
|
const block = raw["_data"];
|
|
@@ -574,6 +575,9 @@ __export(src_exports, {
|
|
|
574
575
|
createYrestServer: () => createYrestServer,
|
|
575
576
|
createYrestServerFromStorage: () => createYrestServerFromStorage,
|
|
576
577
|
createYrestStorage: () => createYrestStorage,
|
|
578
|
+
defineHandler: () => defineHandler,
|
|
579
|
+
defineSseHandler: () => defineSseHandler,
|
|
580
|
+
generateTypes: () => generateTypes,
|
|
577
581
|
yrest: () => yrest,
|
|
578
582
|
yrestOptionsSchema: () => yrestOptionsSchema
|
|
579
583
|
});
|
|
@@ -626,18 +630,25 @@ function dedent(str) {
|
|
|
626
630
|
init_cjs_shims();
|
|
627
631
|
var import_node_path3 = require("path");
|
|
628
632
|
init_parseRelations();
|
|
633
|
+
init_parseRoutes();
|
|
629
634
|
init_parseSseRoutes();
|
|
630
635
|
init_parseSchema();
|
|
631
636
|
|
|
632
637
|
// src/utils/handlers.ts
|
|
633
638
|
init_cjs_shims();
|
|
634
639
|
var import_node_fs = require("fs");
|
|
635
|
-
|
|
640
|
+
function defineHandler(fn) {
|
|
641
|
+
return fn;
|
|
642
|
+
}
|
|
643
|
+
function defineSseHandler(fn) {
|
|
644
|
+
return fn;
|
|
645
|
+
}
|
|
646
|
+
var ALLOWED_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts"];
|
|
636
647
|
async function loadHandlers(filePath) {
|
|
637
648
|
if (!(0, import_node_fs.existsSync)(filePath)) return /* @__PURE__ */ new Map();
|
|
638
649
|
if (!ALLOWED_EXTENSIONS.some((ext) => filePath.endsWith(ext))) {
|
|
639
650
|
console.error(
|
|
640
|
-
` \x1B[31m[handlers] ${filePath} \u2014 only .js, .mjs and .
|
|
651
|
+
` \x1B[31m[handlers] ${filePath} \u2014 only .js, .mjs, .cjs, .ts and .mts files are allowed\x1B[0m`
|
|
641
652
|
);
|
|
642
653
|
return /* @__PURE__ */ new Map();
|
|
643
654
|
}
|
|
@@ -934,6 +945,10 @@ function handlersAccordion(handlers, routes, base) {
|
|
|
934
945
|
function sseRoutesAccordion(routes, base) {
|
|
935
946
|
if (!routes.length) return "";
|
|
936
947
|
const rows = routes.map((r) => {
|
|
948
|
+
if (r.handler) {
|
|
949
|
+
const desc2 = `SSE stream <span style="color:#0ea5e9;font-size:11px">\u2192 ${escapeHtml(r.handler)}()</span>`;
|
|
950
|
+
return endpointRow("SSE", `${base}${r.path}`, desc2);
|
|
951
|
+
}
|
|
937
952
|
const tags = [
|
|
938
953
|
`<span style="color:#0ea5e9;font-size:11px">interval\xB7${r.interval}ms</span>`,
|
|
939
954
|
`<span style="color:var(--text-muted);font-size:11px">${r.events.length} event${r.events.length !== 1 ? "s" : ""}</span>`
|
|
@@ -2244,6 +2259,33 @@ var CollectionRouteCommand = class {
|
|
|
2244
2259
|
// src/router/routes/custom.routes.ts
|
|
2245
2260
|
init_cjs_shims();
|
|
2246
2261
|
|
|
2262
|
+
// src/api/collection.ts
|
|
2263
|
+
init_cjs_shims();
|
|
2264
|
+
function createCollectionHandle(storage, name, idStrategy = "increment") {
|
|
2265
|
+
if (!storage.getCollection(name)) {
|
|
2266
|
+
const known = Object.keys(storage.getData()).join(", ") || "none";
|
|
2267
|
+
throw new Error(`Collection "${name}" does not exist (known collections: ${known})`);
|
|
2268
|
+
}
|
|
2269
|
+
const items = () => storage.getCollection(name) ?? [];
|
|
2270
|
+
const copy = (item) => item === void 0 ? void 0 : structuredClone(item);
|
|
2271
|
+
return {
|
|
2272
|
+
findAll: () => items().map((i) => copy(i)),
|
|
2273
|
+
findById: (id) => copy(findById(items(), String(id))),
|
|
2274
|
+
findWhere: (predicate) => items().map((i) => copy(i)).filter(predicate),
|
|
2275
|
+
count: () => items().length,
|
|
2276
|
+
create: (data) => copy(createItem(storage, name, { ...data }, idStrategy)),
|
|
2277
|
+
update: (id, patch) => {
|
|
2278
|
+
const rest = { ...patch };
|
|
2279
|
+
delete rest["id"];
|
|
2280
|
+
return copy(patchItem(storage, name, String(id), rest));
|
|
2281
|
+
},
|
|
2282
|
+
delete: (id) => copy(deleteItem(storage, name, String(id)))
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
function collectionAccessor(storage, idStrategy = "increment") {
|
|
2286
|
+
return (name) => createCollectionHandle(storage, name, idStrategy);
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2247
2289
|
// src/utils/conditions.ts
|
|
2248
2290
|
init_cjs_shims();
|
|
2249
2291
|
function resolveRequestPath(dotPath, req) {
|
|
@@ -2307,15 +2349,20 @@ function resolveBody(body, ctx) {
|
|
|
2307
2349
|
return body ?? null;
|
|
2308
2350
|
}
|
|
2309
2351
|
var CustomRouteCommand = class {
|
|
2310
|
-
constructor(storage, base, handlers = /* @__PURE__ */ new Map()) {
|
|
2352
|
+
constructor(storage, base, handlers = /* @__PURE__ */ new Map(), idStrategy = "increment") {
|
|
2311
2353
|
this.storage = storage;
|
|
2312
2354
|
this.base = base;
|
|
2313
2355
|
this.handlers = handlers;
|
|
2356
|
+
this.idStrategy = idStrategy;
|
|
2314
2357
|
}
|
|
2315
2358
|
storage;
|
|
2316
2359
|
base;
|
|
2317
2360
|
handlers;
|
|
2361
|
+
idStrategy;
|
|
2318
2362
|
register(server) {
|
|
2363
|
+
const handlerCtx = {
|
|
2364
|
+
collection: collectionAccessor(this.storage, this.idStrategy)
|
|
2365
|
+
};
|
|
2319
2366
|
for (const route of this.storage.getRoutes()) {
|
|
2320
2367
|
const method = route.method?.toUpperCase();
|
|
2321
2368
|
const path = route.path;
|
|
@@ -2352,7 +2399,7 @@ var CustomRouteCommand = class {
|
|
|
2352
2399
|
body: req.body,
|
|
2353
2400
|
headers: req.headers
|
|
2354
2401
|
};
|
|
2355
|
-
const result = await fn(ctx2);
|
|
2402
|
+
const result = await fn(ctx2, handlerCtx);
|
|
2356
2403
|
const resStatus = result.status ?? 200;
|
|
2357
2404
|
for (const [k, v] of Object.entries(result.headers ?? {})) {
|
|
2358
2405
|
reply.header(k, v);
|
|
@@ -2967,26 +3014,64 @@ var SnapshotRouteCommand = class {
|
|
|
2967
3014
|
// src/router/routes/sse.routes.ts
|
|
2968
3015
|
init_cjs_shims();
|
|
2969
3016
|
var SSERouteCommand = class {
|
|
2970
|
-
constructor(storage, base) {
|
|
3017
|
+
constructor(storage, base, handlers = /* @__PURE__ */ new Map(), idStrategy = "increment") {
|
|
2971
3018
|
this.storage = storage;
|
|
2972
3019
|
this.base = base;
|
|
3020
|
+
this.handlers = handlers;
|
|
3021
|
+
this.idStrategy = idStrategy;
|
|
2973
3022
|
}
|
|
2974
3023
|
storage;
|
|
2975
3024
|
base;
|
|
3025
|
+
handlers;
|
|
3026
|
+
idStrategy;
|
|
2976
3027
|
register(server) {
|
|
3028
|
+
const handlerCtx = {
|
|
3029
|
+
collection: collectionAccessor(this.storage, this.idStrategy)
|
|
3030
|
+
};
|
|
2977
3031
|
for (const route of this.storage.getSseRoutes()) {
|
|
2978
3032
|
const url = `${this.base}${route.path}`;
|
|
2979
3033
|
server.route({
|
|
2980
3034
|
method: "GET",
|
|
2981
3035
|
url,
|
|
2982
3036
|
handler: async (req, reply) => {
|
|
3037
|
+
if (route.handler) {
|
|
3038
|
+
const fn = this.handlers.get(route.handler);
|
|
3039
|
+
if (!fn) {
|
|
3040
|
+
return reply.status(501).send({ error: `Handler "${route.handler}" is not defined in the handlers file` });
|
|
3041
|
+
}
|
|
3042
|
+
reply.hijack();
|
|
3043
|
+
const raw2 = reply.raw;
|
|
3044
|
+
openStream(raw2);
|
|
3045
|
+
const keepAlive2 = setInterval(() => {
|
|
3046
|
+
if (!raw2.writableEnded) raw2.write(": ping\n\n");
|
|
3047
|
+
}, 15e3);
|
|
3048
|
+
raw2.on("close", () => clearInterval(keepAlive2));
|
|
3049
|
+
const ctx = {
|
|
3050
|
+
params: req.params,
|
|
3051
|
+
query: req.query,
|
|
3052
|
+
body: null,
|
|
3053
|
+
headers: req.headers
|
|
3054
|
+
};
|
|
3055
|
+
try {
|
|
3056
|
+
await fn(ctx, raw2, handlerCtx);
|
|
3057
|
+
} catch (err) {
|
|
3058
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3059
|
+
clearInterval(keepAlive2);
|
|
3060
|
+
if (!raw2.writableEnded) {
|
|
3061
|
+
raw2.write(
|
|
3062
|
+
`event: error
|
|
3063
|
+
data: ${JSON.stringify({ error: `Handler "${route.handler}" threw an error: ${msg}` })}
|
|
3064
|
+
|
|
3065
|
+
`
|
|
3066
|
+
);
|
|
3067
|
+
raw2.end();
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
2983
3072
|
reply.hijack();
|
|
2984
3073
|
const raw = reply.raw;
|
|
2985
|
-
raw
|
|
2986
|
-
raw.setHeader("Cache-Control", "no-cache");
|
|
2987
|
-
raw.setHeader("Connection", "keep-alive");
|
|
2988
|
-
raw.setHeader("Access-Control-Allow-Origin", "*");
|
|
2989
|
-
raw.flushHeaders();
|
|
3074
|
+
openStream(raw);
|
|
2990
3075
|
let alive = true;
|
|
2991
3076
|
req.raw.on("close", () => {
|
|
2992
3077
|
alive = false;
|
|
@@ -3037,6 +3122,13 @@ var SSERouteCommand = class {
|
|
|
3037
3122
|
}
|
|
3038
3123
|
}
|
|
3039
3124
|
};
|
|
3125
|
+
function openStream(raw) {
|
|
3126
|
+
raw.setHeader("Content-Type", "text/event-stream");
|
|
3127
|
+
raw.setHeader("Cache-Control", "no-cache");
|
|
3128
|
+
raw.setHeader("Connection", "keep-alive");
|
|
3129
|
+
raw.setHeader("Access-Control-Allow-Origin", "*");
|
|
3130
|
+
raw.flushHeaders();
|
|
3131
|
+
}
|
|
3040
3132
|
|
|
3041
3133
|
// src/router/resource.router.ts
|
|
3042
3134
|
function buildResourceRouteCommands(storage, options) {
|
|
@@ -3079,8 +3171,8 @@ async function createServer(storage, options, handlers = /* @__PURE__ */ new Map
|
|
|
3079
3171
|
new AboutRouteCommand(storage, options, handlers),
|
|
3080
3172
|
new OpenApiRouteCommand(storage, options),
|
|
3081
3173
|
...options.snapshot ? [new SnapshotRouteCommand(storage)] : [],
|
|
3082
|
-
new SSERouteCommand(storage, options.base),
|
|
3083
|
-
new CustomRouteCommand(storage, options.base, handlers),
|
|
3174
|
+
new SSERouteCommand(storage, options.base, handlers, options.idStrategy),
|
|
3175
|
+
new CustomRouteCommand(storage, options.base, handlers, options.idStrategy),
|
|
3084
3176
|
...buildResourceRouteCommands(storage, options)
|
|
3085
3177
|
];
|
|
3086
3178
|
for (const command of commands) {
|
|
@@ -3114,6 +3206,9 @@ function createYrestServerFromStorage(storage, options, handlers = /* @__PURE__
|
|
|
3114
3206
|
},
|
|
3115
3207
|
get url() {
|
|
3116
3208
|
return `http://${options.host}:${_port}${options.base}`;
|
|
3209
|
+
},
|
|
3210
|
+
collection(name) {
|
|
3211
|
+
return createCollectionHandle(storage, name, options.idStrategy);
|
|
3117
3212
|
}
|
|
3118
3213
|
};
|
|
3119
3214
|
}
|
|
@@ -3137,6 +3232,10 @@ function createYrestServer(options) {
|
|
|
3137
3232
|
},
|
|
3138
3233
|
get url() {
|
|
3139
3234
|
return _inner?.url ?? "";
|
|
3235
|
+
},
|
|
3236
|
+
collection(name) {
|
|
3237
|
+
if (!_inner) throw new Error("collection() is only available after start()");
|
|
3238
|
+
return _inner.collection(name);
|
|
3140
3239
|
}
|
|
3141
3240
|
};
|
|
3142
3241
|
}
|
|
@@ -3160,7 +3259,7 @@ function createInMemoryStorage(data) {
|
|
|
3160
3259
|
const raw = data;
|
|
3161
3260
|
const RESERVED2 = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
3162
3261
|
const relations = parseRelations(raw["_rel"]);
|
|
3163
|
-
const routes =
|
|
3262
|
+
const routes = parseRoutes(raw["_routes"]);
|
|
3164
3263
|
const sseRoutes = parseSseRoutes(raw["_routes"]);
|
|
3165
3264
|
const schema = parseSchema(raw["_schema"]);
|
|
3166
3265
|
const collections = Object.fromEntries(
|
|
@@ -3251,12 +3350,240 @@ var yrestOptionsSchema = import_zod.z.object({
|
|
|
3251
3350
|
*/
|
|
3252
3351
|
idStrategy: import_zod.z.enum(["increment", "uuid"]).default("increment")
|
|
3253
3352
|
});
|
|
3353
|
+
|
|
3354
|
+
// src/codegen/generateTypes.ts
|
|
3355
|
+
init_cjs_shims();
|
|
3356
|
+
var HEADER = "// Auto-generated by yrest types \u2014 do not edit manually.";
|
|
3357
|
+
var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
3358
|
+
var MAX_DEPTH = 4;
|
|
3359
|
+
function generateTypes(input, options = {}) {
|
|
3360
|
+
const { data, schema = {}, relations = {} } = input;
|
|
3361
|
+
const collections = unique([...Object.keys(data), ...Object.keys(schema)]);
|
|
3362
|
+
const names = resolveInterfaceNames(collections);
|
|
3363
|
+
const out = [HEADER];
|
|
3364
|
+
if (options.source) out.push(`// Source: ${options.source}`);
|
|
3365
|
+
out.push("");
|
|
3366
|
+
for (const collection of collections) {
|
|
3367
|
+
out.push(
|
|
3368
|
+
renderInterface(
|
|
3369
|
+
names.get(collection),
|
|
3370
|
+
collection,
|
|
3371
|
+
data[collection] ?? [],
|
|
3372
|
+
schema[collection] ?? {},
|
|
3373
|
+
relations[collection] ?? {}
|
|
3374
|
+
)
|
|
3375
|
+
);
|
|
3376
|
+
out.push("");
|
|
3377
|
+
}
|
|
3378
|
+
out.push(`/** Union of all collection names in the source file. */`);
|
|
3379
|
+
out.push(
|
|
3380
|
+
collections.length > 0 ? `export type CollectionName = ${collections.map((c) => quote(c)).join(" | ")};` : "export type CollectionName = never;"
|
|
3381
|
+
);
|
|
3382
|
+
out.push("");
|
|
3383
|
+
out.push(`/** Maps each collection name to its item array type. */`);
|
|
3384
|
+
out.push("export interface Collections {");
|
|
3385
|
+
for (const collection of collections) {
|
|
3386
|
+
out.push(` ${propKey(collection)}: ${names.get(collection)}[];`);
|
|
3387
|
+
}
|
|
3388
|
+
out.push("}");
|
|
3389
|
+
return out.join("\n") + "\n";
|
|
3390
|
+
}
|
|
3391
|
+
function renderInterface(name, collection, items, fieldDefs, rels) {
|
|
3392
|
+
const records = items.filter(isPlainObject);
|
|
3393
|
+
const fields = collectFields(records);
|
|
3394
|
+
const fieldNames = unique([...fields.keys(), ...Object.keys(fieldDefs)]);
|
|
3395
|
+
const lines = [`/** Item of the \`${collection}\` collection. */`];
|
|
3396
|
+
lines.push(`export interface ${name} {`);
|
|
3397
|
+
for (const field of fieldNames) {
|
|
3398
|
+
const info = fields.get(field);
|
|
3399
|
+
const def = fieldDefs[field];
|
|
3400
|
+
const inferredRequired = info !== void 0 && info.count === records.length && records.length > 0;
|
|
3401
|
+
const required = def?.required ?? inferredRequired;
|
|
3402
|
+
let type = def ? typeFromDef(def, info) : typeOfValues(info.values, 0);
|
|
3403
|
+
if (def?.nullable === true && !type.split(" | ").includes("null")) type = `${type} | null`;
|
|
3404
|
+
for (const doc of fieldDoc(field, def, rels)) lines.push(` ${doc}`);
|
|
3405
|
+
lines.push(` ${propKey(field)}${required ? "" : "?"}: ${type};`);
|
|
3406
|
+
}
|
|
3407
|
+
if (fieldNames.length === 0) lines.push(" [key: string]: unknown;");
|
|
3408
|
+
lines.push("}");
|
|
3409
|
+
return lines.join("\n");
|
|
3410
|
+
}
|
|
3411
|
+
function fieldDoc(field, def, rels) {
|
|
3412
|
+
const text = [];
|
|
3413
|
+
const tags = [];
|
|
3414
|
+
if (def?.description) text.push(def.description.replace(/\*\//g, "*\\/"));
|
|
3415
|
+
const rel = Object.entries(rels).find(
|
|
3416
|
+
([key, r]) => r.type !== "many2many" && (r.foreignKey ?? key) === field
|
|
3417
|
+
)?.[1];
|
|
3418
|
+
if (rel) text.push(`FK \u2192 ${rel.target}.id`);
|
|
3419
|
+
if (def?.format) tags.push(`@format ${def.format}`);
|
|
3420
|
+
if (def?.deprecated) tags.push("@deprecated");
|
|
3421
|
+
if (text.length === 0 && tags.length === 0) return [];
|
|
3422
|
+
if (tags.length === 0) return [`/** ${text.join(" \u2014 ")} */`];
|
|
3423
|
+
return ["/**", ...[...text, ...tags].map((l) => ` * ${l}`), " */"];
|
|
3424
|
+
}
|
|
3425
|
+
function collectFields(records) {
|
|
3426
|
+
const fields = /* @__PURE__ */ new Map();
|
|
3427
|
+
for (const record of records) {
|
|
3428
|
+
for (const [key, value] of Object.entries(record)) {
|
|
3429
|
+
let info = fields.get(key);
|
|
3430
|
+
if (!info) {
|
|
3431
|
+
info = { values: [], count: 0 };
|
|
3432
|
+
fields.set(key, info);
|
|
3433
|
+
}
|
|
3434
|
+
info.count++;
|
|
3435
|
+
info.values.push(value);
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
return fields;
|
|
3439
|
+
}
|
|
3440
|
+
function typeOfValues(values, depth) {
|
|
3441
|
+
return unionOf(typeSetOf(values, depth));
|
|
3442
|
+
}
|
|
3443
|
+
function typeSetOf(values, depth) {
|
|
3444
|
+
const types = /* @__PURE__ */ new Set();
|
|
3445
|
+
const objects = [];
|
|
3446
|
+
const elements = [];
|
|
3447
|
+
let sawArray = false;
|
|
3448
|
+
for (const value of values) {
|
|
3449
|
+
if (value === null || value === void 0) types.add("null");
|
|
3450
|
+
else if (typeof value === "string") types.add("string");
|
|
3451
|
+
else if (typeof value === "number") types.add("number");
|
|
3452
|
+
else if (typeof value === "boolean") types.add("boolean");
|
|
3453
|
+
else if (Array.isArray(value)) {
|
|
3454
|
+
sawArray = true;
|
|
3455
|
+
elements.push(...value);
|
|
3456
|
+
} else if (isPlainObject(value)) objects.push(value);
|
|
3457
|
+
else types.add("unknown");
|
|
3458
|
+
}
|
|
3459
|
+
if (objects.length > 0) {
|
|
3460
|
+
types.add(depth >= MAX_DEPTH ? "Record<string, unknown>" : inlineObject(objects, depth + 1));
|
|
3461
|
+
}
|
|
3462
|
+
if (sawArray) {
|
|
3463
|
+
types.add(
|
|
3464
|
+
elements.length === 0 ? "unknown[]" : depth >= MAX_DEPTH ? "unknown[]" : arrayOf(typeSetOf(elements, depth + 1))
|
|
3465
|
+
);
|
|
3466
|
+
}
|
|
3467
|
+
return types;
|
|
3468
|
+
}
|
|
3469
|
+
function inlineObject(records, depth) {
|
|
3470
|
+
const fields = collectFields(records);
|
|
3471
|
+
if (fields.size === 0) return "Record<string, unknown>";
|
|
3472
|
+
const props = [...fields].map(([key, info]) => {
|
|
3473
|
+
const optional = info.count < records.length ? "?" : "";
|
|
3474
|
+
return `${propKey(key)}${optional}: ${typeOfValues(info.values, depth)}`;
|
|
3475
|
+
});
|
|
3476
|
+
return `{ ${props.join("; ")} }`;
|
|
3477
|
+
}
|
|
3478
|
+
function unionOf(types) {
|
|
3479
|
+
const list = [...types];
|
|
3480
|
+
if (list.length === 0) return "unknown";
|
|
3481
|
+
if (list.length === 1 && list[0] === "null") return "unknown";
|
|
3482
|
+
const hasConcreteArray = list.some((t) => t.endsWith("[]") && t !== "unknown[]");
|
|
3483
|
+
const filtered = hasConcreteArray ? list.filter((t) => t !== "unknown[]") : list;
|
|
3484
|
+
filtered.sort((a, b) => a === "null" ? 1 : b === "null" ? -1 : 0);
|
|
3485
|
+
return filtered.join(" | ");
|
|
3486
|
+
}
|
|
3487
|
+
function arrayOf(elements) {
|
|
3488
|
+
const element = unionOf(elements);
|
|
3489
|
+
return element.includes(" | ") && elements.size > 1 ? `(${element})[]` : `${element}[]`;
|
|
3490
|
+
}
|
|
3491
|
+
function typeFromDef(def, info) {
|
|
3492
|
+
if (def.enum && def.enum.length > 0) {
|
|
3493
|
+
return unique(def.enum.map((v) => literal(v))).join(" | ");
|
|
3494
|
+
}
|
|
3495
|
+
if (def.type) {
|
|
3496
|
+
if (def.type === "array") {
|
|
3497
|
+
const items = def.items;
|
|
3498
|
+
if (items?.enum && items.enum.length > 0)
|
|
3499
|
+
return arrayOf(new Set(items.enum.map((v) => literal(v))));
|
|
3500
|
+
if (items?.type) return arrayOf(/* @__PURE__ */ new Set([primitiveFor(items.type)]));
|
|
3501
|
+
const inferred = info ? typeOfValues(info.values, 0) : "";
|
|
3502
|
+
return inferred.endsWith("[]") && !inferred.includes(" | ") ? inferred : "unknown[]";
|
|
3503
|
+
}
|
|
3504
|
+
return primitiveFor(def.type);
|
|
3505
|
+
}
|
|
3506
|
+
return info ? typeOfValues(info.values, 0) : "unknown";
|
|
3507
|
+
}
|
|
3508
|
+
function primitiveFor(type) {
|
|
3509
|
+
switch (type) {
|
|
3510
|
+
case "integer":
|
|
3511
|
+
case "number":
|
|
3512
|
+
return "number";
|
|
3513
|
+
case "string":
|
|
3514
|
+
return "string";
|
|
3515
|
+
case "boolean":
|
|
3516
|
+
return "boolean";
|
|
3517
|
+
case "array":
|
|
3518
|
+
return "unknown[]";
|
|
3519
|
+
case "object":
|
|
3520
|
+
return "Record<string, unknown>";
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
function literal(value) {
|
|
3524
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
3525
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3526
|
+
if (value === null) return "null";
|
|
3527
|
+
return "unknown";
|
|
3528
|
+
}
|
|
3529
|
+
function resolveInterfaceNames(collections) {
|
|
3530
|
+
const names = /* @__PURE__ */ new Map();
|
|
3531
|
+
const used = /* @__PURE__ */ new Set();
|
|
3532
|
+
for (const collection of collections) {
|
|
3533
|
+
const words = splitWords(collection);
|
|
3534
|
+
const candidates = [
|
|
3535
|
+
toPascal([...words.slice(0, -1), singularize(words.at(-1) ?? "")]),
|
|
3536
|
+
toPascal(words)
|
|
3537
|
+
];
|
|
3538
|
+
let name = candidates.find((c) => c && !used.has(c));
|
|
3539
|
+
if (!name) {
|
|
3540
|
+
const base = candidates[1] || "Collection";
|
|
3541
|
+
let i = 2;
|
|
3542
|
+
while (used.has(`${base}${i}`)) i++;
|
|
3543
|
+
name = `${base}${i}`;
|
|
3544
|
+
}
|
|
3545
|
+
if (/^[0-9]/.test(name)) name = `_${name}`;
|
|
3546
|
+
used.add(name);
|
|
3547
|
+
names.set(collection, name);
|
|
3548
|
+
}
|
|
3549
|
+
return names;
|
|
3550
|
+
}
|
|
3551
|
+
function splitWords(name) {
|
|
3552
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
3553
|
+
}
|
|
3554
|
+
function toPascal(words) {
|
|
3555
|
+
return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
3556
|
+
}
|
|
3557
|
+
function singularize(word) {
|
|
3558
|
+
const lower = word.toLowerCase();
|
|
3559
|
+
if (lower.length <= 2) return word;
|
|
3560
|
+
if (/(ss|us|is)$/.test(lower)) return word;
|
|
3561
|
+
if (/[^aeiou]ies$/.test(lower)) return word.slice(0, -3) + "y";
|
|
3562
|
+
if (/(s|x|z|ch|sh)es$/.test(lower)) return word.slice(0, -2);
|
|
3563
|
+
if (lower.endsWith("s")) return word.slice(0, -1);
|
|
3564
|
+
return word;
|
|
3565
|
+
}
|
|
3566
|
+
function propKey(key) {
|
|
3567
|
+
return IDENT_RE.test(key) ? key : JSON.stringify(key);
|
|
3568
|
+
}
|
|
3569
|
+
function quote(value) {
|
|
3570
|
+
return JSON.stringify(value);
|
|
3571
|
+
}
|
|
3572
|
+
function unique(list) {
|
|
3573
|
+
return [...new Set(list)];
|
|
3574
|
+
}
|
|
3575
|
+
function isPlainObject(value) {
|
|
3576
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3577
|
+
}
|
|
3254
3578
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3255
3579
|
0 && (module.exports = {
|
|
3256
3580
|
createServer,
|
|
3257
3581
|
createYrestServer,
|
|
3258
3582
|
createYrestServerFromStorage,
|
|
3259
3583
|
createYrestStorage,
|
|
3584
|
+
defineHandler,
|
|
3585
|
+
defineSseHandler,
|
|
3586
|
+
generateTypes,
|
|
3260
3587
|
yrest,
|
|
3261
3588
|
yrestOptionsSchema
|
|
3262
3589
|
});
|