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