@yrest/cli 0.13.1 → 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/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
- if (!Array.isArray(rawEvents)) return null;
174
- const events = rawEvents.map(normaliseEvent).filter((e) => e !== null);
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
- var ALLOWED_EXTENSIONS = [".js", ".mjs", ".cjs"];
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 .cjs files are allowed\x1B[0m`
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&ensp;<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.setHeader("Content-Type", "text/event-stream");
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 = Array.isArray(raw["_routes"]) ? raw["_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
  });