@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.mjs
CHANGED
|
@@ -130,6 +130,62 @@ var init_parseRelations = __esm({
|
|
|
130
130
|
}
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
// src/storage/parseRoutes.ts
|
|
134
|
+
function parseRoutes(raw) {
|
|
135
|
+
if (!Array.isArray(raw)) return [];
|
|
136
|
+
return raw.map(normaliseRoute).filter((r) => r !== null);
|
|
137
|
+
}
|
|
138
|
+
function normaliseRoute(raw) {
|
|
139
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
140
|
+
const v = raw;
|
|
141
|
+
const method = v["_method"];
|
|
142
|
+
if (typeof method === "string" && method.toUpperCase() === "SSE") return null;
|
|
143
|
+
if ("_sse" in v) return null;
|
|
144
|
+
const path2 = v["_path"];
|
|
145
|
+
if (typeof method !== "string" || typeof path2 !== "string") return null;
|
|
146
|
+
const route = { method, path: path2 };
|
|
147
|
+
if (typeof v["_handler"] === "string") route.handler = v["_handler"];
|
|
148
|
+
if (typeof v["_delay"] === "number") route.delay = v["_delay"];
|
|
149
|
+
if (typeof v["_error"] === "number") route.error = v["_error"];
|
|
150
|
+
if (v["_errorBody"] !== void 0) route.errorBody = v["_errorBody"];
|
|
151
|
+
const response = normaliseResponse(v["_response"]);
|
|
152
|
+
if (response) route.response = response;
|
|
153
|
+
const otherwise = normaliseResponse(v["_otherwise"]);
|
|
154
|
+
if (otherwise) route.otherwise = otherwise;
|
|
155
|
+
const scenarios = normaliseScenarios(v["_scenarios"]);
|
|
156
|
+
if (scenarios.length > 0) route.scenarios = scenarios;
|
|
157
|
+
return route;
|
|
158
|
+
}
|
|
159
|
+
function normaliseResponse(raw) {
|
|
160
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
161
|
+
const v = raw;
|
|
162
|
+
const response = {};
|
|
163
|
+
if (typeof v["_status"] === "number") response.status = v["_status"];
|
|
164
|
+
if (v["_body"] !== void 0) response.body = v["_body"];
|
|
165
|
+
if (v["_headers"] && typeof v["_headers"] === "object" && !Array.isArray(v["_headers"]))
|
|
166
|
+
response.headers = v["_headers"];
|
|
167
|
+
return Object.keys(response).length > 0 ? response : void 0;
|
|
168
|
+
}
|
|
169
|
+
function normaliseScenarios(raw) {
|
|
170
|
+
if (!Array.isArray(raw)) return [];
|
|
171
|
+
return raw.map(normaliseScenario).filter((s) => s !== null);
|
|
172
|
+
}
|
|
173
|
+
function normaliseScenario(raw) {
|
|
174
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
175
|
+
const v = raw;
|
|
176
|
+
const when = v["_when"];
|
|
177
|
+
if (!when || typeof when !== "object") return null;
|
|
178
|
+
const response = normaliseResponse(v["_response"]);
|
|
179
|
+
if (!response) return null;
|
|
180
|
+
return { when, response };
|
|
181
|
+
}
|
|
182
|
+
var init_parseRoutes = __esm({
|
|
183
|
+
"src/storage/parseRoutes.ts"() {
|
|
184
|
+
"use strict";
|
|
185
|
+
init_esm_shims();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
133
189
|
// src/storage/parseSseRoutes.ts
|
|
134
190
|
function parseSseRoutes(raw) {
|
|
135
191
|
if (!Array.isArray(raw)) return [];
|
|
@@ -146,11 +202,12 @@ function normaliseSseRoute(raw) {
|
|
|
146
202
|
return null;
|
|
147
203
|
}
|
|
148
204
|
const s = sse;
|
|
205
|
+
const handler = typeof s["_handler"] === "string" && s["_handler"].trim() ? s["_handler"].trim() : void 0;
|
|
149
206
|
const rawEvents = s["_events"];
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (!events.length) return null;
|
|
207
|
+
const events = Array.isArray(rawEvents) ? rawEvents.map(normaliseEvent).filter((e) => e !== null) : [];
|
|
208
|
+
if (!handler && !events.length) return null;
|
|
153
209
|
return {
|
|
210
|
+
...handler ? { handler } : {},
|
|
154
211
|
path: path2,
|
|
155
212
|
interval: typeof s["_interval"] === "number" ? s["_interval"] : 1e3,
|
|
156
213
|
loop: s["_loop"] !== false,
|
|
@@ -252,62 +309,6 @@ var init_deepCopy = __esm({
|
|
|
252
309
|
}
|
|
253
310
|
});
|
|
254
311
|
|
|
255
|
-
// src/storage/parseRoutes.ts
|
|
256
|
-
function parseRoutes(raw) {
|
|
257
|
-
if (!Array.isArray(raw)) return [];
|
|
258
|
-
return raw.map(normaliseRoute).filter((r) => r !== null);
|
|
259
|
-
}
|
|
260
|
-
function normaliseRoute(raw) {
|
|
261
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
262
|
-
const v = raw;
|
|
263
|
-
const method = v["_method"];
|
|
264
|
-
if (typeof method === "string" && method.toUpperCase() === "SSE") return null;
|
|
265
|
-
if ("_sse" in v) return null;
|
|
266
|
-
const path2 = v["_path"];
|
|
267
|
-
if (typeof method !== "string" || typeof path2 !== "string") return null;
|
|
268
|
-
const route = { method, path: path2 };
|
|
269
|
-
if (typeof v["_handler"] === "string") route.handler = v["_handler"];
|
|
270
|
-
if (typeof v["_delay"] === "number") route.delay = v["_delay"];
|
|
271
|
-
if (typeof v["_error"] === "number") route.error = v["_error"];
|
|
272
|
-
if (v["_errorBody"] !== void 0) route.errorBody = v["_errorBody"];
|
|
273
|
-
const response = normaliseResponse(v["_response"]);
|
|
274
|
-
if (response) route.response = response;
|
|
275
|
-
const otherwise = normaliseResponse(v["_otherwise"]);
|
|
276
|
-
if (otherwise) route.otherwise = otherwise;
|
|
277
|
-
const scenarios = normaliseScenarios(v["_scenarios"]);
|
|
278
|
-
if (scenarios.length > 0) route.scenarios = scenarios;
|
|
279
|
-
return route;
|
|
280
|
-
}
|
|
281
|
-
function normaliseResponse(raw) {
|
|
282
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
283
|
-
const v = raw;
|
|
284
|
-
const response = {};
|
|
285
|
-
if (typeof v["_status"] === "number") response.status = v["_status"];
|
|
286
|
-
if (v["_body"] !== void 0) response.body = v["_body"];
|
|
287
|
-
if (v["_headers"] && typeof v["_headers"] === "object" && !Array.isArray(v["_headers"]))
|
|
288
|
-
response.headers = v["_headers"];
|
|
289
|
-
return Object.keys(response).length > 0 ? response : void 0;
|
|
290
|
-
}
|
|
291
|
-
function normaliseScenarios(raw) {
|
|
292
|
-
if (!Array.isArray(raw)) return [];
|
|
293
|
-
return raw.map(normaliseScenario).filter((s) => s !== null);
|
|
294
|
-
}
|
|
295
|
-
function normaliseScenario(raw) {
|
|
296
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
297
|
-
const v = raw;
|
|
298
|
-
const when = v["_when"];
|
|
299
|
-
if (!when || typeof when !== "object") return null;
|
|
300
|
-
const response = normaliseResponse(v["_response"]);
|
|
301
|
-
if (!response) return null;
|
|
302
|
-
return { when, response };
|
|
303
|
-
}
|
|
304
|
-
var init_parseRoutes = __esm({
|
|
305
|
-
"src/storage/parseRoutes.ts"() {
|
|
306
|
-
"use strict";
|
|
307
|
-
init_esm_shims();
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
|
|
311
312
|
// src/storage/parseData.ts
|
|
312
313
|
function parseData(raw) {
|
|
313
314
|
const block = raw["_data"];
|
|
@@ -591,6 +592,7 @@ function dedent(str) {
|
|
|
591
592
|
// src/api/yrestServer.ts
|
|
592
593
|
init_esm_shims();
|
|
593
594
|
init_parseRelations();
|
|
595
|
+
init_parseRoutes();
|
|
594
596
|
init_parseSseRoutes();
|
|
595
597
|
init_parseSchema();
|
|
596
598
|
import { resolve as resolve2 } from "path";
|
|
@@ -598,12 +600,18 @@ import { resolve as resolve2 } from "path";
|
|
|
598
600
|
// src/utils/handlers.ts
|
|
599
601
|
init_esm_shims();
|
|
600
602
|
import { existsSync } from "fs";
|
|
601
|
-
|
|
603
|
+
function defineHandler(fn) {
|
|
604
|
+
return fn;
|
|
605
|
+
}
|
|
606
|
+
function defineSseHandler(fn) {
|
|
607
|
+
return fn;
|
|
608
|
+
}
|
|
609
|
+
var ALLOWED_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts"];
|
|
602
610
|
async function loadHandlers(filePath) {
|
|
603
611
|
if (!existsSync(filePath)) return /* @__PURE__ */ new Map();
|
|
604
612
|
if (!ALLOWED_EXTENSIONS.some((ext) => filePath.endsWith(ext))) {
|
|
605
613
|
console.error(
|
|
606
|
-
` \x1B[31m[handlers] ${filePath} \u2014 only .js, .mjs and .
|
|
614
|
+
` \x1B[31m[handlers] ${filePath} \u2014 only .js, .mjs, .cjs, .ts and .mts files are allowed\x1B[0m`
|
|
607
615
|
);
|
|
608
616
|
return /* @__PURE__ */ new Map();
|
|
609
617
|
}
|
|
@@ -900,6 +908,10 @@ function handlersAccordion(handlers, routes, base) {
|
|
|
900
908
|
function sseRoutesAccordion(routes, base) {
|
|
901
909
|
if (!routes.length) return "";
|
|
902
910
|
const rows = routes.map((r) => {
|
|
911
|
+
if (r.handler) {
|
|
912
|
+
const desc2 = `SSE stream <span style="color:#0ea5e9;font-size:11px">\u2192 ${escapeHtml(r.handler)}()</span>`;
|
|
913
|
+
return endpointRow("SSE", `${base}${r.path}`, desc2);
|
|
914
|
+
}
|
|
903
915
|
const tags = [
|
|
904
916
|
`<span style="color:#0ea5e9;font-size:11px">interval\xB7${r.interval}ms</span>`,
|
|
905
917
|
`<span style="color:var(--text-muted);font-size:11px">${r.events.length} event${r.events.length !== 1 ? "s" : ""}</span>`
|
|
@@ -2210,6 +2222,33 @@ var CollectionRouteCommand = class {
|
|
|
2210
2222
|
// src/router/routes/custom.routes.ts
|
|
2211
2223
|
init_esm_shims();
|
|
2212
2224
|
|
|
2225
|
+
// src/api/collection.ts
|
|
2226
|
+
init_esm_shims();
|
|
2227
|
+
function createCollectionHandle(storage, name, idStrategy = "increment") {
|
|
2228
|
+
if (!storage.getCollection(name)) {
|
|
2229
|
+
const known = Object.keys(storage.getData()).join(", ") || "none";
|
|
2230
|
+
throw new Error(`Collection "${name}" does not exist (known collections: ${known})`);
|
|
2231
|
+
}
|
|
2232
|
+
const items = () => storage.getCollection(name) ?? [];
|
|
2233
|
+
const copy = (item) => item === void 0 ? void 0 : structuredClone(item);
|
|
2234
|
+
return {
|
|
2235
|
+
findAll: () => items().map((i) => copy(i)),
|
|
2236
|
+
findById: (id) => copy(findById(items(), String(id))),
|
|
2237
|
+
findWhere: (predicate) => items().map((i) => copy(i)).filter(predicate),
|
|
2238
|
+
count: () => items().length,
|
|
2239
|
+
create: (data) => copy(createItem(storage, name, { ...data }, idStrategy)),
|
|
2240
|
+
update: (id, patch) => {
|
|
2241
|
+
const rest = { ...patch };
|
|
2242
|
+
delete rest["id"];
|
|
2243
|
+
return copy(patchItem(storage, name, String(id), rest));
|
|
2244
|
+
},
|
|
2245
|
+
delete: (id) => copy(deleteItem(storage, name, String(id)))
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
function collectionAccessor(storage, idStrategy = "increment") {
|
|
2249
|
+
return (name) => createCollectionHandle(storage, name, idStrategy);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2213
2252
|
// src/utils/conditions.ts
|
|
2214
2253
|
init_esm_shims();
|
|
2215
2254
|
function resolveRequestPath(dotPath, req) {
|
|
@@ -2273,15 +2312,20 @@ function resolveBody(body, ctx) {
|
|
|
2273
2312
|
return body ?? null;
|
|
2274
2313
|
}
|
|
2275
2314
|
var CustomRouteCommand = class {
|
|
2276
|
-
constructor(storage, base, handlers = /* @__PURE__ */ new Map()) {
|
|
2315
|
+
constructor(storage, base, handlers = /* @__PURE__ */ new Map(), idStrategy = "increment") {
|
|
2277
2316
|
this.storage = storage;
|
|
2278
2317
|
this.base = base;
|
|
2279
2318
|
this.handlers = handlers;
|
|
2319
|
+
this.idStrategy = idStrategy;
|
|
2280
2320
|
}
|
|
2281
2321
|
storage;
|
|
2282
2322
|
base;
|
|
2283
2323
|
handlers;
|
|
2324
|
+
idStrategy;
|
|
2284
2325
|
register(server) {
|
|
2326
|
+
const handlerCtx = {
|
|
2327
|
+
collection: collectionAccessor(this.storage, this.idStrategy)
|
|
2328
|
+
};
|
|
2285
2329
|
for (const route of this.storage.getRoutes()) {
|
|
2286
2330
|
const method = route.method?.toUpperCase();
|
|
2287
2331
|
const path2 = route.path;
|
|
@@ -2318,7 +2362,7 @@ var CustomRouteCommand = class {
|
|
|
2318
2362
|
body: req.body,
|
|
2319
2363
|
headers: req.headers
|
|
2320
2364
|
};
|
|
2321
|
-
const result = await fn(ctx2);
|
|
2365
|
+
const result = await fn(ctx2, handlerCtx);
|
|
2322
2366
|
const resStatus = result.status ?? 200;
|
|
2323
2367
|
for (const [k, v] of Object.entries(result.headers ?? {})) {
|
|
2324
2368
|
reply.header(k, v);
|
|
@@ -2933,26 +2977,64 @@ var SnapshotRouteCommand = class {
|
|
|
2933
2977
|
// src/router/routes/sse.routes.ts
|
|
2934
2978
|
init_esm_shims();
|
|
2935
2979
|
var SSERouteCommand = class {
|
|
2936
|
-
constructor(storage, base) {
|
|
2980
|
+
constructor(storage, base, handlers = /* @__PURE__ */ new Map(), idStrategy = "increment") {
|
|
2937
2981
|
this.storage = storage;
|
|
2938
2982
|
this.base = base;
|
|
2983
|
+
this.handlers = handlers;
|
|
2984
|
+
this.idStrategy = idStrategy;
|
|
2939
2985
|
}
|
|
2940
2986
|
storage;
|
|
2941
2987
|
base;
|
|
2988
|
+
handlers;
|
|
2989
|
+
idStrategy;
|
|
2942
2990
|
register(server) {
|
|
2991
|
+
const handlerCtx = {
|
|
2992
|
+
collection: collectionAccessor(this.storage, this.idStrategy)
|
|
2993
|
+
};
|
|
2943
2994
|
for (const route of this.storage.getSseRoutes()) {
|
|
2944
2995
|
const url = `${this.base}${route.path}`;
|
|
2945
2996
|
server.route({
|
|
2946
2997
|
method: "GET",
|
|
2947
2998
|
url,
|
|
2948
2999
|
handler: async (req, reply) => {
|
|
3000
|
+
if (route.handler) {
|
|
3001
|
+
const fn = this.handlers.get(route.handler);
|
|
3002
|
+
if (!fn) {
|
|
3003
|
+
return reply.status(501).send({ error: `Handler "${route.handler}" is not defined in the handlers file` });
|
|
3004
|
+
}
|
|
3005
|
+
reply.hijack();
|
|
3006
|
+
const raw2 = reply.raw;
|
|
3007
|
+
openStream(raw2);
|
|
3008
|
+
const keepAlive2 = setInterval(() => {
|
|
3009
|
+
if (!raw2.writableEnded) raw2.write(": ping\n\n");
|
|
3010
|
+
}, 15e3);
|
|
3011
|
+
raw2.on("close", () => clearInterval(keepAlive2));
|
|
3012
|
+
const ctx = {
|
|
3013
|
+
params: req.params,
|
|
3014
|
+
query: req.query,
|
|
3015
|
+
body: null,
|
|
3016
|
+
headers: req.headers
|
|
3017
|
+
};
|
|
3018
|
+
try {
|
|
3019
|
+
await fn(ctx, raw2, handlerCtx);
|
|
3020
|
+
} catch (err) {
|
|
3021
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3022
|
+
clearInterval(keepAlive2);
|
|
3023
|
+
if (!raw2.writableEnded) {
|
|
3024
|
+
raw2.write(
|
|
3025
|
+
`event: error
|
|
3026
|
+
data: ${JSON.stringify({ error: `Handler "${route.handler}" threw an error: ${msg}` })}
|
|
3027
|
+
|
|
3028
|
+
`
|
|
3029
|
+
);
|
|
3030
|
+
raw2.end();
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
2949
3035
|
reply.hijack();
|
|
2950
3036
|
const raw = reply.raw;
|
|
2951
|
-
raw
|
|
2952
|
-
raw.setHeader("Cache-Control", "no-cache");
|
|
2953
|
-
raw.setHeader("Connection", "keep-alive");
|
|
2954
|
-
raw.setHeader("Access-Control-Allow-Origin", "*");
|
|
2955
|
-
raw.flushHeaders();
|
|
3037
|
+
openStream(raw);
|
|
2956
3038
|
let alive = true;
|
|
2957
3039
|
req.raw.on("close", () => {
|
|
2958
3040
|
alive = false;
|
|
@@ -3003,6 +3085,13 @@ var SSERouteCommand = class {
|
|
|
3003
3085
|
}
|
|
3004
3086
|
}
|
|
3005
3087
|
};
|
|
3088
|
+
function openStream(raw) {
|
|
3089
|
+
raw.setHeader("Content-Type", "text/event-stream");
|
|
3090
|
+
raw.setHeader("Cache-Control", "no-cache");
|
|
3091
|
+
raw.setHeader("Connection", "keep-alive");
|
|
3092
|
+
raw.setHeader("Access-Control-Allow-Origin", "*");
|
|
3093
|
+
raw.flushHeaders();
|
|
3094
|
+
}
|
|
3006
3095
|
|
|
3007
3096
|
// src/router/resource.router.ts
|
|
3008
3097
|
function buildResourceRouteCommands(storage, options) {
|
|
@@ -3045,8 +3134,8 @@ async function createServer(storage, options, handlers = /* @__PURE__ */ new Map
|
|
|
3045
3134
|
new AboutRouteCommand(storage, options, handlers),
|
|
3046
3135
|
new OpenApiRouteCommand(storage, options),
|
|
3047
3136
|
...options.snapshot ? [new SnapshotRouteCommand(storage)] : [],
|
|
3048
|
-
new SSERouteCommand(storage, options.base),
|
|
3049
|
-
new CustomRouteCommand(storage, options.base, handlers),
|
|
3137
|
+
new SSERouteCommand(storage, options.base, handlers, options.idStrategy),
|
|
3138
|
+
new CustomRouteCommand(storage, options.base, handlers, options.idStrategy),
|
|
3050
3139
|
...buildResourceRouteCommands(storage, options)
|
|
3051
3140
|
];
|
|
3052
3141
|
for (const command of commands) {
|
|
@@ -3080,6 +3169,9 @@ function createYrestServerFromStorage(storage, options, handlers = /* @__PURE__
|
|
|
3080
3169
|
},
|
|
3081
3170
|
get url() {
|
|
3082
3171
|
return `http://${options.host}:${_port}${options.base}`;
|
|
3172
|
+
},
|
|
3173
|
+
collection(name) {
|
|
3174
|
+
return createCollectionHandle(storage, name, options.idStrategy);
|
|
3083
3175
|
}
|
|
3084
3176
|
};
|
|
3085
3177
|
}
|
|
@@ -3103,6 +3195,10 @@ function createYrestServer(options) {
|
|
|
3103
3195
|
},
|
|
3104
3196
|
get url() {
|
|
3105
3197
|
return _inner?.url ?? "";
|
|
3198
|
+
},
|
|
3199
|
+
collection(name) {
|
|
3200
|
+
if (!_inner) throw new Error("collection() is only available after start()");
|
|
3201
|
+
return _inner.collection(name);
|
|
3106
3202
|
}
|
|
3107
3203
|
};
|
|
3108
3204
|
}
|
|
@@ -3126,7 +3222,7 @@ function createInMemoryStorage(data) {
|
|
|
3126
3222
|
const raw = data;
|
|
3127
3223
|
const RESERVED2 = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
3128
3224
|
const relations = parseRelations(raw["_rel"]);
|
|
3129
|
-
const routes =
|
|
3225
|
+
const routes = parseRoutes(raw["_routes"]);
|
|
3130
3226
|
const sseRoutes = parseSseRoutes(raw["_routes"]);
|
|
3131
3227
|
const schema = parseSchema(raw["_schema"]);
|
|
3132
3228
|
const collections = Object.fromEntries(
|
|
@@ -3217,11 +3313,239 @@ var yrestOptionsSchema = z.object({
|
|
|
3217
3313
|
*/
|
|
3218
3314
|
idStrategy: z.enum(["increment", "uuid"]).default("increment")
|
|
3219
3315
|
});
|
|
3316
|
+
|
|
3317
|
+
// src/codegen/generateTypes.ts
|
|
3318
|
+
init_esm_shims();
|
|
3319
|
+
var HEADER = "// Auto-generated by yrest types \u2014 do not edit manually.";
|
|
3320
|
+
var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
3321
|
+
var MAX_DEPTH = 4;
|
|
3322
|
+
function generateTypes(input, options = {}) {
|
|
3323
|
+
const { data, schema = {}, relations = {} } = input;
|
|
3324
|
+
const collections = unique([...Object.keys(data), ...Object.keys(schema)]);
|
|
3325
|
+
const names = resolveInterfaceNames(collections);
|
|
3326
|
+
const out = [HEADER];
|
|
3327
|
+
if (options.source) out.push(`// Source: ${options.source}`);
|
|
3328
|
+
out.push("");
|
|
3329
|
+
for (const collection of collections) {
|
|
3330
|
+
out.push(
|
|
3331
|
+
renderInterface(
|
|
3332
|
+
names.get(collection),
|
|
3333
|
+
collection,
|
|
3334
|
+
data[collection] ?? [],
|
|
3335
|
+
schema[collection] ?? {},
|
|
3336
|
+
relations[collection] ?? {}
|
|
3337
|
+
)
|
|
3338
|
+
);
|
|
3339
|
+
out.push("");
|
|
3340
|
+
}
|
|
3341
|
+
out.push(`/** Union of all collection names in the source file. */`);
|
|
3342
|
+
out.push(
|
|
3343
|
+
collections.length > 0 ? `export type CollectionName = ${collections.map((c) => quote(c)).join(" | ")};` : "export type CollectionName = never;"
|
|
3344
|
+
);
|
|
3345
|
+
out.push("");
|
|
3346
|
+
out.push(`/** Maps each collection name to its item array type. */`);
|
|
3347
|
+
out.push("export interface Collections {");
|
|
3348
|
+
for (const collection of collections) {
|
|
3349
|
+
out.push(` ${propKey(collection)}: ${names.get(collection)}[];`);
|
|
3350
|
+
}
|
|
3351
|
+
out.push("}");
|
|
3352
|
+
return out.join("\n") + "\n";
|
|
3353
|
+
}
|
|
3354
|
+
function renderInterface(name, collection, items, fieldDefs, rels) {
|
|
3355
|
+
const records = items.filter(isPlainObject);
|
|
3356
|
+
const fields = collectFields(records);
|
|
3357
|
+
const fieldNames = unique([...fields.keys(), ...Object.keys(fieldDefs)]);
|
|
3358
|
+
const lines = [`/** Item of the \`${collection}\` collection. */`];
|
|
3359
|
+
lines.push(`export interface ${name} {`);
|
|
3360
|
+
for (const field of fieldNames) {
|
|
3361
|
+
const info = fields.get(field);
|
|
3362
|
+
const def = fieldDefs[field];
|
|
3363
|
+
const inferredRequired = info !== void 0 && info.count === records.length && records.length > 0;
|
|
3364
|
+
const required = def?.required ?? inferredRequired;
|
|
3365
|
+
let type = def ? typeFromDef(def, info) : typeOfValues(info.values, 0);
|
|
3366
|
+
if (def?.nullable === true && !type.split(" | ").includes("null")) type = `${type} | null`;
|
|
3367
|
+
for (const doc of fieldDoc(field, def, rels)) lines.push(` ${doc}`);
|
|
3368
|
+
lines.push(` ${propKey(field)}${required ? "" : "?"}: ${type};`);
|
|
3369
|
+
}
|
|
3370
|
+
if (fieldNames.length === 0) lines.push(" [key: string]: unknown;");
|
|
3371
|
+
lines.push("}");
|
|
3372
|
+
return lines.join("\n");
|
|
3373
|
+
}
|
|
3374
|
+
function fieldDoc(field, def, rels) {
|
|
3375
|
+
const text = [];
|
|
3376
|
+
const tags = [];
|
|
3377
|
+
if (def?.description) text.push(def.description.replace(/\*\//g, "*\\/"));
|
|
3378
|
+
const rel = Object.entries(rels).find(
|
|
3379
|
+
([key, r]) => r.type !== "many2many" && (r.foreignKey ?? key) === field
|
|
3380
|
+
)?.[1];
|
|
3381
|
+
if (rel) text.push(`FK \u2192 ${rel.target}.id`);
|
|
3382
|
+
if (def?.format) tags.push(`@format ${def.format}`);
|
|
3383
|
+
if (def?.deprecated) tags.push("@deprecated");
|
|
3384
|
+
if (text.length === 0 && tags.length === 0) return [];
|
|
3385
|
+
if (tags.length === 0) return [`/** ${text.join(" \u2014 ")} */`];
|
|
3386
|
+
return ["/**", ...[...text, ...tags].map((l) => ` * ${l}`), " */"];
|
|
3387
|
+
}
|
|
3388
|
+
function collectFields(records) {
|
|
3389
|
+
const fields = /* @__PURE__ */ new Map();
|
|
3390
|
+
for (const record of records) {
|
|
3391
|
+
for (const [key, value] of Object.entries(record)) {
|
|
3392
|
+
let info = fields.get(key);
|
|
3393
|
+
if (!info) {
|
|
3394
|
+
info = { values: [], count: 0 };
|
|
3395
|
+
fields.set(key, info);
|
|
3396
|
+
}
|
|
3397
|
+
info.count++;
|
|
3398
|
+
info.values.push(value);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
return fields;
|
|
3402
|
+
}
|
|
3403
|
+
function typeOfValues(values, depth) {
|
|
3404
|
+
return unionOf(typeSetOf(values, depth));
|
|
3405
|
+
}
|
|
3406
|
+
function typeSetOf(values, depth) {
|
|
3407
|
+
const types = /* @__PURE__ */ new Set();
|
|
3408
|
+
const objects = [];
|
|
3409
|
+
const elements = [];
|
|
3410
|
+
let sawArray = false;
|
|
3411
|
+
for (const value of values) {
|
|
3412
|
+
if (value === null || value === void 0) types.add("null");
|
|
3413
|
+
else if (typeof value === "string") types.add("string");
|
|
3414
|
+
else if (typeof value === "number") types.add("number");
|
|
3415
|
+
else if (typeof value === "boolean") types.add("boolean");
|
|
3416
|
+
else if (Array.isArray(value)) {
|
|
3417
|
+
sawArray = true;
|
|
3418
|
+
elements.push(...value);
|
|
3419
|
+
} else if (isPlainObject(value)) objects.push(value);
|
|
3420
|
+
else types.add("unknown");
|
|
3421
|
+
}
|
|
3422
|
+
if (objects.length > 0) {
|
|
3423
|
+
types.add(depth >= MAX_DEPTH ? "Record<string, unknown>" : inlineObject(objects, depth + 1));
|
|
3424
|
+
}
|
|
3425
|
+
if (sawArray) {
|
|
3426
|
+
types.add(
|
|
3427
|
+
elements.length === 0 ? "unknown[]" : depth >= MAX_DEPTH ? "unknown[]" : arrayOf(typeSetOf(elements, depth + 1))
|
|
3428
|
+
);
|
|
3429
|
+
}
|
|
3430
|
+
return types;
|
|
3431
|
+
}
|
|
3432
|
+
function inlineObject(records, depth) {
|
|
3433
|
+
const fields = collectFields(records);
|
|
3434
|
+
if (fields.size === 0) return "Record<string, unknown>";
|
|
3435
|
+
const props = [...fields].map(([key, info]) => {
|
|
3436
|
+
const optional = info.count < records.length ? "?" : "";
|
|
3437
|
+
return `${propKey(key)}${optional}: ${typeOfValues(info.values, depth)}`;
|
|
3438
|
+
});
|
|
3439
|
+
return `{ ${props.join("; ")} }`;
|
|
3440
|
+
}
|
|
3441
|
+
function unionOf(types) {
|
|
3442
|
+
const list = [...types];
|
|
3443
|
+
if (list.length === 0) return "unknown";
|
|
3444
|
+
if (list.length === 1 && list[0] === "null") return "unknown";
|
|
3445
|
+
const hasConcreteArray = list.some((t) => t.endsWith("[]") && t !== "unknown[]");
|
|
3446
|
+
const filtered = hasConcreteArray ? list.filter((t) => t !== "unknown[]") : list;
|
|
3447
|
+
filtered.sort((a, b) => a === "null" ? 1 : b === "null" ? -1 : 0);
|
|
3448
|
+
return filtered.join(" | ");
|
|
3449
|
+
}
|
|
3450
|
+
function arrayOf(elements) {
|
|
3451
|
+
const element = unionOf(elements);
|
|
3452
|
+
return element.includes(" | ") && elements.size > 1 ? `(${element})[]` : `${element}[]`;
|
|
3453
|
+
}
|
|
3454
|
+
function typeFromDef(def, info) {
|
|
3455
|
+
if (def.enum && def.enum.length > 0) {
|
|
3456
|
+
return unique(def.enum.map((v) => literal(v))).join(" | ");
|
|
3457
|
+
}
|
|
3458
|
+
if (def.type) {
|
|
3459
|
+
if (def.type === "array") {
|
|
3460
|
+
const items = def.items;
|
|
3461
|
+
if (items?.enum && items.enum.length > 0)
|
|
3462
|
+
return arrayOf(new Set(items.enum.map((v) => literal(v))));
|
|
3463
|
+
if (items?.type) return arrayOf(/* @__PURE__ */ new Set([primitiveFor(items.type)]));
|
|
3464
|
+
const inferred = info ? typeOfValues(info.values, 0) : "";
|
|
3465
|
+
return inferred.endsWith("[]") && !inferred.includes(" | ") ? inferred : "unknown[]";
|
|
3466
|
+
}
|
|
3467
|
+
return primitiveFor(def.type);
|
|
3468
|
+
}
|
|
3469
|
+
return info ? typeOfValues(info.values, 0) : "unknown";
|
|
3470
|
+
}
|
|
3471
|
+
function primitiveFor(type) {
|
|
3472
|
+
switch (type) {
|
|
3473
|
+
case "integer":
|
|
3474
|
+
case "number":
|
|
3475
|
+
return "number";
|
|
3476
|
+
case "string":
|
|
3477
|
+
return "string";
|
|
3478
|
+
case "boolean":
|
|
3479
|
+
return "boolean";
|
|
3480
|
+
case "array":
|
|
3481
|
+
return "unknown[]";
|
|
3482
|
+
case "object":
|
|
3483
|
+
return "Record<string, unknown>";
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
function literal(value) {
|
|
3487
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
3488
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3489
|
+
if (value === null) return "null";
|
|
3490
|
+
return "unknown";
|
|
3491
|
+
}
|
|
3492
|
+
function resolveInterfaceNames(collections) {
|
|
3493
|
+
const names = /* @__PURE__ */ new Map();
|
|
3494
|
+
const used = /* @__PURE__ */ new Set();
|
|
3495
|
+
for (const collection of collections) {
|
|
3496
|
+
const words = splitWords(collection);
|
|
3497
|
+
const candidates = [
|
|
3498
|
+
toPascal([...words.slice(0, -1), singularize(words.at(-1) ?? "")]),
|
|
3499
|
+
toPascal(words)
|
|
3500
|
+
];
|
|
3501
|
+
let name = candidates.find((c) => c && !used.has(c));
|
|
3502
|
+
if (!name) {
|
|
3503
|
+
const base = candidates[1] || "Collection";
|
|
3504
|
+
let i = 2;
|
|
3505
|
+
while (used.has(`${base}${i}`)) i++;
|
|
3506
|
+
name = `${base}${i}`;
|
|
3507
|
+
}
|
|
3508
|
+
if (/^[0-9]/.test(name)) name = `_${name}`;
|
|
3509
|
+
used.add(name);
|
|
3510
|
+
names.set(collection, name);
|
|
3511
|
+
}
|
|
3512
|
+
return names;
|
|
3513
|
+
}
|
|
3514
|
+
function splitWords(name) {
|
|
3515
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
3516
|
+
}
|
|
3517
|
+
function toPascal(words) {
|
|
3518
|
+
return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
3519
|
+
}
|
|
3520
|
+
function singularize(word) {
|
|
3521
|
+
const lower = word.toLowerCase();
|
|
3522
|
+
if (lower.length <= 2) return word;
|
|
3523
|
+
if (/(ss|us|is)$/.test(lower)) return word;
|
|
3524
|
+
if (/[^aeiou]ies$/.test(lower)) return word.slice(0, -3) + "y";
|
|
3525
|
+
if (/(s|x|z|ch|sh)es$/.test(lower)) return word.slice(0, -2);
|
|
3526
|
+
if (lower.endsWith("s")) return word.slice(0, -1);
|
|
3527
|
+
return word;
|
|
3528
|
+
}
|
|
3529
|
+
function propKey(key) {
|
|
3530
|
+
return IDENT_RE.test(key) ? key : JSON.stringify(key);
|
|
3531
|
+
}
|
|
3532
|
+
function quote(value) {
|
|
3533
|
+
return JSON.stringify(value);
|
|
3534
|
+
}
|
|
3535
|
+
function unique(list) {
|
|
3536
|
+
return [...new Set(list)];
|
|
3537
|
+
}
|
|
3538
|
+
function isPlainObject(value) {
|
|
3539
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3540
|
+
}
|
|
3220
3541
|
export {
|
|
3221
3542
|
createServer,
|
|
3222
3543
|
createYrestServer,
|
|
3223
3544
|
createYrestServerFromStorage,
|
|
3224
3545
|
createYrestStorage,
|
|
3546
|
+
defineHandler,
|
|
3547
|
+
defineSseHandler,
|
|
3548
|
+
generateTypes,
|
|
3225
3549
|
yrest,
|
|
3226
3550
|
yrestOptionsSchema
|
|
3227
3551
|
};
|
package/package.json
CHANGED