@atscript/typescript 0.1.29 → 0.1.31
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/cli.cjs +117 -7
- package/dist/index.cjs +117 -7
- package/dist/index.mjs +118 -8
- package/dist/utils.d.ts +14 -3
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -289,8 +289,9 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
289
289
|
if (isGrp) this.write(")");
|
|
290
290
|
return this.write("[]");
|
|
291
291
|
}
|
|
292
|
+
if ((0, __atscript_core.isPrimitive)(def)) return this.write(renderPrimitiveTypeDef(def.config.type));
|
|
292
293
|
}
|
|
293
|
-
renderStructure(struct, asClass) {
|
|
294
|
+
renderStructure(struct, asClass, interfaceNode) {
|
|
294
295
|
this.blockln("{}");
|
|
295
296
|
const patterns = [];
|
|
296
297
|
const propsDefs = new Set();
|
|
@@ -336,6 +337,7 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
336
337
|
this.writeln("static toJsonSchema: () => any");
|
|
337
338
|
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
338
339
|
this.writeln("static toExampleData?: () => any");
|
|
340
|
+
if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
|
|
339
341
|
}
|
|
340
342
|
this.pop();
|
|
341
343
|
}
|
|
@@ -344,12 +346,63 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
344
346
|
const exported = node.token("export")?.text === "export";
|
|
345
347
|
this.renderJsDoc(node);
|
|
346
348
|
this.write(exported ? "export declare " : "declare ");
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
349
|
+
if (node.hasExtends) {
|
|
350
|
+
const firstParent = node.extendsTokens[0].text;
|
|
351
|
+
this.write(`class ${node.id} extends ${firstParent} `);
|
|
352
|
+
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
353
|
+
if (resolved?.entity === "structure") {
|
|
354
|
+
const firstParentUnwound = this.doc.unwindType(firstParent);
|
|
355
|
+
let firstParentProps;
|
|
356
|
+
if (firstParentUnwound?.def) {
|
|
357
|
+
let fpDef = firstParentUnwound.def;
|
|
358
|
+
if ((0, __atscript_core.isInterface)(fpDef)) {
|
|
359
|
+
if (fpDef.hasExtends) {
|
|
360
|
+
const fpResolved = firstParentUnwound.doc.resolveInterfaceExtends(fpDef);
|
|
361
|
+
if (fpResolved && (0, __atscript_core.isStructure)(fpResolved)) firstParentProps = fpResolved.props;
|
|
362
|
+
}
|
|
363
|
+
if (!firstParentProps) fpDef = fpDef.getDefinition() || fpDef;
|
|
364
|
+
}
|
|
365
|
+
if (!firstParentProps && (0, __atscript_core.isStructure)(fpDef)) firstParentProps = fpDef.props;
|
|
366
|
+
}
|
|
367
|
+
this.renderStructureFiltered(resolved, node.id, firstParentProps, node);
|
|
368
|
+
} else this.writeln("{}");
|
|
369
|
+
} else {
|
|
370
|
+
this.write(`class ${node.id} `);
|
|
371
|
+
const struct = node.getDefinition();
|
|
372
|
+
if (struct?.entity === "structure") this.renderStructure(struct, node.id, node);
|
|
350
373
|
else this.writeln("{}");
|
|
374
|
+
}
|
|
351
375
|
this.writeln();
|
|
352
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Renders a structure block, optionally filtering out props that exist in a parent.
|
|
379
|
+
*/ renderStructureFiltered(struct, asClass, filterProps, interfaceNode) {
|
|
380
|
+
if (!filterProps) return this.renderStructure(struct, asClass, interfaceNode);
|
|
381
|
+
this.blockln("{}");
|
|
382
|
+
for (const prop of Array.from(struct.props.values())) {
|
|
383
|
+
if (filterProps.has(prop.id)) continue;
|
|
384
|
+
if (prop.token("identifier")?.pattern) continue;
|
|
385
|
+
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
386
|
+
if (phantomType) {
|
|
387
|
+
this.writeln(`// ${prop.id}: ${phantomType}`);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const optional = !!prop.token("optional");
|
|
391
|
+
this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
|
|
392
|
+
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
393
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
394
|
+
}
|
|
395
|
+
this.writeln("static __is_atscript_annotated_type: true");
|
|
396
|
+
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
397
|
+
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
398
|
+
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
399
|
+
if (resolveJsonSchemaMode(this.opts) === false) this.writeln("/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */");
|
|
400
|
+
this.writeln("static toJsonSchema: () => any");
|
|
401
|
+
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
402
|
+
this.writeln("static toExampleData?: () => any");
|
|
403
|
+
if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
|
|
404
|
+
this.pop();
|
|
405
|
+
}
|
|
353
406
|
renderType(node) {
|
|
354
407
|
this.writeln();
|
|
355
408
|
const exported = node.token("export")?.text === "export";
|
|
@@ -403,6 +456,50 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFina
|
|
|
403
456
|
this.writeln("const toExampleData: (() => any) | undefined");
|
|
404
457
|
this.popln();
|
|
405
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Checks whether an interface has the `@db.table` annotation.
|
|
461
|
+
*
|
|
462
|
+
* NOTE: Only `@db.table` interfaces get the `__flat` static property.
|
|
463
|
+
* This is intentionally hardcoded — `__flat` exists solely to improve
|
|
464
|
+
* type-safety for filter expressions and `$select`/`$sort` operations
|
|
465
|
+
* in the DB layer. Non-DB interfaces have no use for dot-notation path types.
|
|
466
|
+
*/ hasDbTable(node) {
|
|
467
|
+
return !!node.annotations?.some((a) => a.name === "db.table");
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Renders the `static __flat` property — a map of all dot-notation paths
|
|
471
|
+
* to their TypeScript value types.
|
|
472
|
+
*
|
|
473
|
+
* This enables type-safe autocomplete for filter keys and `$select`/`$sort`
|
|
474
|
+
* paths when using `AtscriptDbTable`. The `FlatOf<T>` utility type extracts
|
|
475
|
+
* this map at the type level.
|
|
476
|
+
*
|
|
477
|
+
* Special rendering rules:
|
|
478
|
+
* - **Intermediate paths** (structures, arrays of structures) → `never`
|
|
479
|
+
* (prevents `$eq` comparisons, but allows `$select` and `$exists`)
|
|
480
|
+
* - **`@db.json` fields** → `string` (stored as serialized JSON in DB,
|
|
481
|
+
* not individually queryable)
|
|
482
|
+
* - **Leaf fields** → their original TypeScript type
|
|
483
|
+
*/ renderFlat(node) {
|
|
484
|
+
const flatMap = (0, __atscript_core.flattenInterfaceNode)(this.doc, node);
|
|
485
|
+
if (flatMap.size === 0) return;
|
|
486
|
+
this.write("static __flat: ");
|
|
487
|
+
this.blockln("{}");
|
|
488
|
+
for (const [path$3, descriptor] of flatMap) {
|
|
489
|
+
this.write(`"${escapeQuotes(path$3)}"`);
|
|
490
|
+
if (descriptor.optional) this.write("?");
|
|
491
|
+
this.write(": ");
|
|
492
|
+
if (descriptor.intermediate) this.writeln("never");
|
|
493
|
+
else if (descriptor.dbJson) this.writeln("string");
|
|
494
|
+
else {
|
|
495
|
+
const originalDef = descriptor.propNode?.getDefinition();
|
|
496
|
+
const defToRender = originalDef && !((0, __atscript_core.isGroup)(descriptor.def) && descriptor.def !== originalDef) ? originalDef : descriptor.def;
|
|
497
|
+
const renderedDef = this.renderTypeDefString(defToRender);
|
|
498
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
this.pop();
|
|
502
|
+
}
|
|
406
503
|
phantomPropType(def) {
|
|
407
504
|
if (!def) return undefined;
|
|
408
505
|
if ((0, __atscript_core.isPrimitive)(def) && def.config.type === "phantom") return def.id;
|
|
@@ -1214,7 +1311,11 @@ else {
|
|
|
1214
1311
|
const unwound = this.doc.unwindType(annotateNode.targetName);
|
|
1215
1312
|
if (unwound?.def) {
|
|
1216
1313
|
let def = this.doc.mergeIntersection(unwound.def);
|
|
1217
|
-
if ((0, __atscript_core.isInterface)(def))
|
|
1314
|
+
if ((0, __atscript_core.isInterface)(def)) if (def.hasExtends) {
|
|
1315
|
+
const resolved = unwound.doc.resolveInterfaceExtends(def);
|
|
1316
|
+
if (resolved) def = resolved;
|
|
1317
|
+
else def = def.getDefinition() || def;
|
|
1318
|
+
} else def = def.getDefinition() || def;
|
|
1218
1319
|
this._adHocAnnotations = this.buildAdHocMap([annotateNode]);
|
|
1219
1320
|
this.annotateType(def, node.id);
|
|
1220
1321
|
this._adHocAnnotations = null;
|
|
@@ -1225,7 +1326,12 @@ else {
|
|
|
1225
1326
|
}
|
|
1226
1327
|
}
|
|
1227
1328
|
} else {
|
|
1228
|
-
|
|
1329
|
+
let def = node.getDefinition();
|
|
1330
|
+
if ((0, __atscript_core.isInterface)(node) && node.hasExtends) {
|
|
1331
|
+
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
1332
|
+
if (resolved) def = resolved;
|
|
1333
|
+
}
|
|
1334
|
+
this.annotateType(def, node.id);
|
|
1229
1335
|
this.indent().defineMetadata(node).unindent();
|
|
1230
1336
|
this.writeln();
|
|
1231
1337
|
}
|
|
@@ -1314,7 +1420,11 @@ else {
|
|
|
1314
1420
|
switch (node.entity) {
|
|
1315
1421
|
case "interface":
|
|
1316
1422
|
case "type": {
|
|
1317
|
-
|
|
1423
|
+
let def = node.getDefinition();
|
|
1424
|
+
if ((0, __atscript_core.isInterface)(node) && node.hasExtends) {
|
|
1425
|
+
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
1426
|
+
if (resolved) def = resolved;
|
|
1427
|
+
}
|
|
1318
1428
|
const handle = this.toAnnotatedHandle(def, true);
|
|
1319
1429
|
const typeId = this.typeIds.get(node) ?? (node.__typeId !== null && node.__typeId !== undefined ? node.id : undefined);
|
|
1320
1430
|
if (typeId) handle.id(typeId);
|
package/dist/index.cjs
CHANGED
|
@@ -287,8 +287,9 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
287
287
|
if (isGrp) this.write(")");
|
|
288
288
|
return this.write("[]");
|
|
289
289
|
}
|
|
290
|
+
if ((0, __atscript_core.isPrimitive)(def)) return this.write(renderPrimitiveTypeDef(def.config.type));
|
|
290
291
|
}
|
|
291
|
-
renderStructure(struct, asClass) {
|
|
292
|
+
renderStructure(struct, asClass, interfaceNode) {
|
|
292
293
|
this.blockln("{}");
|
|
293
294
|
const patterns = [];
|
|
294
295
|
const propsDefs = new Set();
|
|
@@ -334,6 +335,7 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
334
335
|
this.writeln("static toJsonSchema: () => any");
|
|
335
336
|
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
336
337
|
this.writeln("static toExampleData?: () => any");
|
|
338
|
+
if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
|
|
337
339
|
}
|
|
338
340
|
this.pop();
|
|
339
341
|
}
|
|
@@ -342,12 +344,63 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
342
344
|
const exported = node.token("export")?.text === "export";
|
|
343
345
|
this.renderJsDoc(node);
|
|
344
346
|
this.write(exported ? "export declare " : "declare ");
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
347
|
+
if (node.hasExtends) {
|
|
348
|
+
const firstParent = node.extendsTokens[0].text;
|
|
349
|
+
this.write(`class ${node.id} extends ${firstParent} `);
|
|
350
|
+
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
351
|
+
if (resolved?.entity === "structure") {
|
|
352
|
+
const firstParentUnwound = this.doc.unwindType(firstParent);
|
|
353
|
+
let firstParentProps;
|
|
354
|
+
if (firstParentUnwound?.def) {
|
|
355
|
+
let fpDef = firstParentUnwound.def;
|
|
356
|
+
if ((0, __atscript_core.isInterface)(fpDef)) {
|
|
357
|
+
if (fpDef.hasExtends) {
|
|
358
|
+
const fpResolved = firstParentUnwound.doc.resolveInterfaceExtends(fpDef);
|
|
359
|
+
if (fpResolved && (0, __atscript_core.isStructure)(fpResolved)) firstParentProps = fpResolved.props;
|
|
360
|
+
}
|
|
361
|
+
if (!firstParentProps) fpDef = fpDef.getDefinition() || fpDef;
|
|
362
|
+
}
|
|
363
|
+
if (!firstParentProps && (0, __atscript_core.isStructure)(fpDef)) firstParentProps = fpDef.props;
|
|
364
|
+
}
|
|
365
|
+
this.renderStructureFiltered(resolved, node.id, firstParentProps, node);
|
|
366
|
+
} else this.writeln("{}");
|
|
367
|
+
} else {
|
|
368
|
+
this.write(`class ${node.id} `);
|
|
369
|
+
const struct = node.getDefinition();
|
|
370
|
+
if (struct?.entity === "structure") this.renderStructure(struct, node.id, node);
|
|
348
371
|
else this.writeln("{}");
|
|
372
|
+
}
|
|
349
373
|
this.writeln();
|
|
350
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Renders a structure block, optionally filtering out props that exist in a parent.
|
|
377
|
+
*/ renderStructureFiltered(struct, asClass, filterProps, interfaceNode) {
|
|
378
|
+
if (!filterProps) return this.renderStructure(struct, asClass, interfaceNode);
|
|
379
|
+
this.blockln("{}");
|
|
380
|
+
for (const prop of Array.from(struct.props.values())) {
|
|
381
|
+
if (filterProps.has(prop.id)) continue;
|
|
382
|
+
if (prop.token("identifier")?.pattern) continue;
|
|
383
|
+
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
384
|
+
if (phantomType) {
|
|
385
|
+
this.writeln(`// ${prop.id}: ${phantomType}`);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const optional = !!prop.token("optional");
|
|
389
|
+
this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
|
|
390
|
+
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
391
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
392
|
+
}
|
|
393
|
+
this.writeln("static __is_atscript_annotated_type: true");
|
|
394
|
+
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
395
|
+
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
396
|
+
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
397
|
+
if (resolveJsonSchemaMode(this.opts) === false) this.writeln("/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */");
|
|
398
|
+
this.writeln("static toJsonSchema: () => any");
|
|
399
|
+
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
400
|
+
this.writeln("static toExampleData?: () => any");
|
|
401
|
+
if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
|
|
402
|
+
this.pop();
|
|
403
|
+
}
|
|
351
404
|
renderType(node) {
|
|
352
405
|
this.writeln();
|
|
353
406
|
const exported = node.token("export")?.text === "export";
|
|
@@ -401,6 +454,50 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFina
|
|
|
401
454
|
this.writeln("const toExampleData: (() => any) | undefined");
|
|
402
455
|
this.popln();
|
|
403
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Checks whether an interface has the `@db.table` annotation.
|
|
459
|
+
*
|
|
460
|
+
* NOTE: Only `@db.table` interfaces get the `__flat` static property.
|
|
461
|
+
* This is intentionally hardcoded — `__flat` exists solely to improve
|
|
462
|
+
* type-safety for filter expressions and `$select`/`$sort` operations
|
|
463
|
+
* in the DB layer. Non-DB interfaces have no use for dot-notation path types.
|
|
464
|
+
*/ hasDbTable(node) {
|
|
465
|
+
return !!node.annotations?.some((a) => a.name === "db.table");
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Renders the `static __flat` property — a map of all dot-notation paths
|
|
469
|
+
* to their TypeScript value types.
|
|
470
|
+
*
|
|
471
|
+
* This enables type-safe autocomplete for filter keys and `$select`/`$sort`
|
|
472
|
+
* paths when using `AtscriptDbTable`. The `FlatOf<T>` utility type extracts
|
|
473
|
+
* this map at the type level.
|
|
474
|
+
*
|
|
475
|
+
* Special rendering rules:
|
|
476
|
+
* - **Intermediate paths** (structures, arrays of structures) → `never`
|
|
477
|
+
* (prevents `$eq` comparisons, but allows `$select` and `$exists`)
|
|
478
|
+
* - **`@db.json` fields** → `string` (stored as serialized JSON in DB,
|
|
479
|
+
* not individually queryable)
|
|
480
|
+
* - **Leaf fields** → their original TypeScript type
|
|
481
|
+
*/ renderFlat(node) {
|
|
482
|
+
const flatMap = (0, __atscript_core.flattenInterfaceNode)(this.doc, node);
|
|
483
|
+
if (flatMap.size === 0) return;
|
|
484
|
+
this.write("static __flat: ");
|
|
485
|
+
this.blockln("{}");
|
|
486
|
+
for (const [path$2, descriptor] of flatMap) {
|
|
487
|
+
this.write(`"${escapeQuotes(path$2)}"`);
|
|
488
|
+
if (descriptor.optional) this.write("?");
|
|
489
|
+
this.write(": ");
|
|
490
|
+
if (descriptor.intermediate) this.writeln("never");
|
|
491
|
+
else if (descriptor.dbJson) this.writeln("string");
|
|
492
|
+
else {
|
|
493
|
+
const originalDef = descriptor.propNode?.getDefinition();
|
|
494
|
+
const defToRender = originalDef && !((0, __atscript_core.isGroup)(descriptor.def) && descriptor.def !== originalDef) ? originalDef : descriptor.def;
|
|
495
|
+
const renderedDef = this.renderTypeDefString(defToRender);
|
|
496
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
this.pop();
|
|
500
|
+
}
|
|
404
501
|
phantomPropType(def) {
|
|
405
502
|
if (!def) return undefined;
|
|
406
503
|
if ((0, __atscript_core.isPrimitive)(def) && def.config.type === "phantom") return def.id;
|
|
@@ -1212,7 +1309,11 @@ else {
|
|
|
1212
1309
|
const unwound = this.doc.unwindType(annotateNode.targetName);
|
|
1213
1310
|
if (unwound?.def) {
|
|
1214
1311
|
let def = this.doc.mergeIntersection(unwound.def);
|
|
1215
|
-
if ((0, __atscript_core.isInterface)(def))
|
|
1312
|
+
if ((0, __atscript_core.isInterface)(def)) if (def.hasExtends) {
|
|
1313
|
+
const resolved = unwound.doc.resolveInterfaceExtends(def);
|
|
1314
|
+
if (resolved) def = resolved;
|
|
1315
|
+
else def = def.getDefinition() || def;
|
|
1316
|
+
} else def = def.getDefinition() || def;
|
|
1216
1317
|
this._adHocAnnotations = this.buildAdHocMap([annotateNode]);
|
|
1217
1318
|
this.annotateType(def, node.id);
|
|
1218
1319
|
this._adHocAnnotations = null;
|
|
@@ -1223,7 +1324,12 @@ else {
|
|
|
1223
1324
|
}
|
|
1224
1325
|
}
|
|
1225
1326
|
} else {
|
|
1226
|
-
|
|
1327
|
+
let def = node.getDefinition();
|
|
1328
|
+
if ((0, __atscript_core.isInterface)(node) && node.hasExtends) {
|
|
1329
|
+
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
1330
|
+
if (resolved) def = resolved;
|
|
1331
|
+
}
|
|
1332
|
+
this.annotateType(def, node.id);
|
|
1227
1333
|
this.indent().defineMetadata(node).unindent();
|
|
1228
1334
|
this.writeln();
|
|
1229
1335
|
}
|
|
@@ -1312,7 +1418,11 @@ else {
|
|
|
1312
1418
|
switch (node.entity) {
|
|
1313
1419
|
case "interface":
|
|
1314
1420
|
case "type": {
|
|
1315
|
-
|
|
1421
|
+
let def = node.getDefinition();
|
|
1422
|
+
if ((0, __atscript_core.isInterface)(node) && node.hasExtends) {
|
|
1423
|
+
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
1424
|
+
if (resolved) def = resolved;
|
|
1425
|
+
}
|
|
1316
1426
|
const handle = this.toAnnotatedHandle(def, true);
|
|
1317
1427
|
const typeId = this.typeIds.get(node) ?? (node.__typeId !== null && node.__typeId !== undefined ? node.id : undefined);
|
|
1318
1428
|
if (typeId) handle.id(typeId);
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import { DEFAULT_FORMAT, isArray, isConst, isGroup, isInterface, isPrimitive, isRef, isStructure } from "@atscript/core";
|
|
2
|
+
import { DEFAULT_FORMAT, flattenInterfaceNode, isArray, isConst, isGroup, isInterface, isPrimitive, isRef, isStructure } from "@atscript/core";
|
|
3
3
|
|
|
4
4
|
//#region packages/typescript/src/codegen/code-printer.ts
|
|
5
5
|
function _define_property$4(obj, key, value) {
|
|
@@ -262,8 +262,9 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
262
262
|
if (isGrp) this.write(")");
|
|
263
263
|
return this.write("[]");
|
|
264
264
|
}
|
|
265
|
+
if (isPrimitive(def)) return this.write(renderPrimitiveTypeDef(def.config.type));
|
|
265
266
|
}
|
|
266
|
-
renderStructure(struct, asClass) {
|
|
267
|
+
renderStructure(struct, asClass, interfaceNode) {
|
|
267
268
|
this.blockln("{}");
|
|
268
269
|
const patterns = [];
|
|
269
270
|
const propsDefs = new Set();
|
|
@@ -309,6 +310,7 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
309
310
|
this.writeln("static toJsonSchema: () => any");
|
|
310
311
|
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
311
312
|
this.writeln("static toExampleData?: () => any");
|
|
313
|
+
if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
|
|
312
314
|
}
|
|
313
315
|
this.pop();
|
|
314
316
|
}
|
|
@@ -317,12 +319,63 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
317
319
|
const exported = node.token("export")?.text === "export";
|
|
318
320
|
this.renderJsDoc(node);
|
|
319
321
|
this.write(exported ? "export declare " : "declare ");
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
322
|
+
if (node.hasExtends) {
|
|
323
|
+
const firstParent = node.extendsTokens[0].text;
|
|
324
|
+
this.write(`class ${node.id} extends ${firstParent} `);
|
|
325
|
+
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
326
|
+
if (resolved?.entity === "structure") {
|
|
327
|
+
const firstParentUnwound = this.doc.unwindType(firstParent);
|
|
328
|
+
let firstParentProps;
|
|
329
|
+
if (firstParentUnwound?.def) {
|
|
330
|
+
let fpDef = firstParentUnwound.def;
|
|
331
|
+
if (isInterface(fpDef)) {
|
|
332
|
+
if (fpDef.hasExtends) {
|
|
333
|
+
const fpResolved = firstParentUnwound.doc.resolveInterfaceExtends(fpDef);
|
|
334
|
+
if (fpResolved && isStructure(fpResolved)) firstParentProps = fpResolved.props;
|
|
335
|
+
}
|
|
336
|
+
if (!firstParentProps) fpDef = fpDef.getDefinition() || fpDef;
|
|
337
|
+
}
|
|
338
|
+
if (!firstParentProps && isStructure(fpDef)) firstParentProps = fpDef.props;
|
|
339
|
+
}
|
|
340
|
+
this.renderStructureFiltered(resolved, node.id, firstParentProps, node);
|
|
341
|
+
} else this.writeln("{}");
|
|
342
|
+
} else {
|
|
343
|
+
this.write(`class ${node.id} `);
|
|
344
|
+
const struct = node.getDefinition();
|
|
345
|
+
if (struct?.entity === "structure") this.renderStructure(struct, node.id, node);
|
|
323
346
|
else this.writeln("{}");
|
|
347
|
+
}
|
|
324
348
|
this.writeln();
|
|
325
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Renders a structure block, optionally filtering out props that exist in a parent.
|
|
352
|
+
*/ renderStructureFiltered(struct, asClass, filterProps, interfaceNode) {
|
|
353
|
+
if (!filterProps) return this.renderStructure(struct, asClass, interfaceNode);
|
|
354
|
+
this.blockln("{}");
|
|
355
|
+
for (const prop of Array.from(struct.props.values())) {
|
|
356
|
+
if (filterProps.has(prop.id)) continue;
|
|
357
|
+
if (prop.token("identifier")?.pattern) continue;
|
|
358
|
+
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
359
|
+
if (phantomType) {
|
|
360
|
+
this.writeln(`// ${prop.id}: ${phantomType}`);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const optional = !!prop.token("optional");
|
|
364
|
+
this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
|
|
365
|
+
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
366
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
367
|
+
}
|
|
368
|
+
this.writeln("static __is_atscript_annotated_type: true");
|
|
369
|
+
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
370
|
+
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
371
|
+
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
372
|
+
if (resolveJsonSchemaMode(this.opts) === false) this.writeln("/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */");
|
|
373
|
+
this.writeln("static toJsonSchema: () => any");
|
|
374
|
+
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
375
|
+
this.writeln("static toExampleData?: () => any");
|
|
376
|
+
if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
|
|
377
|
+
this.pop();
|
|
378
|
+
}
|
|
326
379
|
renderType(node) {
|
|
327
380
|
this.writeln();
|
|
328
381
|
const exported = node.token("export")?.text === "export";
|
|
@@ -376,6 +429,50 @@ else if (isPrimitive(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
|
|
|
376
429
|
this.writeln("const toExampleData: (() => any) | undefined");
|
|
377
430
|
this.popln();
|
|
378
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Checks whether an interface has the `@db.table` annotation.
|
|
434
|
+
*
|
|
435
|
+
* NOTE: Only `@db.table` interfaces get the `__flat` static property.
|
|
436
|
+
* This is intentionally hardcoded — `__flat` exists solely to improve
|
|
437
|
+
* type-safety for filter expressions and `$select`/`$sort` operations
|
|
438
|
+
* in the DB layer. Non-DB interfaces have no use for dot-notation path types.
|
|
439
|
+
*/ hasDbTable(node) {
|
|
440
|
+
return !!node.annotations?.some((a) => a.name === "db.table");
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Renders the `static __flat` property — a map of all dot-notation paths
|
|
444
|
+
* to their TypeScript value types.
|
|
445
|
+
*
|
|
446
|
+
* This enables type-safe autocomplete for filter keys and `$select`/`$sort`
|
|
447
|
+
* paths when using `AtscriptDbTable`. The `FlatOf<T>` utility type extracts
|
|
448
|
+
* this map at the type level.
|
|
449
|
+
*
|
|
450
|
+
* Special rendering rules:
|
|
451
|
+
* - **Intermediate paths** (structures, arrays of structures) → `never`
|
|
452
|
+
* (prevents `$eq` comparisons, but allows `$select` and `$exists`)
|
|
453
|
+
* - **`@db.json` fields** → `string` (stored as serialized JSON in DB,
|
|
454
|
+
* not individually queryable)
|
|
455
|
+
* - **Leaf fields** → their original TypeScript type
|
|
456
|
+
*/ renderFlat(node) {
|
|
457
|
+
const flatMap = flattenInterfaceNode(this.doc, node);
|
|
458
|
+
if (flatMap.size === 0) return;
|
|
459
|
+
this.write("static __flat: ");
|
|
460
|
+
this.blockln("{}");
|
|
461
|
+
for (const [path$1, descriptor] of flatMap) {
|
|
462
|
+
this.write(`"${escapeQuotes(path$1)}"`);
|
|
463
|
+
if (descriptor.optional) this.write("?");
|
|
464
|
+
this.write(": ");
|
|
465
|
+
if (descriptor.intermediate) this.writeln("never");
|
|
466
|
+
else if (descriptor.dbJson) this.writeln("string");
|
|
467
|
+
else {
|
|
468
|
+
const originalDef = descriptor.propNode?.getDefinition();
|
|
469
|
+
const defToRender = originalDef && !(isGroup(descriptor.def) && descriptor.def !== originalDef) ? originalDef : descriptor.def;
|
|
470
|
+
const renderedDef = this.renderTypeDefString(defToRender);
|
|
471
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
this.pop();
|
|
475
|
+
}
|
|
379
476
|
phantomPropType(def) {
|
|
380
477
|
if (!def) return undefined;
|
|
381
478
|
if (isPrimitive(def) && def.config.type === "phantom") return def.id;
|
|
@@ -1187,7 +1284,11 @@ else {
|
|
|
1187
1284
|
const unwound = this.doc.unwindType(annotateNode.targetName);
|
|
1188
1285
|
if (unwound?.def) {
|
|
1189
1286
|
let def = this.doc.mergeIntersection(unwound.def);
|
|
1190
|
-
if (isInterface(def))
|
|
1287
|
+
if (isInterface(def)) if (def.hasExtends) {
|
|
1288
|
+
const resolved = unwound.doc.resolveInterfaceExtends(def);
|
|
1289
|
+
if (resolved) def = resolved;
|
|
1290
|
+
else def = def.getDefinition() || def;
|
|
1291
|
+
} else def = def.getDefinition() || def;
|
|
1191
1292
|
this._adHocAnnotations = this.buildAdHocMap([annotateNode]);
|
|
1192
1293
|
this.annotateType(def, node.id);
|
|
1193
1294
|
this._adHocAnnotations = null;
|
|
@@ -1198,7 +1299,12 @@ else {
|
|
|
1198
1299
|
}
|
|
1199
1300
|
}
|
|
1200
1301
|
} else {
|
|
1201
|
-
|
|
1302
|
+
let def = node.getDefinition();
|
|
1303
|
+
if (isInterface(node) && node.hasExtends) {
|
|
1304
|
+
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
1305
|
+
if (resolved) def = resolved;
|
|
1306
|
+
}
|
|
1307
|
+
this.annotateType(def, node.id);
|
|
1202
1308
|
this.indent().defineMetadata(node).unindent();
|
|
1203
1309
|
this.writeln();
|
|
1204
1310
|
}
|
|
@@ -1287,7 +1393,11 @@ else {
|
|
|
1287
1393
|
switch (node.entity) {
|
|
1288
1394
|
case "interface":
|
|
1289
1395
|
case "type": {
|
|
1290
|
-
|
|
1396
|
+
let def = node.getDefinition();
|
|
1397
|
+
if (isInterface(node) && node.hasExtends) {
|
|
1398
|
+
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
1399
|
+
if (resolved) def = resolved;
|
|
1400
|
+
}
|
|
1291
1401
|
const handle = this.toAnnotatedHandle(def, true);
|
|
1292
1402
|
const typeId = this.typeIds.get(node) ?? (node.__typeId !== null && node.__typeId !== undefined ? node.id : undefined);
|
|
1293
1403
|
if (typeId) handle.id(typeId);
|
package/dist/utils.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ interface TValidatorPluginContext {
|
|
|
48
48
|
* @typeParam T - The annotated type definition.
|
|
49
49
|
* @typeParam DataType - The TypeScript type that `validate` narrows to (auto-inferred).
|
|
50
50
|
*/
|
|
51
|
-
declare class Validator<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> {
|
|
51
|
+
declare class Validator<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType$1<T>> {
|
|
52
52
|
protected readonly def: T;
|
|
53
53
|
protected opts: TValidatorOptions;
|
|
54
54
|
constructor(def: T, opts?: Partial<TValidatorOptions>);
|
|
@@ -159,7 +159,7 @@ type InferDataType<T> = T extends {
|
|
|
159
159
|
* type Data = TAtscriptDataType<typeof MyInterface>
|
|
160
160
|
* ```
|
|
161
161
|
*/
|
|
162
|
-
type TAtscriptDataType<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType> = T extends {
|
|
162
|
+
type TAtscriptDataType$1<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType> = T extends {
|
|
163
163
|
type: {
|
|
164
164
|
__dataType?: infer D;
|
|
165
165
|
};
|
|
@@ -535,5 +535,16 @@ declare function serializeAnnotatedType(type: TAtscriptAnnotatedType, options?:
|
|
|
535
535
|
*/
|
|
536
536
|
declare function deserializeAnnotatedType(data: TSerializedAnnotatedType): TAtscriptAnnotatedType;
|
|
537
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Extracts the flat dot-notation type map from an Atscript annotated type.
|
|
540
|
+
* If the type has a `__flat` static property (emitted for `@db.table` interfaces),
|
|
541
|
+
* returns that flat map. Otherwise falls back to `TAtscriptDataType<T>`.
|
|
542
|
+
*
|
|
543
|
+
* Use this for type-safe filters and selects with dot-notation field paths.
|
|
544
|
+
*/
|
|
545
|
+
type FlatOf<T> = T extends {
|
|
546
|
+
__flat: infer F;
|
|
547
|
+
} ? F : TAtscriptDataType<T>;
|
|
548
|
+
|
|
538
549
|
export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
|
|
539
|
-
export type { InferDataType, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TJsonSchema, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
|
|
550
|
+
export type { FlatOf, InferDataType, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType$1 as TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TJsonSchema, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/typescript",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Atscript: typescript-gen support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotations",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"vitest": "3.2.4"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@atscript/core": "^0.1.
|
|
67
|
+
"@atscript/core": "^0.1.31"
|
|
68
68
|
},
|
|
69
69
|
"build": [
|
|
70
70
|
{},
|