@atscript/typescript 0.1.30 → 0.1.32
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 +254 -182
- package/dist/index.cjs +254 -182
- package/dist/index.mjs +255 -183
- package/dist/utils.cjs +93 -55
- package/dist/utils.d.ts +27 -3
- package/dist/utils.mjs +93 -55
- package/package.json +2 -2
- package/skills/atscript-typescript/annotations.md +2 -1
package/dist/index.cjs
CHANGED
|
@@ -141,6 +141,9 @@ else obj[key] = value;
|
|
|
141
141
|
return obj;
|
|
142
142
|
}
|
|
143
143
|
var BaseRenderer = class extends CodePrinter {
|
|
144
|
+
get unused() {
|
|
145
|
+
return this._unused ?? (this._unused = new Set(this.doc.getUnusedTokens().map((t) => t.text)));
|
|
146
|
+
}
|
|
144
147
|
pre() {}
|
|
145
148
|
post() {}
|
|
146
149
|
render() {
|
|
@@ -194,15 +197,14 @@ var BaseRenderer = class extends CodePrinter {
|
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
constructor(doc) {
|
|
197
|
-
super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "
|
|
198
|
-
this.unused = new Set(this.doc.getUnusedTokens().map((t) => t.text));
|
|
200
|
+
super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "_unused", void 0), this.doc = doc;
|
|
199
201
|
}
|
|
200
202
|
};
|
|
201
203
|
|
|
202
204
|
//#endregion
|
|
203
205
|
//#region packages/typescript/src/codegen/utils.ts
|
|
206
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
204
207
|
function wrapProp(name) {
|
|
205
|
-
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
206
208
|
if (!validIdentifier.test(name)) return `"${escapeQuotes(name)}"`;
|
|
207
209
|
return name;
|
|
208
210
|
}
|
|
@@ -287,14 +289,16 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
287
289
|
if (isGrp) this.write(")");
|
|
288
290
|
return this.write("[]");
|
|
289
291
|
}
|
|
292
|
+
if ((0, __atscript_core.isPrimitive)(def)) return this.write(renderPrimitiveTypeDef(def.config.type));
|
|
290
293
|
}
|
|
291
|
-
renderStructure(struct, asClass) {
|
|
294
|
+
renderStructure(struct, asClass, interfaceNode, filterProps) {
|
|
292
295
|
this.blockln("{}");
|
|
293
296
|
const patterns = [];
|
|
294
297
|
const propsDefs = new Set();
|
|
295
|
-
for (const prop of
|
|
298
|
+
for (const prop of struct.props.values()) {
|
|
299
|
+
if (filterProps?.has(prop.id)) continue;
|
|
296
300
|
if (prop.token("identifier")?.pattern) {
|
|
297
|
-
patterns.push(prop);
|
|
301
|
+
if (!filterProps) patterns.push(prop);
|
|
298
302
|
continue;
|
|
299
303
|
}
|
|
300
304
|
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
@@ -310,33 +314,36 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
310
314
|
}
|
|
311
315
|
if (patterns.length > 0) {
|
|
312
316
|
this.write(`[key: string]: `);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
for (const def of defs) {
|
|
319
|
-
this.writeln();
|
|
320
|
-
this.write("| ");
|
|
321
|
-
def.split("\n").forEach((l) => this.write(l.trim()));
|
|
322
|
-
}
|
|
323
|
-
this.unindent();
|
|
317
|
+
for (const prop of patterns) propsDefs.add(this.renderTypeDefString(prop.getDefinition()));
|
|
318
|
+
const defs = Array.from(propsDefs);
|
|
319
|
+
if (defs.length > 1) {
|
|
320
|
+
this.indent();
|
|
321
|
+
for (const def of defs) {
|
|
324
322
|
this.writeln();
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
this.writeln(
|
|
331
|
-
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
332
|
-
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
333
|
-
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. */");
|
|
334
|
-
this.writeln("static toJsonSchema: () => any");
|
|
335
|
-
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
336
|
-
this.writeln("static toExampleData?: () => any");
|
|
323
|
+
this.write("| ");
|
|
324
|
+
def.split("\n").forEach((l) => this.write(l.trim()));
|
|
325
|
+
}
|
|
326
|
+
this.unindent();
|
|
327
|
+
this.writeln();
|
|
328
|
+
} else defs[0].split("\n").forEach((l) => this.writeln(l));
|
|
337
329
|
}
|
|
330
|
+
if (asClass) this.renderStaticDeclarations(asClass, interfaceNode);
|
|
338
331
|
this.pop();
|
|
339
332
|
}
|
|
333
|
+
renderStaticDeclarations(asClass, interfaceNode) {
|
|
334
|
+
this.writeln("static __is_atscript_annotated_type: true");
|
|
335
|
+
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
336
|
+
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
337
|
+
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
338
|
+
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. */");
|
|
339
|
+
this.writeln("static toJsonSchema: () => any");
|
|
340
|
+
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
341
|
+
this.writeln("static toExampleData?: () => any");
|
|
342
|
+
if (interfaceNode && this.hasDbTable(interfaceNode)) {
|
|
343
|
+
this.renderFlat(interfaceNode);
|
|
344
|
+
this.renderPk(interfaceNode);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
340
347
|
renderInterface(node) {
|
|
341
348
|
this.writeln();
|
|
342
349
|
const exported = node.token("export")?.text === "export";
|
|
@@ -360,44 +367,16 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
360
367
|
}
|
|
361
368
|
if (!firstParentProps && (0, __atscript_core.isStructure)(fpDef)) firstParentProps = fpDef.props;
|
|
362
369
|
}
|
|
363
|
-
this.
|
|
370
|
+
this.renderStructure(resolved, node.id, node, firstParentProps);
|
|
364
371
|
} else this.writeln("{}");
|
|
365
372
|
} else {
|
|
366
373
|
this.write(`class ${node.id} `);
|
|
367
374
|
const struct = node.getDefinition();
|
|
368
|
-
if (struct?.entity === "structure") this.renderStructure(struct, node.id);
|
|
375
|
+
if (struct?.entity === "structure") this.renderStructure(struct, node.id, node);
|
|
369
376
|
else this.writeln("{}");
|
|
370
377
|
}
|
|
371
378
|
this.writeln();
|
|
372
379
|
}
|
|
373
|
-
/**
|
|
374
|
-
* Renders a structure block, optionally filtering out props that exist in a parent.
|
|
375
|
-
*/ renderStructureFiltered(struct, asClass, filterProps) {
|
|
376
|
-
if (!filterProps) return this.renderStructure(struct, asClass);
|
|
377
|
-
this.blockln("{}");
|
|
378
|
-
for (const prop of Array.from(struct.props.values())) {
|
|
379
|
-
if (filterProps.has(prop.id)) continue;
|
|
380
|
-
if (prop.token("identifier")?.pattern) continue;
|
|
381
|
-
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
382
|
-
if (phantomType) {
|
|
383
|
-
this.writeln(`// ${prop.id}: ${phantomType}`);
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
386
|
-
const optional = !!prop.token("optional");
|
|
387
|
-
this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
|
|
388
|
-
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
389
|
-
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
390
|
-
}
|
|
391
|
-
this.writeln("static __is_atscript_annotated_type: true");
|
|
392
|
-
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
393
|
-
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
394
|
-
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
395
|
-
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. */");
|
|
396
|
-
this.writeln("static toJsonSchema: () => any");
|
|
397
|
-
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
398
|
-
this.writeln("static toExampleData?: () => any");
|
|
399
|
-
this.pop();
|
|
400
|
-
}
|
|
401
380
|
renderType(node) {
|
|
402
381
|
this.writeln();
|
|
403
382
|
const exported = node.token("export")?.text === "export";
|
|
@@ -451,6 +430,87 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFina
|
|
|
451
430
|
this.writeln("const toExampleData: (() => any) | undefined");
|
|
452
431
|
this.popln();
|
|
453
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Checks whether an interface has the `@db.table` annotation.
|
|
435
|
+
*
|
|
436
|
+
* NOTE: Only `@db.table` interfaces get the `__flat` static property.
|
|
437
|
+
* This is intentionally hardcoded — `__flat` exists solely to improve
|
|
438
|
+
* type-safety for filter expressions and `$select`/`$sort` operations
|
|
439
|
+
* in the DB layer. Non-DB interfaces have no use for dot-notation path types.
|
|
440
|
+
*/ hasDbTable(node) {
|
|
441
|
+
return !!node.annotations?.some((a) => a.name === "db.table");
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Renders the `static __flat` property — a map of all dot-notation paths
|
|
445
|
+
* to their TypeScript value types.
|
|
446
|
+
*
|
|
447
|
+
* This enables type-safe autocomplete for filter keys and `$select`/`$sort`
|
|
448
|
+
* paths when using `AtscriptDbTable`. The `FlatOf<T>` utility type extracts
|
|
449
|
+
* this map at the type level.
|
|
450
|
+
*
|
|
451
|
+
* Special rendering rules:
|
|
452
|
+
* - **Intermediate paths** (structures, arrays of structures) → `never`
|
|
453
|
+
* (prevents `$eq` comparisons, but allows `$select` and `$exists`)
|
|
454
|
+
* - **`@db.json` fields** → `string` (stored as serialized JSON in DB,
|
|
455
|
+
* not individually queryable)
|
|
456
|
+
* - **Leaf fields** → their original TypeScript type
|
|
457
|
+
*/ renderFlat(node) {
|
|
458
|
+
const flatMap = (0, __atscript_core.flattenInterfaceNode)(this.doc, node);
|
|
459
|
+
if (flatMap.size === 0) return;
|
|
460
|
+
this.write("static __flat: ");
|
|
461
|
+
this.blockln("{}");
|
|
462
|
+
for (const [path$2, descriptor] of flatMap) {
|
|
463
|
+
this.write(`"${escapeQuotes(path$2)}"`);
|
|
464
|
+
if (descriptor.optional) this.write("?");
|
|
465
|
+
this.write(": ");
|
|
466
|
+
if (descriptor.intermediate) this.writeln("never");
|
|
467
|
+
else if (descriptor.dbJson) this.writeln("string");
|
|
468
|
+
else {
|
|
469
|
+
const originalDef = descriptor.propNode?.getDefinition();
|
|
470
|
+
const defToRender = originalDef && !((0, __atscript_core.isGroup)(descriptor.def) && descriptor.def !== originalDef) ? originalDef : descriptor.def;
|
|
471
|
+
const renderedDef = this.renderTypeDefString(defToRender);
|
|
472
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
this.pop();
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Renders the `static __pk` property — the primary key type for type-safe
|
|
479
|
+
* `deleteOne`/`findById` signatures on `AtscriptDbTable`.
|
|
480
|
+
*
|
|
481
|
+
* - **Single PK** (one `@meta.id`) → `static __pk: <scalar type>`
|
|
482
|
+
* - **Compound PK** (multiple `@meta.id`) → `static __pk: { field1: Type1; field2: Type2 }`
|
|
483
|
+
* - **No PK** → no `__pk` emitted
|
|
484
|
+
*/ renderPk(node) {
|
|
485
|
+
let struct;
|
|
486
|
+
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
487
|
+
if (!struct) struct = node.getDefinition();
|
|
488
|
+
if (!struct || !(0, __atscript_core.isStructure)(struct)) return;
|
|
489
|
+
const pkProps = [];
|
|
490
|
+
for (const [name, prop] of struct.props) {
|
|
491
|
+
if (prop.token("identifier")?.pattern) continue;
|
|
492
|
+
if (prop.countAnnotations("meta.id") > 0) pkProps.push({
|
|
493
|
+
name,
|
|
494
|
+
prop
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
if (pkProps.length === 0) return;
|
|
498
|
+
this.writeln();
|
|
499
|
+
if (pkProps.length === 1) {
|
|
500
|
+
this.write("static __pk: ");
|
|
501
|
+
const renderedDef = this.renderTypeDefString(pkProps[0].prop.getDefinition());
|
|
502
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
503
|
+
} else {
|
|
504
|
+
this.write("static __pk: ");
|
|
505
|
+
this.blockln("{}");
|
|
506
|
+
for (const { name, prop } of pkProps) {
|
|
507
|
+
this.write(wrapProp(name), ": ");
|
|
508
|
+
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
509
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
510
|
+
}
|
|
511
|
+
this.pop();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
454
514
|
phantomPropType(def) {
|
|
455
515
|
if (!def) return undefined;
|
|
456
516
|
if ((0, __atscript_core.isPrimitive)(def) && def.config.type === "phantom") return def.id;
|
|
@@ -499,24 +559,6 @@ function renderPrimitiveTypeDef(def) {
|
|
|
499
559
|
}
|
|
500
560
|
}
|
|
501
561
|
|
|
502
|
-
//#endregion
|
|
503
|
-
//#region packages/typescript/src/traverse.ts
|
|
504
|
-
function forAnnotatedType(def, handlers) {
|
|
505
|
-
switch (def.type.kind) {
|
|
506
|
-
case "": {
|
|
507
|
-
const typed = def;
|
|
508
|
-
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
509
|
-
return handlers.final(typed);
|
|
510
|
-
}
|
|
511
|
-
case "object": return handlers.object(def);
|
|
512
|
-
case "array": return handlers.array(def);
|
|
513
|
-
case "union": return handlers.union(def);
|
|
514
|
-
case "intersection": return handlers.intersection(def);
|
|
515
|
-
case "tuple": return handlers.tuple(def);
|
|
516
|
-
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
562
|
//#endregion
|
|
521
563
|
//#region packages/typescript/src/validator.ts
|
|
522
564
|
function _define_property$1(obj, key, value) {
|
|
@@ -532,28 +574,35 @@ else obj[key] = value;
|
|
|
532
574
|
const regexCache = new Map();
|
|
533
575
|
var Validator = class {
|
|
534
576
|
isLimitExceeded() {
|
|
535
|
-
if (this.stackErrors.length > 0)
|
|
577
|
+
if (this.stackErrors.length > 0) {
|
|
578
|
+
const top = this.stackErrors[this.stackErrors.length - 1];
|
|
579
|
+
return top !== null && top.length >= this.opts.errorLimit;
|
|
580
|
+
}
|
|
536
581
|
return this.errors.length >= this.opts.errorLimit;
|
|
537
582
|
}
|
|
538
583
|
push(name) {
|
|
539
584
|
this.stackPath.push(name);
|
|
540
|
-
this.stackErrors.push(
|
|
585
|
+
this.stackErrors.push(null);
|
|
586
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
541
587
|
}
|
|
542
588
|
pop(saveErrors) {
|
|
543
589
|
this.stackPath.pop();
|
|
544
590
|
const popped = this.stackErrors.pop();
|
|
545
|
-
if (saveErrors && popped
|
|
546
|
-
|
|
547
|
-
});
|
|
591
|
+
if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
|
|
592
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
548
593
|
return popped;
|
|
549
594
|
}
|
|
550
595
|
clear() {
|
|
551
|
-
this.stackErrors[this.stackErrors.length - 1] =
|
|
596
|
+
this.stackErrors[this.stackErrors.length - 1] = null;
|
|
552
597
|
}
|
|
553
598
|
error(message, path$2, details) {
|
|
554
|
-
|
|
599
|
+
let errors = this.stackErrors[this.stackErrors.length - 1];
|
|
600
|
+
if (!errors) if (this.stackErrors.length > 0) {
|
|
601
|
+
errors = [];
|
|
602
|
+
this.stackErrors[this.stackErrors.length - 1] = errors;
|
|
603
|
+
} else errors = this.errors;
|
|
555
604
|
const error = {
|
|
556
|
-
path: path$2 || this.
|
|
605
|
+
path: path$2 || this.cachedPath,
|
|
557
606
|
message
|
|
558
607
|
};
|
|
559
608
|
if (details?.length) error.details = details;
|
|
@@ -573,9 +622,10 @@ var Validator = class {
|
|
|
573
622
|
* @returns `true` if the value matches the type definition.
|
|
574
623
|
* @throws {ValidatorError} When validation fails and `safe` is not `true`.
|
|
575
624
|
*/ validate(value, safe, context) {
|
|
576
|
-
this.push("");
|
|
577
625
|
this.errors = [];
|
|
578
626
|
this.stackErrors = [];
|
|
627
|
+
this.stackPath = [""];
|
|
628
|
+
this.cachedPath = "";
|
|
579
629
|
this.context = context;
|
|
580
630
|
const passed = this.validateSafe(this.def, value);
|
|
581
631
|
this.pop(!passed);
|
|
@@ -589,7 +639,7 @@ var Validator = class {
|
|
|
589
639
|
validateSafe(def, value) {
|
|
590
640
|
if (this.isLimitExceeded()) return false;
|
|
591
641
|
if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
|
|
592
|
-
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.
|
|
642
|
+
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
|
|
593
643
|
if (def.optional && value === undefined) return true;
|
|
594
644
|
for (const plugin of this.opts.plugins) {
|
|
595
645
|
const result = plugin(this, def, value);
|
|
@@ -598,18 +648,21 @@ var Validator = class {
|
|
|
598
648
|
return this.validateAnnotatedType(def, value);
|
|
599
649
|
}
|
|
600
650
|
get path() {
|
|
601
|
-
return this.
|
|
651
|
+
return this.cachedPath;
|
|
602
652
|
}
|
|
603
653
|
validateAnnotatedType(def, value) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
654
|
+
switch (def.type.kind) {
|
|
655
|
+
case "": {
|
|
656
|
+
if (def.type.designType === "phantom") return true;
|
|
657
|
+
return this.validatePrimitive(def, value);
|
|
658
|
+
}
|
|
659
|
+
case "object": return this.validateObject(def, value);
|
|
660
|
+
case "array": return this.validateArray(def, value);
|
|
661
|
+
case "union": return this.validateUnion(def, value);
|
|
662
|
+
case "intersection": return this.validateIntersection(def, value);
|
|
663
|
+
case "tuple": return this.validateTuple(def, value);
|
|
664
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
665
|
+
}
|
|
613
666
|
}
|
|
614
667
|
validateUnion(def, value) {
|
|
615
668
|
let i = 0;
|
|
@@ -673,6 +726,30 @@ var Validator = class {
|
|
|
673
726
|
return false;
|
|
674
727
|
}
|
|
675
728
|
}
|
|
729
|
+
const uniqueItems = def.metadata.get("expect.array.uniqueItems");
|
|
730
|
+
if (uniqueItems) {
|
|
731
|
+
const separator = "▼↩";
|
|
732
|
+
const seen = new Set();
|
|
733
|
+
const keyProps = new Set();
|
|
734
|
+
if (def.type.of.type.kind === "object") {
|
|
735
|
+
for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
|
|
736
|
+
}
|
|
737
|
+
for (let idx = 0; idx < value.length; idx++) {
|
|
738
|
+
const item = value[idx];
|
|
739
|
+
let key;
|
|
740
|
+
if (keyProps.size > 0) {
|
|
741
|
+
key = "";
|
|
742
|
+
for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
|
|
743
|
+
} else key = JSON.stringify(item);
|
|
744
|
+
if (seen.has(key)) {
|
|
745
|
+
this.push(String(idx));
|
|
746
|
+
this.error(uniqueItems.message || "Duplicate items are not allowed");
|
|
747
|
+
this.pop(true);
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
seen.add(key);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
676
753
|
let i = 0;
|
|
677
754
|
let passed = true;
|
|
678
755
|
for (const item of value) {
|
|
@@ -694,21 +771,20 @@ var Validator = class {
|
|
|
694
771
|
let passed = true;
|
|
695
772
|
const valueKeys = new Set(Object.keys(value));
|
|
696
773
|
const typeKeys = new Set();
|
|
697
|
-
|
|
774
|
+
let skipList;
|
|
698
775
|
if (this.opts.skipList) {
|
|
699
|
-
const path$2 = this.stackPath.length > 1 ? `${this.
|
|
700
|
-
this.opts.skipList.
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
});
|
|
776
|
+
const path$2 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
|
|
777
|
+
for (const item of this.opts.skipList) if (item.startsWith(path$2)) {
|
|
778
|
+
const key = item.slice(path$2.length);
|
|
779
|
+
if (!skipList) skipList = new Set();
|
|
780
|
+
skipList.add(key);
|
|
781
|
+
valueKeys.delete(key);
|
|
782
|
+
}
|
|
707
783
|
}
|
|
708
784
|
let partialFunctionMatched = false;
|
|
709
|
-
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.
|
|
785
|
+
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
|
|
710
786
|
for (const [key, item] of def.type.props.entries()) {
|
|
711
|
-
if (skipList.has(key) || isPhantomType(item)) continue;
|
|
787
|
+
if (skipList && skipList.has(key) || isPhantomType(item)) continue;
|
|
712
788
|
typeKeys.add(key);
|
|
713
789
|
if (value[key] === undefined) {
|
|
714
790
|
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
@@ -729,19 +805,21 @@ else {
|
|
|
729
805
|
def: propDef
|
|
730
806
|
});
|
|
731
807
|
if (matched.length > 0) {
|
|
808
|
+
this.push(key);
|
|
732
809
|
let keyPassed = false;
|
|
733
|
-
for (const { def:
|
|
734
|
-
this.
|
|
735
|
-
|
|
736
|
-
|
|
810
|
+
for (const { def: propDef } of matched) {
|
|
811
|
+
if (this.validateSafe(propDef, value[key])) {
|
|
812
|
+
keyPassed = true;
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
this.clear();
|
|
737
816
|
}
|
|
738
817
|
if (!keyPassed) {
|
|
739
|
-
this.push(key);
|
|
740
818
|
this.validateSafe(matched[0].def, value[key]);
|
|
741
819
|
this.pop(true);
|
|
742
820
|
passed = false;
|
|
743
821
|
if (this.isLimitExceeded()) return false;
|
|
744
|
-
}
|
|
822
|
+
} else this.pop(false);
|
|
745
823
|
} else if (this.opts.unknownProps !== "ignore") {
|
|
746
824
|
if (this.opts.unknownProps === "error") {
|
|
747
825
|
this.push(key);
|
|
@@ -894,11 +972,13 @@ else {
|
|
|
894
972
|
/** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
|
|
895
973
|
_define_property$1(this, "stackErrors", void 0);
|
|
896
974
|
_define_property$1(this, "stackPath", void 0);
|
|
975
|
+
_define_property$1(this, "cachedPath", void 0);
|
|
897
976
|
_define_property$1(this, "context", void 0);
|
|
898
977
|
this.def = def;
|
|
899
978
|
this.errors = [];
|
|
900
979
|
this.stackErrors = [];
|
|
901
980
|
this.stackPath = [];
|
|
981
|
+
this.cachedPath = "";
|
|
902
982
|
this.opts = {
|
|
903
983
|
partial: false,
|
|
904
984
|
unknownProps: "error",
|
|
@@ -1042,6 +1122,24 @@ function isPhantomType(def) {
|
|
|
1042
1122
|
return def.type.kind === "" && def.type.designType === "phantom";
|
|
1043
1123
|
}
|
|
1044
1124
|
|
|
1125
|
+
//#endregion
|
|
1126
|
+
//#region packages/typescript/src/traverse.ts
|
|
1127
|
+
function forAnnotatedType(def, handlers) {
|
|
1128
|
+
switch (def.type.kind) {
|
|
1129
|
+
case "": {
|
|
1130
|
+
const typed = def;
|
|
1131
|
+
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
1132
|
+
return handlers.final(typed);
|
|
1133
|
+
}
|
|
1134
|
+
case "object": return handlers.object(def);
|
|
1135
|
+
case "array": return handlers.array(def);
|
|
1136
|
+
case "union": return handlers.union(def);
|
|
1137
|
+
case "intersection": return handlers.intersection(def);
|
|
1138
|
+
case "tuple": return handlers.tuple(def);
|
|
1139
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1045
1143
|
//#endregion
|
|
1046
1144
|
//#region packages/typescript/src/json-schema.ts
|
|
1047
1145
|
/**
|
|
@@ -1214,24 +1312,25 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
1214
1312
|
this.writeln("// prettier-ignore-start");
|
|
1215
1313
|
this.writeln("/* eslint-disable */");
|
|
1216
1314
|
this.writeln("/* oxlint-disable */");
|
|
1315
|
+
let hasMutatingAnnotate = false;
|
|
1316
|
+
const nodesByName = new Map();
|
|
1317
|
+
for (const node of this.doc.nodes) {
|
|
1318
|
+
if (node.entity === "annotate" && node.isMutating) hasMutatingAnnotate = true;
|
|
1319
|
+
if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
|
|
1320
|
+
const name = node.id;
|
|
1321
|
+
if (!nodesByName.has(name)) nodesByName.set(name, []);
|
|
1322
|
+
nodesByName.get(name).push(node);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
|
|
1326
|
+
else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
|
|
1217
1327
|
const imports = ["defineAnnotatedType as $", "annotate as $a"];
|
|
1218
|
-
const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
|
|
1219
1328
|
if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
|
|
1220
1329
|
const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
|
|
1221
1330
|
if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
|
|
1222
1331
|
if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
|
|
1223
1332
|
if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
|
|
1224
1333
|
this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
|
|
1225
|
-
const nameCounts = new Map();
|
|
1226
|
-
const nodesByName = new Map();
|
|
1227
|
-
for (const node of this.doc.nodes) if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
|
|
1228
|
-
const name = node.id;
|
|
1229
|
-
nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
|
|
1230
|
-
if (!nodesByName.has(name)) nodesByName.set(name, []);
|
|
1231
|
-
nodesByName.get(name).push(node);
|
|
1232
|
-
}
|
|
1233
|
-
for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
|
|
1234
|
-
else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
|
|
1235
1334
|
}
|
|
1236
1335
|
buildAdHocMap(annotateNodes) {
|
|
1237
1336
|
const map = new Map();
|
|
@@ -1240,7 +1339,8 @@ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}_
|
|
|
1240
1339
|
const anns = entry.annotations || [];
|
|
1241
1340
|
if (anns.length > 0) {
|
|
1242
1341
|
const existing = map.get(path$2);
|
|
1243
|
-
|
|
1342
|
+
if (existing) existing.push(...anns);
|
|
1343
|
+
else map.set(path$2, [...anns]);
|
|
1244
1344
|
}
|
|
1245
1345
|
}
|
|
1246
1346
|
return map.size > 0 ? map : null;
|
|
@@ -1299,35 +1399,20 @@ else def = def.getDefinition() || def;
|
|
|
1299
1399
|
this.renderExampleDataMethod(node);
|
|
1300
1400
|
}
|
|
1301
1401
|
renderInterface(node) {
|
|
1302
|
-
this.
|
|
1303
|
-
const exported = node.token("export")?.text === "export";
|
|
1304
|
-
this.write(exported ? "export " : "");
|
|
1305
|
-
this.write(`class ${node.id} `);
|
|
1306
|
-
this.blockln("{}");
|
|
1307
|
-
this.renderClassStatics(node);
|
|
1308
|
-
this.popln();
|
|
1309
|
-
this.postAnnotate.push(node);
|
|
1310
|
-
this.writeln();
|
|
1402
|
+
this.renderDefinitionClass(node);
|
|
1311
1403
|
}
|
|
1312
1404
|
renderType(node) {
|
|
1313
|
-
this.
|
|
1314
|
-
const exported = node.token("export")?.text === "export";
|
|
1315
|
-
this.write(exported ? "export " : "");
|
|
1316
|
-
this.write(`class ${node.id} `);
|
|
1317
|
-
this.blockln("{}");
|
|
1318
|
-
this.renderClassStatics(node);
|
|
1319
|
-
this.popln();
|
|
1320
|
-
this.postAnnotate.push(node);
|
|
1321
|
-
this.writeln();
|
|
1405
|
+
this.renderDefinitionClass(node);
|
|
1322
1406
|
}
|
|
1323
1407
|
renderAnnotate(node) {
|
|
1324
1408
|
if (node.isMutating) {
|
|
1325
1409
|
this.postAnnotate.push(node);
|
|
1326
1410
|
return;
|
|
1327
1411
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1412
|
+
if (!this.doc.unwindType(node.targetName)?.def) return;
|
|
1413
|
+
this.renderDefinitionClass(node);
|
|
1414
|
+
}
|
|
1415
|
+
renderDefinitionClass(node) {
|
|
1331
1416
|
this.writeln();
|
|
1332
1417
|
const exported = node.token("export")?.text === "export";
|
|
1333
1418
|
this.write(exported ? "export " : "");
|
|
@@ -1485,6 +1570,11 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1485
1570
|
handle.annotate(a.name, true);
|
|
1486
1571
|
break;
|
|
1487
1572
|
}
|
|
1573
|
+
case "expect.array.uniqueItems":
|
|
1574
|
+
case "expect.array.key": {
|
|
1575
|
+
handle.annotate(a.name, { message: a.args[0]?.text });
|
|
1576
|
+
break;
|
|
1577
|
+
}
|
|
1488
1578
|
default:
|
|
1489
1579
|
}
|
|
1490
1580
|
});
|
|
@@ -1552,10 +1642,7 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1552
1642
|
this.writeln(`$("array"${name ? `, ${name}` : ""})`).indent().defineArray(node).unindent();
|
|
1553
1643
|
return this;
|
|
1554
1644
|
}
|
|
1555
|
-
default:
|
|
1556
|
-
console.log("!!!!!!! UNKNOWN", node.entity);
|
|
1557
|
-
return this;
|
|
1558
|
-
}
|
|
1645
|
+
default: return this;
|
|
1559
1646
|
}
|
|
1560
1647
|
}
|
|
1561
1648
|
defineConst(node) {
|
|
@@ -1767,40 +1854,25 @@ else targetValue = "true";
|
|
|
1767
1854
|
this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
|
|
1768
1855
|
}
|
|
1769
1856
|
}
|
|
1770
|
-
for (const { entry, accessors } of entryAccessors)
|
|
1771
|
-
const anns = entry.annotations;
|
|
1772
|
-
for (const accessor of accessors) {
|
|
1773
|
-
const cleared = new Set();
|
|
1774
|
-
for (const an of anns) {
|
|
1775
|
-
const { value, multiple } = this.computeAnnotationValue(entry, an);
|
|
1776
|
-
if (multiple) {
|
|
1777
|
-
if (!cleared.has(an.name)) {
|
|
1778
|
-
const spec = this.doc.resolveAnnotation(an.name);
|
|
1779
|
-
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1780
|
-
cleared.add(an.name);
|
|
1781
|
-
}
|
|
1782
|
-
this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1783
|
-
} else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1857
|
+
for (const { entry, accessors } of entryAccessors) for (const accessor of accessors) this.emitMutatingAnnotations(entry, entry.annotations, accessor);
|
|
1787
1858
|
const topAnnotations = node.annotations;
|
|
1788
|
-
if (topAnnotations && topAnnotations.length > 0)
|
|
1789
|
-
const cleared = new Set();
|
|
1790
|
-
for (const an of topAnnotations) {
|
|
1791
|
-
const { value, multiple } = this.computeAnnotationValue(node, an);
|
|
1792
|
-
if (multiple) {
|
|
1793
|
-
if (!cleared.has(an.name)) {
|
|
1794
|
-
const spec = this.doc.resolveAnnotation(an.name);
|
|
1795
|
-
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${targetName}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1796
|
-
cleared.add(an.name);
|
|
1797
|
-
}
|
|
1798
|
-
this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1799
|
-
} else this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1859
|
+
if (topAnnotations && topAnnotations.length > 0) this.emitMutatingAnnotations(node, topAnnotations, targetName);
|
|
1802
1860
|
this.writeln();
|
|
1803
1861
|
}
|
|
1862
|
+
emitMutatingAnnotations(node, annotations, accessor) {
|
|
1863
|
+
const cleared = new Set();
|
|
1864
|
+
for (const an of annotations) {
|
|
1865
|
+
const { value, multiple } = this.computeAnnotationValue(node, an);
|
|
1866
|
+
if (multiple) {
|
|
1867
|
+
if (!cleared.has(an.name)) {
|
|
1868
|
+
const spec = this.doc.resolveAnnotation(an.name);
|
|
1869
|
+
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1870
|
+
cleared.add(an.name);
|
|
1871
|
+
}
|
|
1872
|
+
this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1873
|
+
} else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1804
1876
|
resolveTargetDef(targetName) {
|
|
1805
1877
|
const unwound = this.doc.unwindType(targetName);
|
|
1806
1878
|
if (!unwound?.def) return undefined;
|