@manifesto-ai/codegen 0.1.4 → 0.2.1
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 +50 -22
- package/dist/compiler-codegen.d.ts +15 -0
- package/dist/header.d.ts +12 -0
- package/dist/index.d.ts +11 -114
- package/dist/index.js +798 -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,792 @@ 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
|
+
|
|
977
|
+
// src/compiler-codegen.ts
|
|
978
|
+
function createCompilerCodegen(options = {}) {
|
|
979
|
+
const outDir = options.outDir ?? ".";
|
|
980
|
+
const plugins = options.plugins ?? [createDomainPlugin()];
|
|
981
|
+
return async (input) => {
|
|
982
|
+
const result = await generate({
|
|
983
|
+
schema: input.schema,
|
|
984
|
+
sourceId: input.sourceId,
|
|
985
|
+
outDir,
|
|
986
|
+
plugins,
|
|
987
|
+
stamp: options.stamp
|
|
988
|
+
});
|
|
989
|
+
const errors = result.diagnostics.filter((diagnostic) => diagnostic.level === "error");
|
|
990
|
+
if (errors.length > 0) {
|
|
991
|
+
const details = errors.map((diagnostic) => `[${diagnostic.plugin}] ${diagnostic.message}`).join("\n");
|
|
992
|
+
throw new Error(`Codegen failed for ${input.sourceId}
|
|
993
|
+
${details}`);
|
|
994
|
+
}
|
|
995
|
+
return result;
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
218
999
|
// src/plugins/ts-plugin.ts
|
|
219
|
-
var
|
|
1000
|
+
var PLUGIN_NAME2 = "codegen-plugin-ts";
|
|
220
1001
|
function createTsPlugin(options) {
|
|
221
1002
|
const typesFile = options?.typesFile ?? "types.ts";
|
|
222
1003
|
const actionsFile = options?.actionsFile ?? "actions.ts";
|
|
223
1004
|
return {
|
|
224
|
-
name:
|
|
1005
|
+
name: PLUGIN_NAME2,
|
|
225
1006
|
generate(ctx) {
|
|
226
1007
|
const diagnostics = [];
|
|
227
1008
|
const typeNames = [];
|
|
@@ -238,7 +1019,7 @@ function createTsPlugin(options) {
|
|
|
238
1019
|
for (const actionName of sortedActionNames) {
|
|
239
1020
|
const spec = ctx.schema.actions[actionName];
|
|
240
1021
|
if (spec.input) {
|
|
241
|
-
const typeName =
|
|
1022
|
+
const typeName = pascalCase2(actionName) + "Input";
|
|
242
1023
|
actionDecls.push(renderActionInputType(typeName, spec, diagnostics));
|
|
243
1024
|
}
|
|
244
1025
|
}
|
|
@@ -283,7 +1064,7 @@ function mapTypeDefinition(def, diagnostics) {
|
|
|
283
1064
|
case "primitive":
|
|
284
1065
|
return mapPrimitive(def.type);
|
|
285
1066
|
case "literal":
|
|
286
|
-
return
|
|
1067
|
+
return renderLiteral2(def.value);
|
|
287
1068
|
case "array":
|
|
288
1069
|
return `${wrapComplex(mapTypeDefinition(def.element, diagnostics), def.element)}[]`;
|
|
289
1070
|
case "record":
|
|
@@ -306,7 +1087,7 @@ function mapTypeDefinition(def, diagnostics) {
|
|
|
306
1087
|
default: {
|
|
307
1088
|
diagnostics.push({
|
|
308
1089
|
level: "warn",
|
|
309
|
-
plugin:
|
|
1090
|
+
plugin: PLUGIN_NAME2,
|
|
310
1091
|
message: `Unknown TypeDefinition kind: "${def.kind}". Emitting "unknown".`
|
|
311
1092
|
});
|
|
312
1093
|
return "unknown";
|
|
@@ -327,7 +1108,7 @@ function mapPrimitive(type) {
|
|
|
327
1108
|
return "unknown";
|
|
328
1109
|
}
|
|
329
1110
|
}
|
|
330
|
-
function
|
|
1111
|
+
function renderLiteral2(value) {
|
|
331
1112
|
if (typeof value === "string") {
|
|
332
1113
|
return JSON.stringify(value);
|
|
333
1114
|
}
|
|
@@ -355,7 +1136,7 @@ function mapFieldSpec(spec, diagnostics) {
|
|
|
355
1136
|
}
|
|
356
1137
|
function mapFieldType(type, spec, diagnostics) {
|
|
357
1138
|
if (typeof type === "object" && "enum" in type) {
|
|
358
|
-
return type.enum.map((v) =>
|
|
1139
|
+
return type.enum.map((v) => renderLiteral2(v)).join(" | ");
|
|
359
1140
|
}
|
|
360
1141
|
switch (type) {
|
|
361
1142
|
case "string":
|
|
@@ -380,7 +1161,7 @@ function mapFieldType(type, spec, diagnostics) {
|
|
|
380
1161
|
}
|
|
381
1162
|
diagnostics.push({
|
|
382
1163
|
level: "warn",
|
|
383
|
-
plugin:
|
|
1164
|
+
plugin: PLUGIN_NAME2,
|
|
384
1165
|
message: "Object field without fields spec, degrading to Record<string, unknown>"
|
|
385
1166
|
});
|
|
386
1167
|
return "Record<string, unknown>";
|
|
@@ -391,7 +1172,7 @@ function mapFieldType(type, spec, diagnostics) {
|
|
|
391
1172
|
}
|
|
392
1173
|
diagnostics.push({
|
|
393
1174
|
level: "warn",
|
|
394
|
-
plugin:
|
|
1175
|
+
plugin: PLUGIN_NAME2,
|
|
395
1176
|
message: "Array field without items spec, degrading to unknown[]"
|
|
396
1177
|
});
|
|
397
1178
|
return "unknown[]";
|
|
@@ -400,17 +1181,17 @@ function mapFieldType(type, spec, diagnostics) {
|
|
|
400
1181
|
return "unknown";
|
|
401
1182
|
}
|
|
402
1183
|
}
|
|
403
|
-
function
|
|
1184
|
+
function pascalCase2(str) {
|
|
404
1185
|
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
405
1186
|
}
|
|
406
1187
|
|
|
407
1188
|
// src/plugins/zod-plugin.ts
|
|
408
|
-
var
|
|
1189
|
+
var PLUGIN_NAME3 = "codegen-plugin-zod";
|
|
409
1190
|
var TS_PLUGIN_NAME = "codegen-plugin-ts";
|
|
410
1191
|
function createZodPlugin(options) {
|
|
411
1192
|
const schemasFile = options?.schemasFile ?? "base.ts";
|
|
412
1193
|
return {
|
|
413
|
-
name:
|
|
1194
|
+
name: PLUGIN_NAME3,
|
|
414
1195
|
generate(ctx) {
|
|
415
1196
|
const diagnostics = [];
|
|
416
1197
|
const tsArtifacts = ctx.artifacts[TS_PLUGIN_NAME];
|
|
@@ -462,7 +1243,7 @@ function mapTypeDefinition2(def, allTypeNames, diagnostics) {
|
|
|
462
1243
|
default: {
|
|
463
1244
|
diagnostics.push({
|
|
464
1245
|
level: "warn",
|
|
465
|
-
plugin:
|
|
1246
|
+
plugin: PLUGIN_NAME3,
|
|
466
1247
|
message: `Unknown TypeDefinition kind: "${def.kind}". Emitting "z.unknown()".`
|
|
467
1248
|
});
|
|
468
1249
|
return "z.unknown()";
|
|
@@ -497,7 +1278,7 @@ function handleRecord(def, allTypeNames, diagnostics) {
|
|
|
497
1278
|
if (def.key.kind !== "primitive" || def.key.type !== "string") {
|
|
498
1279
|
diagnostics.push({
|
|
499
1280
|
level: "warn",
|
|
500
|
-
plugin:
|
|
1281
|
+
plugin: PLUGIN_NAME3,
|
|
501
1282
|
message: `Record key type is not string (got ${JSON.stringify(def.key)}). Degrading to z.record(z.string(), ...).`
|
|
502
1283
|
});
|
|
503
1284
|
return `z.record(z.string(), ${valueSchema})`;
|
|
@@ -534,6 +1315,8 @@ function handleUnion(types, allTypeNames, diagnostics) {
|
|
|
534
1315
|
return `z.union([${schemas.join(", ")}])`;
|
|
535
1316
|
}
|
|
536
1317
|
export {
|
|
1318
|
+
createCompilerCodegen,
|
|
1319
|
+
createDomainPlugin,
|
|
537
1320
|
createTsPlugin,
|
|
538
1321
|
createZodPlugin,
|
|
539
1322
|
generate,
|