@manifesto-ai/codegen 0.1.4 → 0.1.5
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 +28 -23
- package/dist/header.d.ts +12 -0
- package/dist/index.d.ts +9 -114
- package/dist/index.js +775 -15
- package/dist/index.js.map +1 -1
- package/dist/path-safety.d.ts +15 -0
- package/dist/plugins/domain-plugin.d.ts +7 -0
- package/dist/plugins/domain-type-inference.d.ts +16 -0
- package/dist/plugins/domain-type-model.d.ts +44 -0
- package/dist/plugins/index.d.ts +6 -0
- package/dist/plugins/ts-plugin.d.ts +16 -0
- package/dist/plugins/zod-plugin.d.ts +11 -0
- package/dist/runner.d.ts +12 -0
- package/dist/stable-hash.d.ts +6 -0
- package/dist/types.d.ts +50 -0
- package/dist/virtual-fs.d.ts +26 -0
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -179,7 +179,9 @@ async function generate(opts) {
|
|
|
179
179
|
if (hasErrors) {
|
|
180
180
|
return { files, artifacts: allArtifacts, diagnostics };
|
|
181
181
|
}
|
|
182
|
-
|
|
182
|
+
if (opts.cleanOutDir) {
|
|
183
|
+
await fs.rm(opts.outDir, { recursive: true, force: true });
|
|
184
|
+
}
|
|
183
185
|
const header = generateHeader({
|
|
184
186
|
sourceId: opts.sourceId,
|
|
185
187
|
schemaHash: opts.schema.hash,
|
|
@@ -215,13 +217,770 @@ function validatePluginNames(plugins) {
|
|
|
215
217
|
return void 0;
|
|
216
218
|
}
|
|
217
219
|
|
|
220
|
+
// src/plugins/domain-type-model.ts
|
|
221
|
+
var UNKNOWN_TYPE = { kind: "unknown" };
|
|
222
|
+
var NULL_TYPE = { kind: "primitive", type: "null" };
|
|
223
|
+
function unknownType() {
|
|
224
|
+
return UNKNOWN_TYPE;
|
|
225
|
+
}
|
|
226
|
+
function primitiveType(type) {
|
|
227
|
+
return type === "null" ? NULL_TYPE : { kind: "primitive", type };
|
|
228
|
+
}
|
|
229
|
+
function literalType(value) {
|
|
230
|
+
return value === null ? NULL_TYPE : { kind: "literal", value };
|
|
231
|
+
}
|
|
232
|
+
function arrayType(element) {
|
|
233
|
+
return { kind: "array", element };
|
|
234
|
+
}
|
|
235
|
+
function tupleType(elements) {
|
|
236
|
+
return { kind: "tuple", elements };
|
|
237
|
+
}
|
|
238
|
+
function objectType(fields) {
|
|
239
|
+
return { kind: "object", fields };
|
|
240
|
+
}
|
|
241
|
+
function recordType(key, value) {
|
|
242
|
+
return { kind: "record", key, value };
|
|
243
|
+
}
|
|
244
|
+
function fieldSpecToDomainField(spec) {
|
|
245
|
+
return {
|
|
246
|
+
type: fieldSpecToDomainType(spec),
|
|
247
|
+
optional: !spec.required
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function fieldSpecToDomainType(spec) {
|
|
251
|
+
let base;
|
|
252
|
+
if (typeof spec.type === "object" && "enum" in spec.type) {
|
|
253
|
+
base = unionOf(
|
|
254
|
+
spec.type.enum.map((value) => literalValueToType(value))
|
|
255
|
+
);
|
|
256
|
+
} else {
|
|
257
|
+
switch (spec.type) {
|
|
258
|
+
case "string":
|
|
259
|
+
base = primitiveType("string");
|
|
260
|
+
break;
|
|
261
|
+
case "number":
|
|
262
|
+
base = primitiveType("number");
|
|
263
|
+
break;
|
|
264
|
+
case "boolean":
|
|
265
|
+
base = primitiveType("boolean");
|
|
266
|
+
break;
|
|
267
|
+
case "null":
|
|
268
|
+
base = NULL_TYPE;
|
|
269
|
+
break;
|
|
270
|
+
case "object":
|
|
271
|
+
if (spec.fields) {
|
|
272
|
+
const fields = {};
|
|
273
|
+
for (const name of Object.keys(spec.fields)) {
|
|
274
|
+
fields[name] = fieldSpecToDomainField(spec.fields[name]);
|
|
275
|
+
}
|
|
276
|
+
base = objectType(fields);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
base = recordType(primitiveType("string"), unknownType());
|
|
280
|
+
break;
|
|
281
|
+
case "array":
|
|
282
|
+
base = arrayType(
|
|
283
|
+
spec.items ? fieldSpecToDomainType(spec.items) : unknownType()
|
|
284
|
+
);
|
|
285
|
+
break;
|
|
286
|
+
default:
|
|
287
|
+
base = unknownType();
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return spec.required ? base : unionOf([base, NULL_TYPE]);
|
|
292
|
+
}
|
|
293
|
+
function literalValueToType(value) {
|
|
294
|
+
if (value === null) {
|
|
295
|
+
return NULL_TYPE;
|
|
296
|
+
}
|
|
297
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
298
|
+
return literalType(value);
|
|
299
|
+
}
|
|
300
|
+
if (Array.isArray(value)) {
|
|
301
|
+
return arrayType(
|
|
302
|
+
value.length === 0 ? unknownType() : unionOf(value.map((item) => literalValueToType(item)))
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if (isPlainObject(value)) {
|
|
306
|
+
const fields = {};
|
|
307
|
+
for (const name of Object.keys(value)) {
|
|
308
|
+
fields[name] = {
|
|
309
|
+
type: literalValueToType(value[name]),
|
|
310
|
+
optional: false
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return objectType(fields);
|
|
314
|
+
}
|
|
315
|
+
return unknownType();
|
|
316
|
+
}
|
|
317
|
+
function unionOf(types) {
|
|
318
|
+
const flattened = [];
|
|
319
|
+
for (const type of types) {
|
|
320
|
+
if (type.kind === "unknown") {
|
|
321
|
+
return type;
|
|
322
|
+
}
|
|
323
|
+
if (type.kind === "union") {
|
|
324
|
+
flattened.push(...type.types);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
flattened.push(type);
|
|
328
|
+
}
|
|
329
|
+
const unique = /* @__PURE__ */ new Map();
|
|
330
|
+
for (const type of flattened) {
|
|
331
|
+
unique.set(stableTypeKey(type), type);
|
|
332
|
+
}
|
|
333
|
+
const deduped = Array.from(unique.values());
|
|
334
|
+
if (deduped.length === 0) {
|
|
335
|
+
return unknownType();
|
|
336
|
+
}
|
|
337
|
+
if (deduped.length === 1) {
|
|
338
|
+
return deduped[0];
|
|
339
|
+
}
|
|
340
|
+
return { kind: "union", types: deduped };
|
|
341
|
+
}
|
|
342
|
+
function removeNullType(type) {
|
|
343
|
+
switch (type.kind) {
|
|
344
|
+
case "primitive":
|
|
345
|
+
return type.type === "null" ? [] : [type];
|
|
346
|
+
case "literal":
|
|
347
|
+
return type.value === null ? [] : [type];
|
|
348
|
+
case "union":
|
|
349
|
+
return type.types.flatMap((member) => removeNullType(member));
|
|
350
|
+
default:
|
|
351
|
+
return [type];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function renderDomainType(type) {
|
|
355
|
+
switch (type.kind) {
|
|
356
|
+
case "unknown":
|
|
357
|
+
return "unknown";
|
|
358
|
+
case "primitive":
|
|
359
|
+
return type.type;
|
|
360
|
+
case "literal":
|
|
361
|
+
return renderLiteral(type.value);
|
|
362
|
+
case "array":
|
|
363
|
+
return `${wrapArrayElement(renderDomainType(type.element), type.element)}[]`;
|
|
364
|
+
case "tuple":
|
|
365
|
+
return `[${type.elements.map((element) => renderDomainType(element)).join(", ")}]`;
|
|
366
|
+
case "object": {
|
|
367
|
+
const fieldNames = Object.keys(type.fields).sort();
|
|
368
|
+
if (fieldNames.length === 0) {
|
|
369
|
+
return "{}";
|
|
370
|
+
}
|
|
371
|
+
const parts = fieldNames.map((name) => {
|
|
372
|
+
const field = type.fields[name];
|
|
373
|
+
const optional = field.optional ? "?" : "";
|
|
374
|
+
return `${name}${optional}: ${renderDomainType(field.type)}`;
|
|
375
|
+
});
|
|
376
|
+
return `{ ${parts.join("; ")} }`;
|
|
377
|
+
}
|
|
378
|
+
case "record":
|
|
379
|
+
return `Record<${renderDomainType(type.key)}, ${renderDomainType(type.value)}>`;
|
|
380
|
+
case "union":
|
|
381
|
+
return type.types.map((member) => renderDomainType(member)).join(" | ");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function wrapArrayElement(rendered, type) {
|
|
385
|
+
if (type.kind === "union") {
|
|
386
|
+
return `(${rendered})`;
|
|
387
|
+
}
|
|
388
|
+
return rendered;
|
|
389
|
+
}
|
|
390
|
+
function renderLiteral(value) {
|
|
391
|
+
if (typeof value === "string") {
|
|
392
|
+
return JSON.stringify(value);
|
|
393
|
+
}
|
|
394
|
+
return String(value);
|
|
395
|
+
}
|
|
396
|
+
function stableTypeKey(type) {
|
|
397
|
+
switch (type.kind) {
|
|
398
|
+
case "unknown":
|
|
399
|
+
return "unknown";
|
|
400
|
+
case "primitive":
|
|
401
|
+
return `primitive:${type.type}`;
|
|
402
|
+
case "literal":
|
|
403
|
+
return `literal:${JSON.stringify(type.value)}`;
|
|
404
|
+
case "array":
|
|
405
|
+
return `array:${stableTypeKey(type.element)}`;
|
|
406
|
+
case "tuple":
|
|
407
|
+
return `tuple:${type.elements.map((element) => stableTypeKey(element)).join(",")}`;
|
|
408
|
+
case "object": {
|
|
409
|
+
const keys = Object.keys(type.fields).sort();
|
|
410
|
+
const fields = keys.map((name) => {
|
|
411
|
+
const field = type.fields[name];
|
|
412
|
+
return `${name}:${field.optional ? "?" : ""}${stableTypeKey(field.type)}`;
|
|
413
|
+
});
|
|
414
|
+
return `object:${fields.join(",")}`;
|
|
415
|
+
}
|
|
416
|
+
case "record":
|
|
417
|
+
return `record:${stableTypeKey(type.key)}:${stableTypeKey(type.value)}`;
|
|
418
|
+
case "union":
|
|
419
|
+
return `union:${type.types.map((member) => stableTypeKey(member)).sort().join("|")}`;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function isPlainObject(value) {
|
|
423
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/plugins/domain-type-inference.ts
|
|
427
|
+
var META_TYPE = objectType({
|
|
428
|
+
actionName: { type: primitiveType("string"), optional: false },
|
|
429
|
+
intentId: { type: primitiveType("string"), optional: false },
|
|
430
|
+
timestamp: { type: primitiveType("number"), optional: false }
|
|
431
|
+
});
|
|
432
|
+
function createInferenceContext(schema, diagnostics, pluginName) {
|
|
433
|
+
return {
|
|
434
|
+
schema,
|
|
435
|
+
pluginName,
|
|
436
|
+
diagnostics,
|
|
437
|
+
warnedMessages: /* @__PURE__ */ new Set(),
|
|
438
|
+
computedCache: /* @__PURE__ */ new Map(),
|
|
439
|
+
computedInFlight: /* @__PURE__ */ new Set()
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function inferComputedType(name, ctx) {
|
|
443
|
+
if (ctx.computedCache.has(name)) {
|
|
444
|
+
return ctx.computedCache.get(name) ?? unknownType();
|
|
445
|
+
}
|
|
446
|
+
const spec = ctx.schema.computed.fields[name];
|
|
447
|
+
if (!spec) {
|
|
448
|
+
warn(ctx, `Unknown computed field "${name}". Emitting "unknown".`);
|
|
449
|
+
return unknownType();
|
|
450
|
+
}
|
|
451
|
+
if (ctx.computedInFlight.has(name)) {
|
|
452
|
+
warn(ctx, `Recursive computed field "${name}" could not be inferred. Emitting "unknown".`);
|
|
453
|
+
return unknownType();
|
|
454
|
+
}
|
|
455
|
+
ctx.computedInFlight.add(name);
|
|
456
|
+
const inferred = inferExprType(spec.expr, ctx, /* @__PURE__ */ new Map());
|
|
457
|
+
ctx.computedInFlight.delete(name);
|
|
458
|
+
ctx.computedCache.set(name, inferred);
|
|
459
|
+
return inferred;
|
|
460
|
+
}
|
|
461
|
+
function inferExprType(expr, ctx, env = /* @__PURE__ */ new Map()) {
|
|
462
|
+
switch (expr.kind) {
|
|
463
|
+
case "lit":
|
|
464
|
+
return literalValueToType(expr.value);
|
|
465
|
+
case "get":
|
|
466
|
+
return inferPathType(expr.path, ctx, env);
|
|
467
|
+
case "eq":
|
|
468
|
+
case "neq":
|
|
469
|
+
case "gt":
|
|
470
|
+
case "gte":
|
|
471
|
+
case "lt":
|
|
472
|
+
case "lte":
|
|
473
|
+
case "and":
|
|
474
|
+
case "or":
|
|
475
|
+
case "not":
|
|
476
|
+
case "startsWith":
|
|
477
|
+
case "endsWith":
|
|
478
|
+
case "strIncludes":
|
|
479
|
+
case "includes":
|
|
480
|
+
case "every":
|
|
481
|
+
case "some":
|
|
482
|
+
case "hasKey":
|
|
483
|
+
case "isNull":
|
|
484
|
+
case "toBoolean":
|
|
485
|
+
return primitiveType("boolean");
|
|
486
|
+
case "add":
|
|
487
|
+
case "sub":
|
|
488
|
+
case "mul":
|
|
489
|
+
case "div":
|
|
490
|
+
case "mod":
|
|
491
|
+
case "min":
|
|
492
|
+
case "max":
|
|
493
|
+
case "abs":
|
|
494
|
+
case "neg":
|
|
495
|
+
case "floor":
|
|
496
|
+
case "ceil":
|
|
497
|
+
case "round":
|
|
498
|
+
case "sqrt":
|
|
499
|
+
case "pow":
|
|
500
|
+
case "sumArray":
|
|
501
|
+
case "strLen":
|
|
502
|
+
case "len":
|
|
503
|
+
case "indexOf":
|
|
504
|
+
case "toNumber":
|
|
505
|
+
return primitiveType("number");
|
|
506
|
+
case "concat":
|
|
507
|
+
case "substring":
|
|
508
|
+
case "trim":
|
|
509
|
+
case "toLowerCase":
|
|
510
|
+
case "toUpperCase":
|
|
511
|
+
case "replace":
|
|
512
|
+
case "typeof":
|
|
513
|
+
case "toString":
|
|
514
|
+
return primitiveType("string");
|
|
515
|
+
case "if":
|
|
516
|
+
return unionOf([
|
|
517
|
+
inferExprType(expr.then, ctx, env),
|
|
518
|
+
inferExprType(expr.else, ctx, env)
|
|
519
|
+
]);
|
|
520
|
+
case "split":
|
|
521
|
+
return arrayType(primitiveType("string"));
|
|
522
|
+
case "at":
|
|
523
|
+
return unionOf([inferIndexedAccessType(expr.array, ctx, env), primitiveType("null")]);
|
|
524
|
+
case "first":
|
|
525
|
+
case "last":
|
|
526
|
+
case "minArray":
|
|
527
|
+
case "maxArray":
|
|
528
|
+
return unionOf([inferCollectionElementType(expr.array, ctx, env), primitiveType("null")]);
|
|
529
|
+
case "slice":
|
|
530
|
+
case "reverse":
|
|
531
|
+
case "unique":
|
|
532
|
+
return inferArrayLikeType(expr.array, ctx, env);
|
|
533
|
+
case "filter": {
|
|
534
|
+
const elementType = inferCollectionElementType(expr.array, ctx, env);
|
|
535
|
+
const nextEnv = withCollectionEnv(env, elementType);
|
|
536
|
+
inferExprType(expr.predicate, ctx, nextEnv);
|
|
537
|
+
return arrayType(elementType);
|
|
538
|
+
}
|
|
539
|
+
case "map": {
|
|
540
|
+
const elementType = inferCollectionElementType(expr.array, ctx, env);
|
|
541
|
+
const nextEnv = withCollectionEnv(env, elementType);
|
|
542
|
+
return arrayType(inferExprType(expr.mapper, ctx, nextEnv));
|
|
543
|
+
}
|
|
544
|
+
case "find": {
|
|
545
|
+
const elementType = inferCollectionElementType(expr.array, ctx, env);
|
|
546
|
+
const nextEnv = withCollectionEnv(env, elementType);
|
|
547
|
+
inferExprType(expr.predicate, ctx, nextEnv);
|
|
548
|
+
return unionOf([elementType, primitiveType("null")]);
|
|
549
|
+
}
|
|
550
|
+
case "append": {
|
|
551
|
+
const baseArray = inferArrayLikeType(expr.array, ctx, env);
|
|
552
|
+
const baseElement = getArrayElementType(baseArray);
|
|
553
|
+
const itemTypes = expr.items.map((item) => inferExprType(item, ctx, env));
|
|
554
|
+
return arrayType(unionOf([baseElement, ...itemTypes]));
|
|
555
|
+
}
|
|
556
|
+
case "flat":
|
|
557
|
+
return inferFlatType(expr.array, ctx, env);
|
|
558
|
+
case "object": {
|
|
559
|
+
const fields = {};
|
|
560
|
+
for (const name of Object.keys(expr.fields)) {
|
|
561
|
+
fields[name] = {
|
|
562
|
+
type: inferExprType(expr.fields[name], ctx, env),
|
|
563
|
+
optional: false
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return objectType(fields);
|
|
567
|
+
}
|
|
568
|
+
case "field":
|
|
569
|
+
return inferFieldType(inferExprType(expr.object, ctx, env), expr.property);
|
|
570
|
+
case "keys":
|
|
571
|
+
return arrayType(primitiveType("string"));
|
|
572
|
+
case "values":
|
|
573
|
+
return arrayType(inferObjectValueType(inferExprType(expr.obj, ctx, env)));
|
|
574
|
+
case "entries":
|
|
575
|
+
return arrayType(
|
|
576
|
+
tupleType([
|
|
577
|
+
primitiveType("string"),
|
|
578
|
+
inferObjectValueType(inferExprType(expr.obj, ctx, env))
|
|
579
|
+
])
|
|
580
|
+
);
|
|
581
|
+
case "merge":
|
|
582
|
+
return inferMergeType(
|
|
583
|
+
expr.objects.map((objectExpr) => inferExprType(objectExpr, ctx, env))
|
|
584
|
+
);
|
|
585
|
+
case "pick":
|
|
586
|
+
return inferPickLikeType(expr.obj, expr.keys, false, ctx, env);
|
|
587
|
+
case "omit":
|
|
588
|
+
return inferPickLikeType(expr.obj, expr.keys, true, ctx, env);
|
|
589
|
+
case "fromEntries":
|
|
590
|
+
return inferFromEntriesType(expr.entries, ctx, env);
|
|
591
|
+
case "coalesce": {
|
|
592
|
+
const members = expr.args.flatMap(
|
|
593
|
+
(arg) => removeNullType(inferExprType(arg, ctx, env))
|
|
594
|
+
);
|
|
595
|
+
return members.length === 0 ? primitiveType("null") : unionOf(members);
|
|
596
|
+
}
|
|
597
|
+
default:
|
|
598
|
+
warn(ctx, `Unsupported expression kind "${expr.kind}". Emitting "unknown".`);
|
|
599
|
+
return unknownType();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function inferPathType(path, ctx, env) {
|
|
603
|
+
const [head, ...tail] = path.split(".");
|
|
604
|
+
if (!head) {
|
|
605
|
+
warn(ctx, `Empty get() path encountered. Emitting "unknown".`);
|
|
606
|
+
return unknownType();
|
|
607
|
+
}
|
|
608
|
+
let base = env.get(head);
|
|
609
|
+
if (!base) {
|
|
610
|
+
if (head === "meta") {
|
|
611
|
+
base = META_TYPE;
|
|
612
|
+
} else if (Object.hasOwn(ctx.schema.state.fields, head)) {
|
|
613
|
+
base = fieldSpecToDomainType(ctx.schema.state.fields[head]);
|
|
614
|
+
} else if (Object.hasOwn(ctx.schema.computed.fields, head)) {
|
|
615
|
+
base = inferComputedType(head, ctx);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (!base) {
|
|
619
|
+
warn(ctx, `Unknown get() path "${path}". Emitting "unknown".`);
|
|
620
|
+
return unknownType();
|
|
621
|
+
}
|
|
622
|
+
return walkPathType(base, tail);
|
|
623
|
+
}
|
|
624
|
+
function walkPathType(base, segments) {
|
|
625
|
+
let current = base;
|
|
626
|
+
for (const segment of segments) {
|
|
627
|
+
current = accessSegmentType(current, segment);
|
|
628
|
+
}
|
|
629
|
+
return current;
|
|
630
|
+
}
|
|
631
|
+
function accessSegmentType(base, segment) {
|
|
632
|
+
switch (base.kind) {
|
|
633
|
+
case "object":
|
|
634
|
+
return Object.hasOwn(base.fields, segment) ? base.fields[segment].type : unknownType();
|
|
635
|
+
case "record":
|
|
636
|
+
return unionOf([base.value, primitiveType("null")]);
|
|
637
|
+
case "array":
|
|
638
|
+
return isNumericSegment(segment) ? unionOf([base.element, primitiveType("null")]) : unknownType();
|
|
639
|
+
case "tuple":
|
|
640
|
+
return isNumericSegment(segment) ? base.elements[Number(segment)] ?? unknownType() : unknownType();
|
|
641
|
+
case "union":
|
|
642
|
+
return unionIgnoringUnknown(
|
|
643
|
+
base.types.map((member) => accessSegmentType(member, segment))
|
|
644
|
+
);
|
|
645
|
+
default:
|
|
646
|
+
return unknownType();
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function inferIndexedAccessType(expr, ctx, env) {
|
|
650
|
+
const base = inferExprType(expr, ctx, env);
|
|
651
|
+
switch (base.kind) {
|
|
652
|
+
case "array":
|
|
653
|
+
return base.element;
|
|
654
|
+
case "tuple":
|
|
655
|
+
return unionOf(base.elements);
|
|
656
|
+
case "record":
|
|
657
|
+
return base.value;
|
|
658
|
+
case "union":
|
|
659
|
+
return unionOf(base.types.map((member) => inferIndexedAccessFromType(member)));
|
|
660
|
+
default:
|
|
661
|
+
return unknownType();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function inferIndexedAccessFromType(type) {
|
|
665
|
+
switch (type.kind) {
|
|
666
|
+
case "array":
|
|
667
|
+
return type.element;
|
|
668
|
+
case "tuple":
|
|
669
|
+
return unionOf(type.elements);
|
|
670
|
+
case "record":
|
|
671
|
+
return type.value;
|
|
672
|
+
default:
|
|
673
|
+
return unknownType();
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function inferCollectionElementType(expr, ctx, env) {
|
|
677
|
+
return getArrayElementType(inferExprType(expr, ctx, env));
|
|
678
|
+
}
|
|
679
|
+
function getArrayElementType(type) {
|
|
680
|
+
switch (type.kind) {
|
|
681
|
+
case "array":
|
|
682
|
+
return type.element;
|
|
683
|
+
case "tuple":
|
|
684
|
+
return unionOf(type.elements);
|
|
685
|
+
case "union":
|
|
686
|
+
return unionOf(type.types.map((member) => getArrayElementType(member)));
|
|
687
|
+
default:
|
|
688
|
+
return unknownType();
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function inferArrayLikeType(expr, ctx, env) {
|
|
692
|
+
const inferred = inferExprType(expr, ctx, env);
|
|
693
|
+
switch (inferred.kind) {
|
|
694
|
+
case "array":
|
|
695
|
+
return inferred;
|
|
696
|
+
case "tuple":
|
|
697
|
+
return arrayType(unionOf(inferred.elements));
|
|
698
|
+
case "union": {
|
|
699
|
+
const arrays = inferred.types.map((member) => inferArrayLikeFromType(member)).filter((member) => member.kind !== "unknown");
|
|
700
|
+
return arrays.length === 0 ? arrayType(unknownType()) : unionOf(arrays);
|
|
701
|
+
}
|
|
702
|
+
default:
|
|
703
|
+
return arrayType(unknownType());
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function inferArrayLikeFromType(type) {
|
|
707
|
+
switch (type.kind) {
|
|
708
|
+
case "array":
|
|
709
|
+
return type;
|
|
710
|
+
case "tuple":
|
|
711
|
+
return arrayType(unionOf(type.elements));
|
|
712
|
+
default:
|
|
713
|
+
return unknownType();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function inferFlatType(expr, ctx, env) {
|
|
717
|
+
const outer = inferArrayLikeType(expr, ctx, env);
|
|
718
|
+
const outerElement = getArrayElementType(outer);
|
|
719
|
+
switch (outerElement.kind) {
|
|
720
|
+
case "array":
|
|
721
|
+
return arrayType(outerElement.element);
|
|
722
|
+
case "tuple":
|
|
723
|
+
return arrayType(unionOf(outerElement.elements));
|
|
724
|
+
case "union": {
|
|
725
|
+
const flatMembers = [];
|
|
726
|
+
for (const member of outerElement.types) {
|
|
727
|
+
if (member.kind === "array") {
|
|
728
|
+
flatMembers.push(member.element);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (member.kind === "tuple") {
|
|
732
|
+
flatMembers.push(unionOf(member.elements));
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
flatMembers.push(member);
|
|
736
|
+
}
|
|
737
|
+
return arrayType(unionOf(flatMembers));
|
|
738
|
+
}
|
|
739
|
+
default:
|
|
740
|
+
return arrayType(outerElement);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function inferFieldType(base, property) {
|
|
744
|
+
switch (base.kind) {
|
|
745
|
+
case "object":
|
|
746
|
+
return Object.hasOwn(base.fields, property) ? base.fields[property].type : primitiveType("null");
|
|
747
|
+
case "record":
|
|
748
|
+
return unionOf([base.value, primitiveType("null")]);
|
|
749
|
+
case "union":
|
|
750
|
+
return unionOf(base.types.map((member) => inferFieldType(member, property)));
|
|
751
|
+
default:
|
|
752
|
+
return primitiveType("null");
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function inferObjectValueType(type) {
|
|
756
|
+
switch (type.kind) {
|
|
757
|
+
case "object": {
|
|
758
|
+
const values = Object.keys(type.fields).map((name) => type.fields[name].type);
|
|
759
|
+
return values.length === 0 ? unknownType() : unionOf(values);
|
|
760
|
+
}
|
|
761
|
+
case "record":
|
|
762
|
+
return type.value;
|
|
763
|
+
case "union":
|
|
764
|
+
return unionOf(type.types.map((member) => inferObjectValueType(member)));
|
|
765
|
+
default:
|
|
766
|
+
return unknownType();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function inferMergeType(types) {
|
|
770
|
+
const fields = {};
|
|
771
|
+
let sawObject = false;
|
|
772
|
+
for (const type of types) {
|
|
773
|
+
if (type.kind !== "object") {
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
sawObject = true;
|
|
777
|
+
for (const name of Object.keys(type.fields)) {
|
|
778
|
+
fields[name] = type.fields[name];
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return sawObject ? objectType(fields) : objectType({});
|
|
782
|
+
}
|
|
783
|
+
function inferPickLikeType(objectExpr, keysExpr, omit, ctx, env) {
|
|
784
|
+
const base = inferExprType(objectExpr, ctx, env);
|
|
785
|
+
if (base.kind !== "object") {
|
|
786
|
+
return objectType({});
|
|
787
|
+
}
|
|
788
|
+
const keys = readStringArrayLiteral(keysExpr);
|
|
789
|
+
if (!keys) {
|
|
790
|
+
return base;
|
|
791
|
+
}
|
|
792
|
+
const selected = new Set(keys);
|
|
793
|
+
const fields = {};
|
|
794
|
+
for (const name of Object.keys(base.fields)) {
|
|
795
|
+
const shouldInclude = omit ? !selected.has(name) : selected.has(name);
|
|
796
|
+
if (shouldInclude) {
|
|
797
|
+
fields[name] = base.fields[name];
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return objectType(fields);
|
|
801
|
+
}
|
|
802
|
+
function inferFromEntriesType(entriesExpr, ctx, env) {
|
|
803
|
+
const literalEntries = readLiteralEntries(entriesExpr);
|
|
804
|
+
if (literalEntries) {
|
|
805
|
+
const valueTypes = literalEntries.map(([, value]) => literalValueToType(value));
|
|
806
|
+
return recordType(primitiveType("string"), unionOf(valueTypes));
|
|
807
|
+
}
|
|
808
|
+
const entriesType = inferExprType(entriesExpr, ctx, env);
|
|
809
|
+
const elementType = getArrayElementType(entriesType);
|
|
810
|
+
if (elementType.kind === "tuple" && elementType.elements.length >= 2) {
|
|
811
|
+
return recordType(primitiveType("string"), elementType.elements[1]);
|
|
812
|
+
}
|
|
813
|
+
return recordType(primitiveType("string"), unknownType());
|
|
814
|
+
}
|
|
815
|
+
function withCollectionEnv(env, itemType) {
|
|
816
|
+
const next = new Map(env);
|
|
817
|
+
next.set("$index", primitiveType("number"));
|
|
818
|
+
next.set("$item", itemType);
|
|
819
|
+
return next;
|
|
820
|
+
}
|
|
821
|
+
function readStringArrayLiteral(expr) {
|
|
822
|
+
if (expr.kind !== "lit" || !Array.isArray(expr.value)) {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
const values = [];
|
|
826
|
+
for (const item of expr.value) {
|
|
827
|
+
if (typeof item !== "string") {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
values.push(item);
|
|
831
|
+
}
|
|
832
|
+
return values;
|
|
833
|
+
}
|
|
834
|
+
function readLiteralEntries(expr) {
|
|
835
|
+
if (expr.kind !== "lit" || !Array.isArray(expr.value)) {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
const entries = [];
|
|
839
|
+
for (const item of expr.value) {
|
|
840
|
+
if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== "string") {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
entries.push([item[0], item[1]]);
|
|
844
|
+
}
|
|
845
|
+
return entries;
|
|
846
|
+
}
|
|
847
|
+
function unionIgnoringUnknown(types) {
|
|
848
|
+
const filtered = types.filter((type) => type.kind !== "unknown");
|
|
849
|
+
return filtered.length === 0 ? unknownType() : unionOf(filtered);
|
|
850
|
+
}
|
|
851
|
+
function isNumericSegment(segment) {
|
|
852
|
+
return /^\d+$/.test(segment);
|
|
853
|
+
}
|
|
854
|
+
function warn(ctx, message) {
|
|
855
|
+
if (ctx.warnedMessages.has(message)) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
ctx.warnedMessages.add(message);
|
|
859
|
+
ctx.diagnostics.push({
|
|
860
|
+
level: "warn",
|
|
861
|
+
plugin: ctx.pluginName,
|
|
862
|
+
message
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// src/plugins/domain-plugin.ts
|
|
867
|
+
var PLUGIN_NAME = "codegen-plugin-domain";
|
|
868
|
+
function createDomainPlugin(options) {
|
|
869
|
+
return {
|
|
870
|
+
name: PLUGIN_NAME,
|
|
871
|
+
generate(ctx) {
|
|
872
|
+
const diagnostics = [];
|
|
873
|
+
const inference = createInferenceContext(ctx.schema, diagnostics, PLUGIN_NAME);
|
|
874
|
+
const interfaceName = options?.interfaceName ?? deriveInterfaceName(ctx) ?? "Domain";
|
|
875
|
+
const fileName = options?.fileName ?? deriveFileName(ctx.sourceId);
|
|
876
|
+
const stateFields = renderFieldBlock(
|
|
877
|
+
ctx.schema.state.fields,
|
|
878
|
+
{ includeReservedState: options?.includeReservedState ?? false },
|
|
879
|
+
(_name, spec) => fieldSpecToDomainField(spec)
|
|
880
|
+
);
|
|
881
|
+
const computedFields = renderFieldBlock(
|
|
882
|
+
ctx.schema.computed.fields,
|
|
883
|
+
{ includeReservedState: true },
|
|
884
|
+
(name) => ({
|
|
885
|
+
type: inferComputedType(name, inference),
|
|
886
|
+
optional: false
|
|
887
|
+
})
|
|
888
|
+
);
|
|
889
|
+
const actionNames = Object.keys(ctx.schema.actions).sort();
|
|
890
|
+
const actionLines = actionNames.map((name) => {
|
|
891
|
+
const action = ctx.schema.actions[name];
|
|
892
|
+
return ` ${name}: ${renderActionSignature(action.input)}`;
|
|
893
|
+
});
|
|
894
|
+
const sections = [
|
|
895
|
+
"export interface " + interfaceName + " {",
|
|
896
|
+
" readonly state: {",
|
|
897
|
+
stateFields,
|
|
898
|
+
" }",
|
|
899
|
+
" readonly computed: {",
|
|
900
|
+
computedFields,
|
|
901
|
+
" }",
|
|
902
|
+
" readonly actions: {",
|
|
903
|
+
actionLines.join("\n"),
|
|
904
|
+
" }",
|
|
905
|
+
"}"
|
|
906
|
+
];
|
|
907
|
+
return {
|
|
908
|
+
patches: [{ op: "set", path: fileName, content: sections.join("\n") + "\n" }],
|
|
909
|
+
diagnostics
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function renderFieldBlock(source, options, mapField) {
|
|
915
|
+
const names = Object.keys(source).filter((name) => options.includeReservedState || !name.startsWith("$")).sort();
|
|
916
|
+
if (names.length === 0) {
|
|
917
|
+
return "";
|
|
918
|
+
}
|
|
919
|
+
return names.map((name) => {
|
|
920
|
+
const field = mapField(name, source[name]);
|
|
921
|
+
const optional = field.optional ? "?" : "";
|
|
922
|
+
return ` ${name}${optional}: ${renderDomainType(field.type)}`;
|
|
923
|
+
}).join("\n");
|
|
924
|
+
}
|
|
925
|
+
function renderActionSignature(input) {
|
|
926
|
+
if (!input || typeof input !== "object" || !("type" in input)) {
|
|
927
|
+
return "() => void";
|
|
928
|
+
}
|
|
929
|
+
if (input.type !== "object" || !input.fields) {
|
|
930
|
+
return `(input: ${renderDomainType(fieldSpecToDomainType(input))}) => void`;
|
|
931
|
+
}
|
|
932
|
+
const names = Object.keys(input.fields);
|
|
933
|
+
if (names.length === 0) {
|
|
934
|
+
return "() => void";
|
|
935
|
+
}
|
|
936
|
+
const params = names.map((name) => {
|
|
937
|
+
const field = fieldSpecToDomainField(input.fields[name]);
|
|
938
|
+
const optional = field.optional ? "?" : "";
|
|
939
|
+
return `${name}${optional}: ${renderDomainType(field.type)}`;
|
|
940
|
+
});
|
|
941
|
+
return `(${params.join(", ")}) => void`;
|
|
942
|
+
}
|
|
943
|
+
function deriveInterfaceName(ctx) {
|
|
944
|
+
const metaName = ctx.schema.meta?.name?.trim();
|
|
945
|
+
if (metaName) {
|
|
946
|
+
return metaName;
|
|
947
|
+
}
|
|
948
|
+
const basename = basenameWithoutExtension(ctx.sourceId);
|
|
949
|
+
if (!basename) {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
const candidate = pascalCase(basename);
|
|
953
|
+
return candidate.endsWith("Domain") ? candidate : `${candidate}Domain`;
|
|
954
|
+
}
|
|
955
|
+
function deriveFileName(sourceId) {
|
|
956
|
+
if (!sourceId) {
|
|
957
|
+
return "domain.ts";
|
|
958
|
+
}
|
|
959
|
+
const normalized = sourceId.replace(/\\/g, "/");
|
|
960
|
+
return `${normalized}.ts`;
|
|
961
|
+
}
|
|
962
|
+
function basenameWithoutExtension(sourceId) {
|
|
963
|
+
if (!sourceId) {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
const normalized = sourceId.replace(/\\/g, "/");
|
|
967
|
+
const basename = normalized.split("/").pop() ?? "";
|
|
968
|
+
if (!basename) {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
return basename.replace(/\.[^.]+$/, "");
|
|
972
|
+
}
|
|
973
|
+
function pascalCase(value) {
|
|
974
|
+
return value.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
975
|
+
}
|
|
976
|
+
|
|
218
977
|
// src/plugins/ts-plugin.ts
|
|
219
|
-
var
|
|
978
|
+
var PLUGIN_NAME2 = "codegen-plugin-ts";
|
|
220
979
|
function createTsPlugin(options) {
|
|
221
980
|
const typesFile = options?.typesFile ?? "types.ts";
|
|
222
981
|
const actionsFile = options?.actionsFile ?? "actions.ts";
|
|
223
982
|
return {
|
|
224
|
-
name:
|
|
983
|
+
name: PLUGIN_NAME2,
|
|
225
984
|
generate(ctx) {
|
|
226
985
|
const diagnostics = [];
|
|
227
986
|
const typeNames = [];
|
|
@@ -238,7 +997,7 @@ function createTsPlugin(options) {
|
|
|
238
997
|
for (const actionName of sortedActionNames) {
|
|
239
998
|
const spec = ctx.schema.actions[actionName];
|
|
240
999
|
if (spec.input) {
|
|
241
|
-
const typeName =
|
|
1000
|
+
const typeName = pascalCase2(actionName) + "Input";
|
|
242
1001
|
actionDecls.push(renderActionInputType(typeName, spec, diagnostics));
|
|
243
1002
|
}
|
|
244
1003
|
}
|
|
@@ -283,7 +1042,7 @@ function mapTypeDefinition(def, diagnostics) {
|
|
|
283
1042
|
case "primitive":
|
|
284
1043
|
return mapPrimitive(def.type);
|
|
285
1044
|
case "literal":
|
|
286
|
-
return
|
|
1045
|
+
return renderLiteral2(def.value);
|
|
287
1046
|
case "array":
|
|
288
1047
|
return `${wrapComplex(mapTypeDefinition(def.element, diagnostics), def.element)}[]`;
|
|
289
1048
|
case "record":
|
|
@@ -306,7 +1065,7 @@ function mapTypeDefinition(def, diagnostics) {
|
|
|
306
1065
|
default: {
|
|
307
1066
|
diagnostics.push({
|
|
308
1067
|
level: "warn",
|
|
309
|
-
plugin:
|
|
1068
|
+
plugin: PLUGIN_NAME2,
|
|
310
1069
|
message: `Unknown TypeDefinition kind: "${def.kind}". Emitting "unknown".`
|
|
311
1070
|
});
|
|
312
1071
|
return "unknown";
|
|
@@ -327,7 +1086,7 @@ function mapPrimitive(type) {
|
|
|
327
1086
|
return "unknown";
|
|
328
1087
|
}
|
|
329
1088
|
}
|
|
330
|
-
function
|
|
1089
|
+
function renderLiteral2(value) {
|
|
331
1090
|
if (typeof value === "string") {
|
|
332
1091
|
return JSON.stringify(value);
|
|
333
1092
|
}
|
|
@@ -355,7 +1114,7 @@ function mapFieldSpec(spec, diagnostics) {
|
|
|
355
1114
|
}
|
|
356
1115
|
function mapFieldType(type, spec, diagnostics) {
|
|
357
1116
|
if (typeof type === "object" && "enum" in type) {
|
|
358
|
-
return type.enum.map((v) =>
|
|
1117
|
+
return type.enum.map((v) => renderLiteral2(v)).join(" | ");
|
|
359
1118
|
}
|
|
360
1119
|
switch (type) {
|
|
361
1120
|
case "string":
|
|
@@ -380,7 +1139,7 @@ function mapFieldType(type, spec, diagnostics) {
|
|
|
380
1139
|
}
|
|
381
1140
|
diagnostics.push({
|
|
382
1141
|
level: "warn",
|
|
383
|
-
plugin:
|
|
1142
|
+
plugin: PLUGIN_NAME2,
|
|
384
1143
|
message: "Object field without fields spec, degrading to Record<string, unknown>"
|
|
385
1144
|
});
|
|
386
1145
|
return "Record<string, unknown>";
|
|
@@ -391,7 +1150,7 @@ function mapFieldType(type, spec, diagnostics) {
|
|
|
391
1150
|
}
|
|
392
1151
|
diagnostics.push({
|
|
393
1152
|
level: "warn",
|
|
394
|
-
plugin:
|
|
1153
|
+
plugin: PLUGIN_NAME2,
|
|
395
1154
|
message: "Array field without items spec, degrading to unknown[]"
|
|
396
1155
|
});
|
|
397
1156
|
return "unknown[]";
|
|
@@ -400,17 +1159,17 @@ function mapFieldType(type, spec, diagnostics) {
|
|
|
400
1159
|
return "unknown";
|
|
401
1160
|
}
|
|
402
1161
|
}
|
|
403
|
-
function
|
|
1162
|
+
function pascalCase2(str) {
|
|
404
1163
|
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
405
1164
|
}
|
|
406
1165
|
|
|
407
1166
|
// src/plugins/zod-plugin.ts
|
|
408
|
-
var
|
|
1167
|
+
var PLUGIN_NAME3 = "codegen-plugin-zod";
|
|
409
1168
|
var TS_PLUGIN_NAME = "codegen-plugin-ts";
|
|
410
1169
|
function createZodPlugin(options) {
|
|
411
1170
|
const schemasFile = options?.schemasFile ?? "base.ts";
|
|
412
1171
|
return {
|
|
413
|
-
name:
|
|
1172
|
+
name: PLUGIN_NAME3,
|
|
414
1173
|
generate(ctx) {
|
|
415
1174
|
const diagnostics = [];
|
|
416
1175
|
const tsArtifacts = ctx.artifacts[TS_PLUGIN_NAME];
|
|
@@ -462,7 +1221,7 @@ function mapTypeDefinition2(def, allTypeNames, diagnostics) {
|
|
|
462
1221
|
default: {
|
|
463
1222
|
diagnostics.push({
|
|
464
1223
|
level: "warn",
|
|
465
|
-
plugin:
|
|
1224
|
+
plugin: PLUGIN_NAME3,
|
|
466
1225
|
message: `Unknown TypeDefinition kind: "${def.kind}". Emitting "z.unknown()".`
|
|
467
1226
|
});
|
|
468
1227
|
return "z.unknown()";
|
|
@@ -497,7 +1256,7 @@ function handleRecord(def, allTypeNames, diagnostics) {
|
|
|
497
1256
|
if (def.key.kind !== "primitive" || def.key.type !== "string") {
|
|
498
1257
|
diagnostics.push({
|
|
499
1258
|
level: "warn",
|
|
500
|
-
plugin:
|
|
1259
|
+
plugin: PLUGIN_NAME3,
|
|
501
1260
|
message: `Record key type is not string (got ${JSON.stringify(def.key)}). Degrading to z.record(z.string(), ...).`
|
|
502
1261
|
});
|
|
503
1262
|
return `z.record(z.string(), ${valueSchema})`;
|
|
@@ -534,6 +1293,7 @@ function handleUnion(types, allTypeNames, diagnostics) {
|
|
|
534
1293
|
return `z.union([${schemas.join(", ")}])`;
|
|
535
1294
|
}
|
|
536
1295
|
export {
|
|
1296
|
+
createDomainPlugin,
|
|
537
1297
|
createTsPlugin,
|
|
538
1298
|
createZodPlugin,
|
|
539
1299
|
generate,
|