@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.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) {
|
|
@@ -116,6 +116,9 @@ else obj[key] = value;
|
|
|
116
116
|
return obj;
|
|
117
117
|
}
|
|
118
118
|
var BaseRenderer = class extends CodePrinter {
|
|
119
|
+
get unused() {
|
|
120
|
+
return this._unused ?? (this._unused = new Set(this.doc.getUnusedTokens().map((t) => t.text)));
|
|
121
|
+
}
|
|
119
122
|
pre() {}
|
|
120
123
|
post() {}
|
|
121
124
|
render() {
|
|
@@ -169,15 +172,14 @@ var BaseRenderer = class extends CodePrinter {
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
constructor(doc) {
|
|
172
|
-
super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "
|
|
173
|
-
this.unused = new Set(this.doc.getUnusedTokens().map((t) => t.text));
|
|
175
|
+
super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "_unused", void 0), this.doc = doc;
|
|
174
176
|
}
|
|
175
177
|
};
|
|
176
178
|
|
|
177
179
|
//#endregion
|
|
178
180
|
//#region packages/typescript/src/codegen/utils.ts
|
|
181
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
179
182
|
function wrapProp(name) {
|
|
180
|
-
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
181
183
|
if (!validIdentifier.test(name)) return `"${escapeQuotes(name)}"`;
|
|
182
184
|
return name;
|
|
183
185
|
}
|
|
@@ -262,14 +264,16 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
262
264
|
if (isGrp) this.write(")");
|
|
263
265
|
return this.write("[]");
|
|
264
266
|
}
|
|
267
|
+
if (isPrimitive(def)) return this.write(renderPrimitiveTypeDef(def.config.type));
|
|
265
268
|
}
|
|
266
|
-
renderStructure(struct, asClass) {
|
|
269
|
+
renderStructure(struct, asClass, interfaceNode, filterProps) {
|
|
267
270
|
this.blockln("{}");
|
|
268
271
|
const patterns = [];
|
|
269
272
|
const propsDefs = new Set();
|
|
270
|
-
for (const prop of
|
|
273
|
+
for (const prop of struct.props.values()) {
|
|
274
|
+
if (filterProps?.has(prop.id)) continue;
|
|
271
275
|
if (prop.token("identifier")?.pattern) {
|
|
272
|
-
patterns.push(prop);
|
|
276
|
+
if (!filterProps) patterns.push(prop);
|
|
273
277
|
continue;
|
|
274
278
|
}
|
|
275
279
|
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
@@ -285,33 +289,36 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
285
289
|
}
|
|
286
290
|
if (patterns.length > 0) {
|
|
287
291
|
this.write(`[key: string]: `);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
for (const def of defs) {
|
|
294
|
-
this.writeln();
|
|
295
|
-
this.write("| ");
|
|
296
|
-
def.split("\n").forEach((l) => this.write(l.trim()));
|
|
297
|
-
}
|
|
298
|
-
this.unindent();
|
|
292
|
+
for (const prop of patterns) propsDefs.add(this.renderTypeDefString(prop.getDefinition()));
|
|
293
|
+
const defs = Array.from(propsDefs);
|
|
294
|
+
if (defs.length > 1) {
|
|
295
|
+
this.indent();
|
|
296
|
+
for (const def of defs) {
|
|
299
297
|
this.writeln();
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
this.writeln(
|
|
306
|
-
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
307
|
-
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
308
|
-
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. */");
|
|
309
|
-
this.writeln("static toJsonSchema: () => any");
|
|
310
|
-
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
311
|
-
this.writeln("static toExampleData?: () => any");
|
|
298
|
+
this.write("| ");
|
|
299
|
+
def.split("\n").forEach((l) => this.write(l.trim()));
|
|
300
|
+
}
|
|
301
|
+
this.unindent();
|
|
302
|
+
this.writeln();
|
|
303
|
+
} else defs[0].split("\n").forEach((l) => this.writeln(l));
|
|
312
304
|
}
|
|
305
|
+
if (asClass) this.renderStaticDeclarations(asClass, interfaceNode);
|
|
313
306
|
this.pop();
|
|
314
307
|
}
|
|
308
|
+
renderStaticDeclarations(asClass, interfaceNode) {
|
|
309
|
+
this.writeln("static __is_atscript_annotated_type: true");
|
|
310
|
+
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
311
|
+
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
312
|
+
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
313
|
+
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. */");
|
|
314
|
+
this.writeln("static toJsonSchema: () => any");
|
|
315
|
+
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
316
|
+
this.writeln("static toExampleData?: () => any");
|
|
317
|
+
if (interfaceNode && this.hasDbTable(interfaceNode)) {
|
|
318
|
+
this.renderFlat(interfaceNode);
|
|
319
|
+
this.renderPk(interfaceNode);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
315
322
|
renderInterface(node) {
|
|
316
323
|
this.writeln();
|
|
317
324
|
const exported = node.token("export")?.text === "export";
|
|
@@ -335,44 +342,16 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
335
342
|
}
|
|
336
343
|
if (!firstParentProps && isStructure(fpDef)) firstParentProps = fpDef.props;
|
|
337
344
|
}
|
|
338
|
-
this.
|
|
345
|
+
this.renderStructure(resolved, node.id, node, firstParentProps);
|
|
339
346
|
} else this.writeln("{}");
|
|
340
347
|
} else {
|
|
341
348
|
this.write(`class ${node.id} `);
|
|
342
349
|
const struct = node.getDefinition();
|
|
343
|
-
if (struct?.entity === "structure") this.renderStructure(struct, node.id);
|
|
350
|
+
if (struct?.entity === "structure") this.renderStructure(struct, node.id, node);
|
|
344
351
|
else this.writeln("{}");
|
|
345
352
|
}
|
|
346
353
|
this.writeln();
|
|
347
354
|
}
|
|
348
|
-
/**
|
|
349
|
-
* Renders a structure block, optionally filtering out props that exist in a parent.
|
|
350
|
-
*/ renderStructureFiltered(struct, asClass, filterProps) {
|
|
351
|
-
if (!filterProps) return this.renderStructure(struct, asClass);
|
|
352
|
-
this.blockln("{}");
|
|
353
|
-
for (const prop of Array.from(struct.props.values())) {
|
|
354
|
-
if (filterProps.has(prop.id)) continue;
|
|
355
|
-
if (prop.token("identifier")?.pattern) continue;
|
|
356
|
-
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
357
|
-
if (phantomType) {
|
|
358
|
-
this.writeln(`// ${prop.id}: ${phantomType}`);
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
const optional = !!prop.token("optional");
|
|
362
|
-
this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
|
|
363
|
-
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
364
|
-
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
365
|
-
}
|
|
366
|
-
this.writeln("static __is_atscript_annotated_type: true");
|
|
367
|
-
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
368
|
-
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
369
|
-
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
370
|
-
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. */");
|
|
371
|
-
this.writeln("static toJsonSchema: () => any");
|
|
372
|
-
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
373
|
-
this.writeln("static toExampleData?: () => any");
|
|
374
|
-
this.pop();
|
|
375
|
-
}
|
|
376
355
|
renderType(node) {
|
|
377
356
|
this.writeln();
|
|
378
357
|
const exported = node.token("export")?.text === "export";
|
|
@@ -426,6 +405,87 @@ else if (isPrimitive(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
|
|
|
426
405
|
this.writeln("const toExampleData: (() => any) | undefined");
|
|
427
406
|
this.popln();
|
|
428
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Checks whether an interface has the `@db.table` annotation.
|
|
410
|
+
*
|
|
411
|
+
* NOTE: Only `@db.table` interfaces get the `__flat` static property.
|
|
412
|
+
* This is intentionally hardcoded — `__flat` exists solely to improve
|
|
413
|
+
* type-safety for filter expressions and `$select`/`$sort` operations
|
|
414
|
+
* in the DB layer. Non-DB interfaces have no use for dot-notation path types.
|
|
415
|
+
*/ hasDbTable(node) {
|
|
416
|
+
return !!node.annotations?.some((a) => a.name === "db.table");
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Renders the `static __flat` property — a map of all dot-notation paths
|
|
420
|
+
* to their TypeScript value types.
|
|
421
|
+
*
|
|
422
|
+
* This enables type-safe autocomplete for filter keys and `$select`/`$sort`
|
|
423
|
+
* paths when using `AtscriptDbTable`. The `FlatOf<T>` utility type extracts
|
|
424
|
+
* this map at the type level.
|
|
425
|
+
*
|
|
426
|
+
* Special rendering rules:
|
|
427
|
+
* - **Intermediate paths** (structures, arrays of structures) → `never`
|
|
428
|
+
* (prevents `$eq` comparisons, but allows `$select` and `$exists`)
|
|
429
|
+
* - **`@db.json` fields** → `string` (stored as serialized JSON in DB,
|
|
430
|
+
* not individually queryable)
|
|
431
|
+
* - **Leaf fields** → their original TypeScript type
|
|
432
|
+
*/ renderFlat(node) {
|
|
433
|
+
const flatMap = flattenInterfaceNode(this.doc, node);
|
|
434
|
+
if (flatMap.size === 0) return;
|
|
435
|
+
this.write("static __flat: ");
|
|
436
|
+
this.blockln("{}");
|
|
437
|
+
for (const [path$1, descriptor] of flatMap) {
|
|
438
|
+
this.write(`"${escapeQuotes(path$1)}"`);
|
|
439
|
+
if (descriptor.optional) this.write("?");
|
|
440
|
+
this.write(": ");
|
|
441
|
+
if (descriptor.intermediate) this.writeln("never");
|
|
442
|
+
else if (descriptor.dbJson) this.writeln("string");
|
|
443
|
+
else {
|
|
444
|
+
const originalDef = descriptor.propNode?.getDefinition();
|
|
445
|
+
const defToRender = originalDef && !(isGroup(descriptor.def) && descriptor.def !== originalDef) ? originalDef : descriptor.def;
|
|
446
|
+
const renderedDef = this.renderTypeDefString(defToRender);
|
|
447
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
this.pop();
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Renders the `static __pk` property — the primary key type for type-safe
|
|
454
|
+
* `deleteOne`/`findById` signatures on `AtscriptDbTable`.
|
|
455
|
+
*
|
|
456
|
+
* - **Single PK** (one `@meta.id`) → `static __pk: <scalar type>`
|
|
457
|
+
* - **Compound PK** (multiple `@meta.id`) → `static __pk: { field1: Type1; field2: Type2 }`
|
|
458
|
+
* - **No PK** → no `__pk` emitted
|
|
459
|
+
*/ renderPk(node) {
|
|
460
|
+
let struct;
|
|
461
|
+
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
462
|
+
if (!struct) struct = node.getDefinition();
|
|
463
|
+
if (!struct || !isStructure(struct)) return;
|
|
464
|
+
const pkProps = [];
|
|
465
|
+
for (const [name, prop] of struct.props) {
|
|
466
|
+
if (prop.token("identifier")?.pattern) continue;
|
|
467
|
+
if (prop.countAnnotations("meta.id") > 0) pkProps.push({
|
|
468
|
+
name,
|
|
469
|
+
prop
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
if (pkProps.length === 0) return;
|
|
473
|
+
this.writeln();
|
|
474
|
+
if (pkProps.length === 1) {
|
|
475
|
+
this.write("static __pk: ");
|
|
476
|
+
const renderedDef = this.renderTypeDefString(pkProps[0].prop.getDefinition());
|
|
477
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
478
|
+
} else {
|
|
479
|
+
this.write("static __pk: ");
|
|
480
|
+
this.blockln("{}");
|
|
481
|
+
for (const { name, prop } of pkProps) {
|
|
482
|
+
this.write(wrapProp(name), ": ");
|
|
483
|
+
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
484
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
485
|
+
}
|
|
486
|
+
this.pop();
|
|
487
|
+
}
|
|
488
|
+
}
|
|
429
489
|
phantomPropType(def) {
|
|
430
490
|
if (!def) return undefined;
|
|
431
491
|
if (isPrimitive(def) && def.config.type === "phantom") return def.id;
|
|
@@ -474,24 +534,6 @@ function renderPrimitiveTypeDef(def) {
|
|
|
474
534
|
}
|
|
475
535
|
}
|
|
476
536
|
|
|
477
|
-
//#endregion
|
|
478
|
-
//#region packages/typescript/src/traverse.ts
|
|
479
|
-
function forAnnotatedType(def, handlers) {
|
|
480
|
-
switch (def.type.kind) {
|
|
481
|
-
case "": {
|
|
482
|
-
const typed = def;
|
|
483
|
-
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
484
|
-
return handlers.final(typed);
|
|
485
|
-
}
|
|
486
|
-
case "object": return handlers.object(def);
|
|
487
|
-
case "array": return handlers.array(def);
|
|
488
|
-
case "union": return handlers.union(def);
|
|
489
|
-
case "intersection": return handlers.intersection(def);
|
|
490
|
-
case "tuple": return handlers.tuple(def);
|
|
491
|
-
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
537
|
//#endregion
|
|
496
538
|
//#region packages/typescript/src/validator.ts
|
|
497
539
|
function _define_property$1(obj, key, value) {
|
|
@@ -507,28 +549,35 @@ else obj[key] = value;
|
|
|
507
549
|
const regexCache = new Map();
|
|
508
550
|
var Validator = class {
|
|
509
551
|
isLimitExceeded() {
|
|
510
|
-
if (this.stackErrors.length > 0)
|
|
552
|
+
if (this.stackErrors.length > 0) {
|
|
553
|
+
const top = this.stackErrors[this.stackErrors.length - 1];
|
|
554
|
+
return top !== null && top.length >= this.opts.errorLimit;
|
|
555
|
+
}
|
|
511
556
|
return this.errors.length >= this.opts.errorLimit;
|
|
512
557
|
}
|
|
513
558
|
push(name) {
|
|
514
559
|
this.stackPath.push(name);
|
|
515
|
-
this.stackErrors.push(
|
|
560
|
+
this.stackErrors.push(null);
|
|
561
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
516
562
|
}
|
|
517
563
|
pop(saveErrors) {
|
|
518
564
|
this.stackPath.pop();
|
|
519
565
|
const popped = this.stackErrors.pop();
|
|
520
|
-
if (saveErrors && popped
|
|
521
|
-
|
|
522
|
-
});
|
|
566
|
+
if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
|
|
567
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
523
568
|
return popped;
|
|
524
569
|
}
|
|
525
570
|
clear() {
|
|
526
|
-
this.stackErrors[this.stackErrors.length - 1] =
|
|
571
|
+
this.stackErrors[this.stackErrors.length - 1] = null;
|
|
527
572
|
}
|
|
528
573
|
error(message, path$1, details) {
|
|
529
|
-
|
|
574
|
+
let errors = this.stackErrors[this.stackErrors.length - 1];
|
|
575
|
+
if (!errors) if (this.stackErrors.length > 0) {
|
|
576
|
+
errors = [];
|
|
577
|
+
this.stackErrors[this.stackErrors.length - 1] = errors;
|
|
578
|
+
} else errors = this.errors;
|
|
530
579
|
const error = {
|
|
531
|
-
path: path$1 || this.
|
|
580
|
+
path: path$1 || this.cachedPath,
|
|
532
581
|
message
|
|
533
582
|
};
|
|
534
583
|
if (details?.length) error.details = details;
|
|
@@ -548,9 +597,10 @@ var Validator = class {
|
|
|
548
597
|
* @returns `true` if the value matches the type definition.
|
|
549
598
|
* @throws {ValidatorError} When validation fails and `safe` is not `true`.
|
|
550
599
|
*/ validate(value, safe, context) {
|
|
551
|
-
this.push("");
|
|
552
600
|
this.errors = [];
|
|
553
601
|
this.stackErrors = [];
|
|
602
|
+
this.stackPath = [""];
|
|
603
|
+
this.cachedPath = "";
|
|
554
604
|
this.context = context;
|
|
555
605
|
const passed = this.validateSafe(this.def, value);
|
|
556
606
|
this.pop(!passed);
|
|
@@ -564,7 +614,7 @@ var Validator = class {
|
|
|
564
614
|
validateSafe(def, value) {
|
|
565
615
|
if (this.isLimitExceeded()) return false;
|
|
566
616
|
if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
|
|
567
|
-
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.
|
|
617
|
+
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
|
|
568
618
|
if (def.optional && value === undefined) return true;
|
|
569
619
|
for (const plugin of this.opts.plugins) {
|
|
570
620
|
const result = plugin(this, def, value);
|
|
@@ -573,18 +623,21 @@ var Validator = class {
|
|
|
573
623
|
return this.validateAnnotatedType(def, value);
|
|
574
624
|
}
|
|
575
625
|
get path() {
|
|
576
|
-
return this.
|
|
626
|
+
return this.cachedPath;
|
|
577
627
|
}
|
|
578
628
|
validateAnnotatedType(def, value) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
629
|
+
switch (def.type.kind) {
|
|
630
|
+
case "": {
|
|
631
|
+
if (def.type.designType === "phantom") return true;
|
|
632
|
+
return this.validatePrimitive(def, value);
|
|
633
|
+
}
|
|
634
|
+
case "object": return this.validateObject(def, value);
|
|
635
|
+
case "array": return this.validateArray(def, value);
|
|
636
|
+
case "union": return this.validateUnion(def, value);
|
|
637
|
+
case "intersection": return this.validateIntersection(def, value);
|
|
638
|
+
case "tuple": return this.validateTuple(def, value);
|
|
639
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
640
|
+
}
|
|
588
641
|
}
|
|
589
642
|
validateUnion(def, value) {
|
|
590
643
|
let i = 0;
|
|
@@ -648,6 +701,30 @@ var Validator = class {
|
|
|
648
701
|
return false;
|
|
649
702
|
}
|
|
650
703
|
}
|
|
704
|
+
const uniqueItems = def.metadata.get("expect.array.uniqueItems");
|
|
705
|
+
if (uniqueItems) {
|
|
706
|
+
const separator = "▼↩";
|
|
707
|
+
const seen = new Set();
|
|
708
|
+
const keyProps = new Set();
|
|
709
|
+
if (def.type.of.type.kind === "object") {
|
|
710
|
+
for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
|
|
711
|
+
}
|
|
712
|
+
for (let idx = 0; idx < value.length; idx++) {
|
|
713
|
+
const item = value[idx];
|
|
714
|
+
let key;
|
|
715
|
+
if (keyProps.size > 0) {
|
|
716
|
+
key = "";
|
|
717
|
+
for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
|
|
718
|
+
} else key = JSON.stringify(item);
|
|
719
|
+
if (seen.has(key)) {
|
|
720
|
+
this.push(String(idx));
|
|
721
|
+
this.error(uniqueItems.message || "Duplicate items are not allowed");
|
|
722
|
+
this.pop(true);
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
seen.add(key);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
651
728
|
let i = 0;
|
|
652
729
|
let passed = true;
|
|
653
730
|
for (const item of value) {
|
|
@@ -669,21 +746,20 @@ var Validator = class {
|
|
|
669
746
|
let passed = true;
|
|
670
747
|
const valueKeys = new Set(Object.keys(value));
|
|
671
748
|
const typeKeys = new Set();
|
|
672
|
-
|
|
749
|
+
let skipList;
|
|
673
750
|
if (this.opts.skipList) {
|
|
674
|
-
const path$1 = this.stackPath.length > 1 ? `${this.
|
|
675
|
-
this.opts.skipList.
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
});
|
|
751
|
+
const path$1 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
|
|
752
|
+
for (const item of this.opts.skipList) if (item.startsWith(path$1)) {
|
|
753
|
+
const key = item.slice(path$1.length);
|
|
754
|
+
if (!skipList) skipList = new Set();
|
|
755
|
+
skipList.add(key);
|
|
756
|
+
valueKeys.delete(key);
|
|
757
|
+
}
|
|
682
758
|
}
|
|
683
759
|
let partialFunctionMatched = false;
|
|
684
|
-
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.
|
|
760
|
+
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
|
|
685
761
|
for (const [key, item] of def.type.props.entries()) {
|
|
686
|
-
if (skipList.has(key) || isPhantomType(item)) continue;
|
|
762
|
+
if (skipList && skipList.has(key) || isPhantomType(item)) continue;
|
|
687
763
|
typeKeys.add(key);
|
|
688
764
|
if (value[key] === undefined) {
|
|
689
765
|
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
@@ -704,19 +780,21 @@ else {
|
|
|
704
780
|
def: propDef
|
|
705
781
|
});
|
|
706
782
|
if (matched.length > 0) {
|
|
783
|
+
this.push(key);
|
|
707
784
|
let keyPassed = false;
|
|
708
|
-
for (const { def:
|
|
709
|
-
this.
|
|
710
|
-
|
|
711
|
-
|
|
785
|
+
for (const { def: propDef } of matched) {
|
|
786
|
+
if (this.validateSafe(propDef, value[key])) {
|
|
787
|
+
keyPassed = true;
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
this.clear();
|
|
712
791
|
}
|
|
713
792
|
if (!keyPassed) {
|
|
714
|
-
this.push(key);
|
|
715
793
|
this.validateSafe(matched[0].def, value[key]);
|
|
716
794
|
this.pop(true);
|
|
717
795
|
passed = false;
|
|
718
796
|
if (this.isLimitExceeded()) return false;
|
|
719
|
-
}
|
|
797
|
+
} else this.pop(false);
|
|
720
798
|
} else if (this.opts.unknownProps !== "ignore") {
|
|
721
799
|
if (this.opts.unknownProps === "error") {
|
|
722
800
|
this.push(key);
|
|
@@ -869,11 +947,13 @@ else {
|
|
|
869
947
|
/** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
|
|
870
948
|
_define_property$1(this, "stackErrors", void 0);
|
|
871
949
|
_define_property$1(this, "stackPath", void 0);
|
|
950
|
+
_define_property$1(this, "cachedPath", void 0);
|
|
872
951
|
_define_property$1(this, "context", void 0);
|
|
873
952
|
this.def = def;
|
|
874
953
|
this.errors = [];
|
|
875
954
|
this.stackErrors = [];
|
|
876
955
|
this.stackPath = [];
|
|
956
|
+
this.cachedPath = "";
|
|
877
957
|
this.opts = {
|
|
878
958
|
partial: false,
|
|
879
959
|
unknownProps: "error",
|
|
@@ -1017,6 +1097,24 @@ function isPhantomType(def) {
|
|
|
1017
1097
|
return def.type.kind === "" && def.type.designType === "phantom";
|
|
1018
1098
|
}
|
|
1019
1099
|
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region packages/typescript/src/traverse.ts
|
|
1102
|
+
function forAnnotatedType(def, handlers) {
|
|
1103
|
+
switch (def.type.kind) {
|
|
1104
|
+
case "": {
|
|
1105
|
+
const typed = def;
|
|
1106
|
+
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
1107
|
+
return handlers.final(typed);
|
|
1108
|
+
}
|
|
1109
|
+
case "object": return handlers.object(def);
|
|
1110
|
+
case "array": return handlers.array(def);
|
|
1111
|
+
case "union": return handlers.union(def);
|
|
1112
|
+
case "intersection": return handlers.intersection(def);
|
|
1113
|
+
case "tuple": return handlers.tuple(def);
|
|
1114
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1020
1118
|
//#endregion
|
|
1021
1119
|
//#region packages/typescript/src/json-schema.ts
|
|
1022
1120
|
/**
|
|
@@ -1189,24 +1287,25 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
1189
1287
|
this.writeln("// prettier-ignore-start");
|
|
1190
1288
|
this.writeln("/* eslint-disable */");
|
|
1191
1289
|
this.writeln("/* oxlint-disable */");
|
|
1290
|
+
let hasMutatingAnnotate = false;
|
|
1291
|
+
const nodesByName = new Map();
|
|
1292
|
+
for (const node of this.doc.nodes) {
|
|
1293
|
+
if (node.entity === "annotate" && node.isMutating) hasMutatingAnnotate = true;
|
|
1294
|
+
if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
|
|
1295
|
+
const name = node.id;
|
|
1296
|
+
if (!nodesByName.has(name)) nodesByName.set(name, []);
|
|
1297
|
+
nodesByName.get(name).push(node);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
|
|
1301
|
+
else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
|
|
1192
1302
|
const imports = ["defineAnnotatedType as $", "annotate as $a"];
|
|
1193
|
-
const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
|
|
1194
1303
|
if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
|
|
1195
1304
|
const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
|
|
1196
1305
|
if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
|
|
1197
1306
|
if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
|
|
1198
1307
|
if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
|
|
1199
1308
|
this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
|
|
1200
|
-
const nameCounts = new Map();
|
|
1201
|
-
const nodesByName = new Map();
|
|
1202
|
-
for (const node of this.doc.nodes) if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
|
|
1203
|
-
const name = node.id;
|
|
1204
|
-
nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
|
|
1205
|
-
if (!nodesByName.has(name)) nodesByName.set(name, []);
|
|
1206
|
-
nodesByName.get(name).push(node);
|
|
1207
|
-
}
|
|
1208
|
-
for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
|
|
1209
|
-
else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
|
|
1210
1309
|
}
|
|
1211
1310
|
buildAdHocMap(annotateNodes) {
|
|
1212
1311
|
const map = new Map();
|
|
@@ -1215,7 +1314,8 @@ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}_
|
|
|
1215
1314
|
const anns = entry.annotations || [];
|
|
1216
1315
|
if (anns.length > 0) {
|
|
1217
1316
|
const existing = map.get(path$1);
|
|
1218
|
-
|
|
1317
|
+
if (existing) existing.push(...anns);
|
|
1318
|
+
else map.set(path$1, [...anns]);
|
|
1219
1319
|
}
|
|
1220
1320
|
}
|
|
1221
1321
|
return map.size > 0 ? map : null;
|
|
@@ -1274,35 +1374,20 @@ else def = def.getDefinition() || def;
|
|
|
1274
1374
|
this.renderExampleDataMethod(node);
|
|
1275
1375
|
}
|
|
1276
1376
|
renderInterface(node) {
|
|
1277
|
-
this.
|
|
1278
|
-
const exported = node.token("export")?.text === "export";
|
|
1279
|
-
this.write(exported ? "export " : "");
|
|
1280
|
-
this.write(`class ${node.id} `);
|
|
1281
|
-
this.blockln("{}");
|
|
1282
|
-
this.renderClassStatics(node);
|
|
1283
|
-
this.popln();
|
|
1284
|
-
this.postAnnotate.push(node);
|
|
1285
|
-
this.writeln();
|
|
1377
|
+
this.renderDefinitionClass(node);
|
|
1286
1378
|
}
|
|
1287
1379
|
renderType(node) {
|
|
1288
|
-
this.
|
|
1289
|
-
const exported = node.token("export")?.text === "export";
|
|
1290
|
-
this.write(exported ? "export " : "");
|
|
1291
|
-
this.write(`class ${node.id} `);
|
|
1292
|
-
this.blockln("{}");
|
|
1293
|
-
this.renderClassStatics(node);
|
|
1294
|
-
this.popln();
|
|
1295
|
-
this.postAnnotate.push(node);
|
|
1296
|
-
this.writeln();
|
|
1380
|
+
this.renderDefinitionClass(node);
|
|
1297
1381
|
}
|
|
1298
1382
|
renderAnnotate(node) {
|
|
1299
1383
|
if (node.isMutating) {
|
|
1300
1384
|
this.postAnnotate.push(node);
|
|
1301
1385
|
return;
|
|
1302
1386
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1387
|
+
if (!this.doc.unwindType(node.targetName)?.def) return;
|
|
1388
|
+
this.renderDefinitionClass(node);
|
|
1389
|
+
}
|
|
1390
|
+
renderDefinitionClass(node) {
|
|
1306
1391
|
this.writeln();
|
|
1307
1392
|
const exported = node.token("export")?.text === "export";
|
|
1308
1393
|
this.write(exported ? "export " : "");
|
|
@@ -1460,6 +1545,11 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1460
1545
|
handle.annotate(a.name, true);
|
|
1461
1546
|
break;
|
|
1462
1547
|
}
|
|
1548
|
+
case "expect.array.uniqueItems":
|
|
1549
|
+
case "expect.array.key": {
|
|
1550
|
+
handle.annotate(a.name, { message: a.args[0]?.text });
|
|
1551
|
+
break;
|
|
1552
|
+
}
|
|
1463
1553
|
default:
|
|
1464
1554
|
}
|
|
1465
1555
|
});
|
|
@@ -1527,10 +1617,7 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1527
1617
|
this.writeln(`$("array"${name ? `, ${name}` : ""})`).indent().defineArray(node).unindent();
|
|
1528
1618
|
return this;
|
|
1529
1619
|
}
|
|
1530
|
-
default:
|
|
1531
|
-
console.log("!!!!!!! UNKNOWN", node.entity);
|
|
1532
|
-
return this;
|
|
1533
|
-
}
|
|
1620
|
+
default: return this;
|
|
1534
1621
|
}
|
|
1535
1622
|
}
|
|
1536
1623
|
defineConst(node) {
|
|
@@ -1742,40 +1829,25 @@ else targetValue = "true";
|
|
|
1742
1829
|
this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
|
|
1743
1830
|
}
|
|
1744
1831
|
}
|
|
1745
|
-
for (const { entry, accessors } of entryAccessors)
|
|
1746
|
-
const anns = entry.annotations;
|
|
1747
|
-
for (const accessor of accessors) {
|
|
1748
|
-
const cleared = new Set();
|
|
1749
|
-
for (const an of anns) {
|
|
1750
|
-
const { value, multiple } = this.computeAnnotationValue(entry, an);
|
|
1751
|
-
if (multiple) {
|
|
1752
|
-
if (!cleared.has(an.name)) {
|
|
1753
|
-
const spec = this.doc.resolveAnnotation(an.name);
|
|
1754
|
-
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1755
|
-
cleared.add(an.name);
|
|
1756
|
-
}
|
|
1757
|
-
this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1758
|
-
} else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1832
|
+
for (const { entry, accessors } of entryAccessors) for (const accessor of accessors) this.emitMutatingAnnotations(entry, entry.annotations, accessor);
|
|
1762
1833
|
const topAnnotations = node.annotations;
|
|
1763
|
-
if (topAnnotations && topAnnotations.length > 0)
|
|
1764
|
-
const cleared = new Set();
|
|
1765
|
-
for (const an of topAnnotations) {
|
|
1766
|
-
const { value, multiple } = this.computeAnnotationValue(node, an);
|
|
1767
|
-
if (multiple) {
|
|
1768
|
-
if (!cleared.has(an.name)) {
|
|
1769
|
-
const spec = this.doc.resolveAnnotation(an.name);
|
|
1770
|
-
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${targetName}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1771
|
-
cleared.add(an.name);
|
|
1772
|
-
}
|
|
1773
|
-
this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1774
|
-
} else this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1834
|
+
if (topAnnotations && topAnnotations.length > 0) this.emitMutatingAnnotations(node, topAnnotations, targetName);
|
|
1777
1835
|
this.writeln();
|
|
1778
1836
|
}
|
|
1837
|
+
emitMutatingAnnotations(node, annotations, accessor) {
|
|
1838
|
+
const cleared = new Set();
|
|
1839
|
+
for (const an of annotations) {
|
|
1840
|
+
const { value, multiple } = this.computeAnnotationValue(node, an);
|
|
1841
|
+
if (multiple) {
|
|
1842
|
+
if (!cleared.has(an.name)) {
|
|
1843
|
+
const spec = this.doc.resolveAnnotation(an.name);
|
|
1844
|
+
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1845
|
+
cleared.add(an.name);
|
|
1846
|
+
}
|
|
1847
|
+
this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1848
|
+
} else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1779
1851
|
resolveTargetDef(targetName) {
|
|
1780
1852
|
const unwound = this.doc.unwindType(targetName);
|
|
1781
1853
|
if (!unwound?.def) return undefined;
|