@atscript/typescript 0.1.34 → 0.1.36
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/LICENSE +1 -1
- package/dist/cli.cjs +552 -113
- package/dist/index.cjs +165 -814
- package/dist/index.mjs +157 -806
- package/dist/json-schema-0UUPoHud.mjs +952 -0
- package/dist/json-schema-S5-XAOrR.cjs +1030 -0
- package/dist/utils.cjs +46 -976
- package/dist/utils.d.ts +47 -3
- package/dist/utils.mjs +24 -954
- package/package.json +11 -6
package/dist/index.cjs
CHANGED
|
@@ -23,11 +23,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
}) : target, mod));
|
|
24
24
|
|
|
25
25
|
//#endregion
|
|
26
|
+
const require_json_schema = require('./json-schema-S5-XAOrR.cjs');
|
|
26
27
|
const path = __toESM(require("path"));
|
|
27
28
|
const __atscript_core = __toESM(require("@atscript/core"));
|
|
28
29
|
|
|
29
30
|
//#region packages/typescript/src/codegen/code-printer.ts
|
|
30
|
-
function _define_property$
|
|
31
|
+
function _define_property$3(obj, key, value) {
|
|
31
32
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
32
33
|
value,
|
|
33
34
|
enumerable: true,
|
|
@@ -120,17 +121,17 @@ else this.write(closing);
|
|
|
120
121
|
return " ".repeat(this.indentLevel * this.indentSize);
|
|
121
122
|
}
|
|
122
123
|
constructor() {
|
|
123
|
-
_define_property$
|
|
124
|
-
_define_property$
|
|
125
|
-
_define_property$
|
|
126
|
-
_define_property$
|
|
127
|
-
_define_property$
|
|
124
|
+
_define_property$3(this, "lines", []);
|
|
125
|
+
_define_property$3(this, "currentLine", "");
|
|
126
|
+
_define_property$3(this, "indentLevel", 0);
|
|
127
|
+
_define_property$3(this, "indentSize", 2);
|
|
128
|
+
_define_property$3(this, "blockStack", []);
|
|
128
129
|
}
|
|
129
130
|
};
|
|
130
131
|
|
|
131
132
|
//#endregion
|
|
132
133
|
//#region packages/typescript/src/codegen/base-renderer.ts
|
|
133
|
-
function _define_property$
|
|
134
|
+
function _define_property$2(obj, key, value) {
|
|
134
135
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
135
136
|
value,
|
|
136
137
|
enumerable: true,
|
|
@@ -197,7 +198,7 @@ var BaseRenderer = class extends CodePrinter {
|
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
constructor(doc) {
|
|
200
|
-
super(), _define_property$
|
|
201
|
+
super(), _define_property$2(this, "doc", void 0), _define_property$2(this, "_unused", void 0), this.doc = doc;
|
|
201
202
|
}
|
|
202
203
|
};
|
|
203
204
|
|
|
@@ -214,7 +215,7 @@ function escapeQuotes(str) {
|
|
|
214
215
|
|
|
215
216
|
//#endregion
|
|
216
217
|
//#region packages/typescript/src/codegen/type-renderer.ts
|
|
217
|
-
function _define_property$
|
|
218
|
+
function _define_property$1(obj, key, value) {
|
|
218
219
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
219
220
|
value,
|
|
220
221
|
enumerable: true,
|
|
@@ -339,8 +340,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
339
340
|
this.writeln("static toJsonSchema: () => any");
|
|
340
341
|
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
341
342
|
this.writeln("static toExampleData?: () => any");
|
|
342
|
-
if (interfaceNode && this.
|
|
343
|
+
if (interfaceNode && this.hasDbEntity(interfaceNode)) {
|
|
343
344
|
this.renderFlat(interfaceNode);
|
|
345
|
+
this.renderOwnProps(interfaceNode);
|
|
346
|
+
this.renderNavProps(interfaceNode);
|
|
344
347
|
this.renderPk(interfaceNode);
|
|
345
348
|
}
|
|
346
349
|
}
|
|
@@ -431,14 +434,13 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFina
|
|
|
431
434
|
this.popln();
|
|
432
435
|
}
|
|
433
436
|
/**
|
|
434
|
-
* Checks whether an interface
|
|
437
|
+
* Checks whether an interface is a DB entity (`@db.table` or `@db.view`).
|
|
435
438
|
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return !!node.annotations?.some((a) => a.name === "db.table");
|
|
439
|
+
* Only DB entities get `__flat`, `__pk`, `__ownProps`, and `__navProps` static properties.
|
|
440
|
+
* These exist solely to improve type-safety for filter expressions, `$select`/`$sort`,
|
|
441
|
+
* and `$with` operations in the DB layer.
|
|
442
|
+
*/ hasDbEntity(node) {
|
|
443
|
+
return !!node.annotations?.some((a) => a.name === "db.table" || a.name === "db.view" || a.name === "db.view.for");
|
|
442
444
|
}
|
|
443
445
|
/**
|
|
444
446
|
* Renders the `static __flat` property — a map of all dot-notation paths
|
|
@@ -455,9 +457,27 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFina
|
|
|
455
457
|
* not individually queryable)
|
|
456
458
|
* - **Leaf fields** → their original TypeScript type
|
|
457
459
|
*/ renderFlat(node) {
|
|
458
|
-
|
|
460
|
+
this.renderFlatMap("__flat", (0, __atscript_core.flattenInterfaceNode)(this.doc, node));
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Renders the `static __ownProps` property — table-owned fields only (no nav props).
|
|
464
|
+
*/ renderOwnProps(node) {
|
|
465
|
+
this.renderFlatMap("__ownProps", (0, __atscript_core.flattenInterfaceNode)(this.doc, node, { skipNavProps: true }), {
|
|
466
|
+
leadingNewline: true,
|
|
467
|
+
trailingNewline: true
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Shared renderer for flat-map static properties (`__flat`, `__ownProps`).
|
|
472
|
+
*
|
|
473
|
+
* Special rendering rules:
|
|
474
|
+
* - **Intermediate paths** (structures, arrays of structures) → `never`
|
|
475
|
+
* - **`@db.json` fields** → `string` (stored as serialized JSON in DB)
|
|
476
|
+
* - **Leaf fields** → their original TypeScript type
|
|
477
|
+
*/ renderFlatMap(propName, flatMap, opts) {
|
|
459
478
|
if (flatMap.size === 0) return;
|
|
460
|
-
this.
|
|
479
|
+
if (opts?.leadingNewline) this.writeln();
|
|
480
|
+
this.write(`static ${propName}: `);
|
|
461
481
|
this.blockln("{}");
|
|
462
482
|
for (const [path$2, descriptor] of flatMap) {
|
|
463
483
|
this.write(`"${escapeQuotes(path$2)}"`);
|
|
@@ -472,19 +492,46 @@ else {
|
|
|
472
492
|
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
473
493
|
}
|
|
474
494
|
}
|
|
475
|
-
this.
|
|
495
|
+
if (opts?.trailingNewline) this.popln();
|
|
496
|
+
else this.pop();
|
|
476
497
|
}
|
|
477
498
|
/**
|
|
478
|
-
* Renders the `static
|
|
479
|
-
*
|
|
499
|
+
* Renders the `static __navProps` property — a map of navigation property names
|
|
500
|
+
* to their declared TypeScript types.
|
|
480
501
|
*
|
|
481
|
-
* -
|
|
482
|
-
*
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
502
|
+
* This enables type-safe `$with` in queries: `name` is constrained to known
|
|
503
|
+
* navigation property keys, and nested filter/controls are typed to the target entity.
|
|
504
|
+
*/ renderNavProps(node) {
|
|
505
|
+
let struct;
|
|
506
|
+
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
507
|
+
if (!struct) struct = node.getDefinition();
|
|
508
|
+
if (!struct || !(0, __atscript_core.isStructure)(struct)) return;
|
|
509
|
+
const structNode = struct;
|
|
510
|
+
const navProps = [];
|
|
511
|
+
for (const [name, prop] of structNode.props) {
|
|
512
|
+
if (prop.token("identifier")?.pattern) continue;
|
|
513
|
+
if ((0, __atscript_core.hasNavPropAnnotation)(prop)) navProps.push({
|
|
514
|
+
name,
|
|
515
|
+
prop
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
if (navProps.length === 0) return;
|
|
519
|
+
this.writeln();
|
|
520
|
+
this.write("static __navProps: ");
|
|
521
|
+
this.blockln("{}");
|
|
522
|
+
for (const { name, prop } of navProps) {
|
|
523
|
+
this.write(`"${escapeQuotes(name)}"`);
|
|
524
|
+
if (prop.token("optional")) this.write("?");
|
|
525
|
+
this.write(": ");
|
|
526
|
+
const propDef = prop.getDefinition();
|
|
527
|
+
if (propDef) {
|
|
528
|
+
const renderedDef = this.renderTypeDefString(propDef);
|
|
529
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
530
|
+
} else this.writeln("unknown");
|
|
531
|
+
}
|
|
532
|
+
this.popln();
|
|
533
|
+
}
|
|
534
|
+
renderPk(node) {
|
|
488
535
|
const isMongoCollection = !!node.annotations?.some((a) => a.name === "db.mongo.collection");
|
|
489
536
|
let struct;
|
|
490
537
|
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
@@ -584,7 +631,7 @@ else if (pkProps.length === 1) {
|
|
|
584
631
|
this.writeln(` */`);
|
|
585
632
|
}
|
|
586
633
|
constructor(doc, opts) {
|
|
587
|
-
super(doc), _define_property$
|
|
634
|
+
super(doc), _define_property$1(this, "opts", void 0), this.opts = opts;
|
|
588
635
|
}
|
|
589
636
|
};
|
|
590
637
|
function renderPrimitiveTypeDef(def) {
|
|
@@ -604,777 +651,6 @@ function renderPrimitiveTypeDef(def) {
|
|
|
604
651
|
}
|
|
605
652
|
}
|
|
606
653
|
|
|
607
|
-
//#endregion
|
|
608
|
-
//#region packages/typescript/src/validator.ts
|
|
609
|
-
function _define_property$1(obj, key, value) {
|
|
610
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
611
|
-
value,
|
|
612
|
-
enumerable: true,
|
|
613
|
-
configurable: true,
|
|
614
|
-
writable: true
|
|
615
|
-
});
|
|
616
|
-
else obj[key] = value;
|
|
617
|
-
return obj;
|
|
618
|
-
}
|
|
619
|
-
const regexCache = new Map();
|
|
620
|
-
var Validator = class {
|
|
621
|
-
isLimitExceeded() {
|
|
622
|
-
if (this.stackErrors.length > 0) {
|
|
623
|
-
const top = this.stackErrors[this.stackErrors.length - 1];
|
|
624
|
-
return top !== null && top.length >= this.opts.errorLimit;
|
|
625
|
-
}
|
|
626
|
-
return this.errors.length >= this.opts.errorLimit;
|
|
627
|
-
}
|
|
628
|
-
push(name) {
|
|
629
|
-
this.stackPath.push(name);
|
|
630
|
-
this.stackErrors.push(null);
|
|
631
|
-
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
632
|
-
}
|
|
633
|
-
pop(saveErrors) {
|
|
634
|
-
this.stackPath.pop();
|
|
635
|
-
const popped = this.stackErrors.pop();
|
|
636
|
-
if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
|
|
637
|
-
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
638
|
-
return popped;
|
|
639
|
-
}
|
|
640
|
-
clear() {
|
|
641
|
-
this.stackErrors[this.stackErrors.length - 1] = null;
|
|
642
|
-
}
|
|
643
|
-
error(message, path$2, details) {
|
|
644
|
-
let errors = this.stackErrors[this.stackErrors.length - 1];
|
|
645
|
-
if (!errors) if (this.stackErrors.length > 0) {
|
|
646
|
-
errors = [];
|
|
647
|
-
this.stackErrors[this.stackErrors.length - 1] = errors;
|
|
648
|
-
} else errors = this.errors;
|
|
649
|
-
const error = {
|
|
650
|
-
path: path$2 || this.cachedPath,
|
|
651
|
-
message
|
|
652
|
-
};
|
|
653
|
-
if (details?.length) error.details = details;
|
|
654
|
-
errors.push(error);
|
|
655
|
-
}
|
|
656
|
-
throw() {
|
|
657
|
-
throw new ValidatorError(this.errors);
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Validates a value against the type definition.
|
|
661
|
-
*
|
|
662
|
-
* Acts as a TypeScript type guard — when it returns `true`, the value
|
|
663
|
-
* is narrowed to `DataType`.
|
|
664
|
-
*
|
|
665
|
-
* @param value - The value to validate.
|
|
666
|
-
* @param safe - If `true`, returns `false` on failure instead of throwing.
|
|
667
|
-
* @returns `true` if the value matches the type definition.
|
|
668
|
-
* @throws {ValidatorError} When validation fails and `safe` is not `true`.
|
|
669
|
-
*/ validate(value, safe, context) {
|
|
670
|
-
this.errors = [];
|
|
671
|
-
this.stackErrors = [];
|
|
672
|
-
this.stackPath = [""];
|
|
673
|
-
this.cachedPath = "";
|
|
674
|
-
this.context = context;
|
|
675
|
-
const passed = this.validateSafe(this.def, value);
|
|
676
|
-
this.pop(!passed);
|
|
677
|
-
this.context = undefined;
|
|
678
|
-
if (!passed) {
|
|
679
|
-
if (safe) return false;
|
|
680
|
-
this.throw();
|
|
681
|
-
}
|
|
682
|
-
return true;
|
|
683
|
-
}
|
|
684
|
-
validateSafe(def, value) {
|
|
685
|
-
if (this.isLimitExceeded()) return false;
|
|
686
|
-
if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
|
|
687
|
-
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
|
|
688
|
-
if (def.optional && value === undefined) return true;
|
|
689
|
-
for (const plugin of this.opts.plugins) {
|
|
690
|
-
const result = plugin(this, def, value);
|
|
691
|
-
if (result === false || result === true) return result;
|
|
692
|
-
}
|
|
693
|
-
return this.validateAnnotatedType(def, value);
|
|
694
|
-
}
|
|
695
|
-
get path() {
|
|
696
|
-
return this.cachedPath;
|
|
697
|
-
}
|
|
698
|
-
validateAnnotatedType(def, value) {
|
|
699
|
-
switch (def.type.kind) {
|
|
700
|
-
case "": {
|
|
701
|
-
if (def.type.designType === "phantom") return true;
|
|
702
|
-
return this.validatePrimitive(def, value);
|
|
703
|
-
}
|
|
704
|
-
case "object": return this.validateObject(def, value);
|
|
705
|
-
case "array": return this.validateArray(def, value);
|
|
706
|
-
case "union": return this.validateUnion(def, value);
|
|
707
|
-
case "intersection": return this.validateIntersection(def, value);
|
|
708
|
-
case "tuple": return this.validateTuple(def, value);
|
|
709
|
-
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
validateUnion(def, value) {
|
|
713
|
-
let i = 0;
|
|
714
|
-
const popped = [];
|
|
715
|
-
for (const item of def.type.items) {
|
|
716
|
-
this.push(`[${item.type.kind || item.type.designType}(${i})]`);
|
|
717
|
-
if (this.validateSafe(item, value)) {
|
|
718
|
-
this.pop(false);
|
|
719
|
-
return true;
|
|
720
|
-
}
|
|
721
|
-
const errors = this.pop(false);
|
|
722
|
-
if (errors) popped.push(...errors);
|
|
723
|
-
i++;
|
|
724
|
-
}
|
|
725
|
-
this.clear();
|
|
726
|
-
const expected = def.type.items.map((item, i$1) => `[${item.type.kind || item.type.designType}(${i$1})]`).join(", ");
|
|
727
|
-
this.error(`Value does not match any of the allowed types: ${expected}`, undefined, popped);
|
|
728
|
-
return false;
|
|
729
|
-
}
|
|
730
|
-
validateIntersection(def, value) {
|
|
731
|
-
for (const item of def.type.items) if (!this.validateSafe(item, value)) return false;
|
|
732
|
-
return true;
|
|
733
|
-
}
|
|
734
|
-
validateTuple(def, value) {
|
|
735
|
-
if (!Array.isArray(value) || value.length !== def.type.items.length) {
|
|
736
|
-
this.error(`Expected array of length ${def.type.items.length}`);
|
|
737
|
-
return false;
|
|
738
|
-
}
|
|
739
|
-
let i = 0;
|
|
740
|
-
for (const item of def.type.items) {
|
|
741
|
-
this.push(String(i));
|
|
742
|
-
if (!this.validateSafe(item, value[i])) {
|
|
743
|
-
this.pop(true);
|
|
744
|
-
return false;
|
|
745
|
-
}
|
|
746
|
-
this.pop(false);
|
|
747
|
-
i++;
|
|
748
|
-
}
|
|
749
|
-
return true;
|
|
750
|
-
}
|
|
751
|
-
validateArray(def, value) {
|
|
752
|
-
if (!Array.isArray(value)) {
|
|
753
|
-
this.error("Expected array");
|
|
754
|
-
return false;
|
|
755
|
-
}
|
|
756
|
-
const minLength = def.metadata.get("expect.minLength");
|
|
757
|
-
if (minLength) {
|
|
758
|
-
const length = typeof minLength === "number" ? minLength : minLength.length;
|
|
759
|
-
if (value.length < length) {
|
|
760
|
-
const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} items, got ${value.length} items`;
|
|
761
|
-
this.error(message);
|
|
762
|
-
return false;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
const maxLength = def.metadata.get("expect.maxLength");
|
|
766
|
-
if (maxLength) {
|
|
767
|
-
const length = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
768
|
-
if (value.length > length) {
|
|
769
|
-
const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} items, got ${value.length} items`;
|
|
770
|
-
this.error(message);
|
|
771
|
-
return false;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
const uniqueItems = def.metadata.get("expect.array.uniqueItems");
|
|
775
|
-
if (uniqueItems) {
|
|
776
|
-
const separator = "▼↩";
|
|
777
|
-
const seen = new Set();
|
|
778
|
-
const keyProps = new Set();
|
|
779
|
-
if (def.type.of.type.kind === "object") {
|
|
780
|
-
for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
|
|
781
|
-
}
|
|
782
|
-
for (let idx = 0; idx < value.length; idx++) {
|
|
783
|
-
const item = value[idx];
|
|
784
|
-
let key;
|
|
785
|
-
if (keyProps.size > 0) {
|
|
786
|
-
key = "";
|
|
787
|
-
for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
|
|
788
|
-
} else key = JSON.stringify(item);
|
|
789
|
-
if (seen.has(key)) {
|
|
790
|
-
this.push(String(idx));
|
|
791
|
-
this.error(uniqueItems.message || "Duplicate items are not allowed");
|
|
792
|
-
this.pop(true);
|
|
793
|
-
return false;
|
|
794
|
-
}
|
|
795
|
-
seen.add(key);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
let i = 0;
|
|
799
|
-
let passed = true;
|
|
800
|
-
for (const item of value) {
|
|
801
|
-
this.push(String(i));
|
|
802
|
-
if (!this.validateSafe(def.type.of, item)) {
|
|
803
|
-
passed = false;
|
|
804
|
-
this.pop(true);
|
|
805
|
-
if (this.isLimitExceeded()) return false;
|
|
806
|
-
} else this.pop(false);
|
|
807
|
-
i++;
|
|
808
|
-
}
|
|
809
|
-
return passed;
|
|
810
|
-
}
|
|
811
|
-
validateObject(def, value) {
|
|
812
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
813
|
-
this.error("Expected object");
|
|
814
|
-
return false;
|
|
815
|
-
}
|
|
816
|
-
let passed = true;
|
|
817
|
-
const valueKeys = new Set(Object.keys(value));
|
|
818
|
-
const typeKeys = new Set();
|
|
819
|
-
let skipList;
|
|
820
|
-
if (this.opts.skipList) {
|
|
821
|
-
const path$2 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
|
|
822
|
-
for (const item of this.opts.skipList) if (item.startsWith(path$2)) {
|
|
823
|
-
const key = item.slice(path$2.length);
|
|
824
|
-
if (!skipList) skipList = new Set();
|
|
825
|
-
skipList.add(key);
|
|
826
|
-
valueKeys.delete(key);
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
let partialFunctionMatched = false;
|
|
830
|
-
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
|
|
831
|
-
for (const [key, item] of def.type.props.entries()) {
|
|
832
|
-
if (skipList && skipList.has(key) || isPhantomType(item)) continue;
|
|
833
|
-
typeKeys.add(key);
|
|
834
|
-
if (value[key] === undefined) {
|
|
835
|
-
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
836
|
-
}
|
|
837
|
-
this.push(key);
|
|
838
|
-
if (this.validateSafe(item, value[key])) this.pop(false);
|
|
839
|
-
else {
|
|
840
|
-
passed = false;
|
|
841
|
-
this.pop(true);
|
|
842
|
-
if (this.isLimitExceeded()) return false;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
for (const key of valueKeys)
|
|
846
|
-
/** matched patterns for unknown keys */ if (!typeKeys.has(key)) {
|
|
847
|
-
const matched = [];
|
|
848
|
-
for (const { pattern, def: propDef } of def.type.propsPatterns) if (pattern.test(key)) matched.push({
|
|
849
|
-
pattern,
|
|
850
|
-
def: propDef
|
|
851
|
-
});
|
|
852
|
-
if (matched.length > 0) {
|
|
853
|
-
this.push(key);
|
|
854
|
-
let keyPassed = false;
|
|
855
|
-
for (const { def: propDef } of matched) {
|
|
856
|
-
if (this.validateSafe(propDef, value[key])) {
|
|
857
|
-
keyPassed = true;
|
|
858
|
-
break;
|
|
859
|
-
}
|
|
860
|
-
this.clear();
|
|
861
|
-
}
|
|
862
|
-
if (!keyPassed) {
|
|
863
|
-
this.validateSafe(matched[0].def, value[key]);
|
|
864
|
-
this.pop(true);
|
|
865
|
-
passed = false;
|
|
866
|
-
if (this.isLimitExceeded()) return false;
|
|
867
|
-
} else this.pop(false);
|
|
868
|
-
} else if (this.opts.unknownProps !== "ignore") {
|
|
869
|
-
if (this.opts.unknownProps === "error") {
|
|
870
|
-
this.push(key);
|
|
871
|
-
this.error(`Unexpected property`);
|
|
872
|
-
this.pop(true);
|
|
873
|
-
if (this.isLimitExceeded()) return false;
|
|
874
|
-
passed = false;
|
|
875
|
-
} else if (this.opts.unknownProps === "strip") delete value[key];
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
return passed;
|
|
879
|
-
}
|
|
880
|
-
validatePrimitive(def, value) {
|
|
881
|
-
if (def.type.value !== undefined) {
|
|
882
|
-
if (value !== def.type.value) {
|
|
883
|
-
this.error(`Expected ${def.type.value}, got ${value}`);
|
|
884
|
-
return false;
|
|
885
|
-
}
|
|
886
|
-
return true;
|
|
887
|
-
}
|
|
888
|
-
const typeOfValue = Array.isArray(value) ? "array" : typeof value;
|
|
889
|
-
switch (def.type.designType) {
|
|
890
|
-
case "never": {
|
|
891
|
-
this.error(`This type is impossible, must be an internal problem`);
|
|
892
|
-
return false;
|
|
893
|
-
}
|
|
894
|
-
case "any": return true;
|
|
895
|
-
case "string": {
|
|
896
|
-
if (typeOfValue !== def.type.designType) {
|
|
897
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
898
|
-
return false;
|
|
899
|
-
}
|
|
900
|
-
return this.validateString(def, value);
|
|
901
|
-
}
|
|
902
|
-
case "number": {
|
|
903
|
-
if (typeOfValue !== def.type.designType) {
|
|
904
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
905
|
-
return false;
|
|
906
|
-
}
|
|
907
|
-
return this.validateNumber(def, value);
|
|
908
|
-
}
|
|
909
|
-
case "boolean": {
|
|
910
|
-
if (typeOfValue !== def.type.designType) {
|
|
911
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
912
|
-
return false;
|
|
913
|
-
}
|
|
914
|
-
return this.validateBoolean(def, value);
|
|
915
|
-
}
|
|
916
|
-
case "undefined": {
|
|
917
|
-
if (value !== undefined) {
|
|
918
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
919
|
-
return false;
|
|
920
|
-
}
|
|
921
|
-
return true;
|
|
922
|
-
}
|
|
923
|
-
case "null": {
|
|
924
|
-
if (value !== null) {
|
|
925
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
926
|
-
return false;
|
|
927
|
-
}
|
|
928
|
-
return true;
|
|
929
|
-
}
|
|
930
|
-
default: throw new Error(`Unknown type "${def.type.designType}"`);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
validateString(def, value) {
|
|
934
|
-
const filled = def.metadata.get("meta.required");
|
|
935
|
-
if (filled) {
|
|
936
|
-
if (value.trim().length === 0) {
|
|
937
|
-
const message = typeof filled === "object" && filled.message ? filled.message : `Must not be empty`;
|
|
938
|
-
this.error(message);
|
|
939
|
-
return false;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
const minLength = def.metadata.get("expect.minLength");
|
|
943
|
-
if (minLength) {
|
|
944
|
-
const length = typeof minLength === "number" ? minLength : minLength.length;
|
|
945
|
-
if (value.length < length) {
|
|
946
|
-
const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} characters, got ${value.length} characters`;
|
|
947
|
-
this.error(message);
|
|
948
|
-
return false;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
const maxLength = def.metadata.get("expect.maxLength");
|
|
952
|
-
if (maxLength) {
|
|
953
|
-
const length = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
954
|
-
if (value.length > length) {
|
|
955
|
-
const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} characters, got ${value.length} characters`;
|
|
956
|
-
this.error(message);
|
|
957
|
-
return false;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
const patterns = def.metadata.get("expect.pattern");
|
|
961
|
-
for (const { pattern, flags, message } of patterns || []) {
|
|
962
|
-
if (!pattern) continue;
|
|
963
|
-
const cacheKey = `${pattern}//${flags || ""}`;
|
|
964
|
-
let regex = regexCache.get(cacheKey);
|
|
965
|
-
if (!regex) {
|
|
966
|
-
regex = new RegExp(pattern, flags);
|
|
967
|
-
regexCache.set(cacheKey, regex);
|
|
968
|
-
}
|
|
969
|
-
if (!regex.test(value)) {
|
|
970
|
-
this.error(message || `Value is expected to match pattern "${pattern}"`);
|
|
971
|
-
return false;
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
return true;
|
|
975
|
-
}
|
|
976
|
-
validateNumber(def, value) {
|
|
977
|
-
const int = def.metadata.get("expect.int");
|
|
978
|
-
if (int && value % 1 !== 0) {
|
|
979
|
-
const message = typeof int === "object" && int.message ? int.message : `Expected integer, got ${value}`;
|
|
980
|
-
this.error(message);
|
|
981
|
-
return false;
|
|
982
|
-
}
|
|
983
|
-
const min = def.metadata.get("expect.min");
|
|
984
|
-
if (min) {
|
|
985
|
-
const minValue = typeof min === "number" ? min : min.minValue;
|
|
986
|
-
if (value < minValue) {
|
|
987
|
-
const message = typeof min === "object" && min.message ? min.message : `Expected minimum ${minValue}, got ${value}`;
|
|
988
|
-
this.error(message);
|
|
989
|
-
return false;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
const max = def.metadata.get("expect.max");
|
|
993
|
-
if (max) {
|
|
994
|
-
const maxValue = typeof max === "number" ? max : max.maxValue;
|
|
995
|
-
if (value > maxValue) {
|
|
996
|
-
const message = typeof max === "object" && max.message ? max.message : `Expected maximum ${maxValue}, got ${value}`;
|
|
997
|
-
this.error(message);
|
|
998
|
-
return false;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
return true;
|
|
1002
|
-
}
|
|
1003
|
-
validateBoolean(def, value) {
|
|
1004
|
-
const filled = def.metadata.get("meta.required");
|
|
1005
|
-
if (filled) {
|
|
1006
|
-
if (value !== true) {
|
|
1007
|
-
const message = typeof filled === "object" && filled.message ? filled.message : `Must be checked`;
|
|
1008
|
-
this.error(message);
|
|
1009
|
-
return false;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
return true;
|
|
1013
|
-
}
|
|
1014
|
-
constructor(def, opts) {
|
|
1015
|
-
_define_property$1(this, "def", void 0);
|
|
1016
|
-
_define_property$1(this, "opts", void 0);
|
|
1017
|
-
/** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
|
|
1018
|
-
_define_property$1(this, "stackErrors", void 0);
|
|
1019
|
-
_define_property$1(this, "stackPath", void 0);
|
|
1020
|
-
_define_property$1(this, "cachedPath", void 0);
|
|
1021
|
-
_define_property$1(this, "context", void 0);
|
|
1022
|
-
this.def = def;
|
|
1023
|
-
this.errors = [];
|
|
1024
|
-
this.stackErrors = [];
|
|
1025
|
-
this.stackPath = [];
|
|
1026
|
-
this.cachedPath = "";
|
|
1027
|
-
this.opts = {
|
|
1028
|
-
partial: false,
|
|
1029
|
-
unknownProps: "error",
|
|
1030
|
-
errorLimit: 10,
|
|
1031
|
-
...opts,
|
|
1032
|
-
plugins: opts?.plugins || []
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
};
|
|
1036
|
-
var ValidatorError = class extends Error {
|
|
1037
|
-
constructor(errors) {
|
|
1038
|
-
super(`${errors[0].path ? errors[0].path + ": " : ""}${errors[0].message}`), _define_property$1(this, "errors", void 0), _define_property$1(this, "name", void 0), this.errors = errors, this.name = "Validation Error";
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
|
-
//#endregion
|
|
1043
|
-
//#region packages/typescript/src/annotated-type.ts
|
|
1044
|
-
const COMPLEX_KINDS = new Set([
|
|
1045
|
-
"union",
|
|
1046
|
-
"intersection",
|
|
1047
|
-
"tuple"
|
|
1048
|
-
]);
|
|
1049
|
-
/** Shared validator method reused by all annotated type nodes. */ function validatorMethod(opts) {
|
|
1050
|
-
return new Validator(this, opts);
|
|
1051
|
-
}
|
|
1052
|
-
function createAnnotatedTypeNode(type, metadata, opts) {
|
|
1053
|
-
return {
|
|
1054
|
-
__is_atscript_annotated_type: true,
|
|
1055
|
-
type,
|
|
1056
|
-
metadata,
|
|
1057
|
-
validator: validatorMethod,
|
|
1058
|
-
id: opts?.id,
|
|
1059
|
-
optional: opts?.optional,
|
|
1060
|
-
ref: opts?.ref
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
function isAnnotatedType(type) {
|
|
1064
|
-
return type && type.__is_atscript_annotated_type;
|
|
1065
|
-
}
|
|
1066
|
-
function annotate(metadata, key, value, asArray) {
|
|
1067
|
-
if (!metadata) return;
|
|
1068
|
-
if (asArray) if (metadata.has(key)) {
|
|
1069
|
-
const a = metadata.get(key);
|
|
1070
|
-
if (Array.isArray(a)) a.push(value);
|
|
1071
|
-
else metadata.set(key, [a, value]);
|
|
1072
|
-
} else metadata.set(key, [value]);
|
|
1073
|
-
else metadata.set(key, value);
|
|
1074
|
-
}
|
|
1075
|
-
function defineAnnotatedType(_kind, base) {
|
|
1076
|
-
const kind = _kind || "";
|
|
1077
|
-
const type = base?.type || {};
|
|
1078
|
-
type.kind = kind;
|
|
1079
|
-
if (COMPLEX_KINDS.has(kind)) type.items = [];
|
|
1080
|
-
if (kind === "object") {
|
|
1081
|
-
type.props = new Map();
|
|
1082
|
-
type.propsPatterns = [];
|
|
1083
|
-
}
|
|
1084
|
-
type.tags = new Set();
|
|
1085
|
-
const metadata = base?.metadata || new Map();
|
|
1086
|
-
const payload = {
|
|
1087
|
-
__is_atscript_annotated_type: true,
|
|
1088
|
-
metadata,
|
|
1089
|
-
type,
|
|
1090
|
-
validator: validatorMethod
|
|
1091
|
-
};
|
|
1092
|
-
base = base ? Object.assign(base, payload) : payload;
|
|
1093
|
-
const handle = {
|
|
1094
|
-
$type: base,
|
|
1095
|
-
$def: type,
|
|
1096
|
-
$metadata: metadata,
|
|
1097
|
-
tags(...tags) {
|
|
1098
|
-
for (const tag of tags) this.$def.tags.add(tag);
|
|
1099
|
-
return this;
|
|
1100
|
-
},
|
|
1101
|
-
designType(value) {
|
|
1102
|
-
this.$def.designType = value;
|
|
1103
|
-
return this;
|
|
1104
|
-
},
|
|
1105
|
-
value(value) {
|
|
1106
|
-
this.$def.value = value;
|
|
1107
|
-
return this;
|
|
1108
|
-
},
|
|
1109
|
-
of(value) {
|
|
1110
|
-
this.$def.of = value;
|
|
1111
|
-
return this;
|
|
1112
|
-
},
|
|
1113
|
-
item(value) {
|
|
1114
|
-
this.$def.items.push(value);
|
|
1115
|
-
return this;
|
|
1116
|
-
},
|
|
1117
|
-
prop(name, value) {
|
|
1118
|
-
this.$def.props.set(name, value);
|
|
1119
|
-
return this;
|
|
1120
|
-
},
|
|
1121
|
-
propPattern(pattern, def) {
|
|
1122
|
-
this.$def.propsPatterns.push({
|
|
1123
|
-
pattern,
|
|
1124
|
-
def
|
|
1125
|
-
});
|
|
1126
|
-
return this;
|
|
1127
|
-
},
|
|
1128
|
-
optional(value = true) {
|
|
1129
|
-
this.$type.optional = value;
|
|
1130
|
-
return this;
|
|
1131
|
-
},
|
|
1132
|
-
copyMetadata(fromMetadata, ignore) {
|
|
1133
|
-
for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
|
|
1134
|
-
return this;
|
|
1135
|
-
},
|
|
1136
|
-
refTo(type$1, chain) {
|
|
1137
|
-
if (isAnnotatedType(type$1)) {
|
|
1138
|
-
let newBase = type$1;
|
|
1139
|
-
const typeName = type$1.name || "Unknown";
|
|
1140
|
-
if (chain) for (let i = 0; i < chain.length; i++) {
|
|
1141
|
-
const c = chain[i];
|
|
1142
|
-
if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
|
|
1143
|
-
else {
|
|
1144
|
-
const keys = chain.slice(0, i + 1).map((k) => `["${k}"]`).join("");
|
|
1145
|
-
throw new Error(`Can't find prop ${typeName}${keys}`);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
this.$type = createAnnotatedTypeNode(newBase.type, metadata, {
|
|
1149
|
-
id: newBase.id,
|
|
1150
|
-
ref: chain && chain.length > 0 ? {
|
|
1151
|
-
type: () => type$1,
|
|
1152
|
-
field: chain.join(".")
|
|
1153
|
-
} : undefined
|
|
1154
|
-
});
|
|
1155
|
-
} else if (typeof type$1 === "function") {
|
|
1156
|
-
const lazyType = type$1;
|
|
1157
|
-
this.$type = createAnnotatedTypeNode({ kind: "" }, metadata, { ref: {
|
|
1158
|
-
type: lazyType,
|
|
1159
|
-
field: chain ? chain.join(".") : ""
|
|
1160
|
-
} });
|
|
1161
|
-
const node = this.$type;
|
|
1162
|
-
const placeholder = node.type;
|
|
1163
|
-
Object.defineProperty(node, "type", {
|
|
1164
|
-
get() {
|
|
1165
|
-
const t = lazyType();
|
|
1166
|
-
if (!isAnnotatedType(t)) {
|
|
1167
|
-
Object.defineProperty(node, "type", {
|
|
1168
|
-
value: placeholder,
|
|
1169
|
-
writable: false,
|
|
1170
|
-
configurable: true
|
|
1171
|
-
});
|
|
1172
|
-
return placeholder;
|
|
1173
|
-
}
|
|
1174
|
-
let target = t;
|
|
1175
|
-
if (chain) for (const c of chain) if (target.type.kind === "object" && target.type.props.has(c)) target = target.type.props.get(c);
|
|
1176
|
-
else return t.type;
|
|
1177
|
-
node.id = target.id || t.id;
|
|
1178
|
-
Object.defineProperty(node, "type", {
|
|
1179
|
-
value: target.type,
|
|
1180
|
-
writable: false,
|
|
1181
|
-
configurable: true
|
|
1182
|
-
});
|
|
1183
|
-
return target.type;
|
|
1184
|
-
},
|
|
1185
|
-
configurable: true
|
|
1186
|
-
});
|
|
1187
|
-
} else throw new Error(`${type$1} is not annotated type`);
|
|
1188
|
-
return this;
|
|
1189
|
-
},
|
|
1190
|
-
annotate(key, value, asArray) {
|
|
1191
|
-
annotate(this.$metadata, key, value, asArray);
|
|
1192
|
-
return this;
|
|
1193
|
-
},
|
|
1194
|
-
id(value) {
|
|
1195
|
-
this.$type.id = value;
|
|
1196
|
-
return this;
|
|
1197
|
-
}
|
|
1198
|
-
};
|
|
1199
|
-
return handle;
|
|
1200
|
-
}
|
|
1201
|
-
function isPhantomType(def) {
|
|
1202
|
-
return def.type.kind === "" && def.type.designType === "phantom";
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
//#endregion
|
|
1206
|
-
//#region packages/typescript/src/traverse.ts
|
|
1207
|
-
function forAnnotatedType(def, handlers) {
|
|
1208
|
-
switch (def.type.kind) {
|
|
1209
|
-
case "": {
|
|
1210
|
-
const typed = def;
|
|
1211
|
-
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
1212
|
-
return handlers.final(typed);
|
|
1213
|
-
}
|
|
1214
|
-
case "object": return handlers.object(def);
|
|
1215
|
-
case "array": return handlers.array(def);
|
|
1216
|
-
case "union": return handlers.union(def);
|
|
1217
|
-
case "intersection": return handlers.intersection(def);
|
|
1218
|
-
case "tuple": return handlers.tuple(def);
|
|
1219
|
-
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
//#endregion
|
|
1224
|
-
//#region packages/typescript/src/json-schema.ts
|
|
1225
|
-
/**
|
|
1226
|
-
* Detects a discriminator property across union items.
|
|
1227
|
-
*
|
|
1228
|
-
* Scans all items for object-typed members that share a common property
|
|
1229
|
-
* with distinct const/literal values. If exactly one such property exists,
|
|
1230
|
-
* it is returned as the discriminator.
|
|
1231
|
-
*/ function detectDiscriminator(items) {
|
|
1232
|
-
if (items.length < 2) return null;
|
|
1233
|
-
for (const item of items) if (item.type.kind !== "object") return null;
|
|
1234
|
-
const firstObj = items[0].type;
|
|
1235
|
-
const candidates = [];
|
|
1236
|
-
for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
|
|
1237
|
-
let result = null;
|
|
1238
|
-
for (const candidate of candidates) {
|
|
1239
|
-
const values = new Set();
|
|
1240
|
-
const indexMapping = {};
|
|
1241
|
-
let valid = true;
|
|
1242
|
-
for (let i = 0; i < items.length; i++) {
|
|
1243
|
-
const obj = items[i].type;
|
|
1244
|
-
const prop = obj.props.get(candidate);
|
|
1245
|
-
if (!prop || prop.type.kind !== "" || prop.type.value === undefined) {
|
|
1246
|
-
valid = false;
|
|
1247
|
-
break;
|
|
1248
|
-
}
|
|
1249
|
-
const val = prop.type.value;
|
|
1250
|
-
if (values.has(val)) {
|
|
1251
|
-
valid = false;
|
|
1252
|
-
break;
|
|
1253
|
-
}
|
|
1254
|
-
values.add(val);
|
|
1255
|
-
indexMapping[String(val)] = i;
|
|
1256
|
-
}
|
|
1257
|
-
if (valid) {
|
|
1258
|
-
if (result) return null;
|
|
1259
|
-
result = {
|
|
1260
|
-
propertyName: candidate,
|
|
1261
|
-
indexMapping
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
return result;
|
|
1266
|
-
}
|
|
1267
|
-
function buildJsonSchema(type) {
|
|
1268
|
-
const defs = {};
|
|
1269
|
-
let hasDefs = false;
|
|
1270
|
-
const buildObject = (d) => {
|
|
1271
|
-
const properties = {};
|
|
1272
|
-
const required = [];
|
|
1273
|
-
for (const [key, val] of d.type.props.entries()) {
|
|
1274
|
-
if (isPhantomType(val)) continue;
|
|
1275
|
-
properties[key] = build(val);
|
|
1276
|
-
if (!val.optional) required.push(key);
|
|
1277
|
-
}
|
|
1278
|
-
const schema$1 = {
|
|
1279
|
-
type: "object",
|
|
1280
|
-
properties
|
|
1281
|
-
};
|
|
1282
|
-
if (required.length > 0) schema$1.required = required;
|
|
1283
|
-
return schema$1;
|
|
1284
|
-
};
|
|
1285
|
-
const build = (def) => {
|
|
1286
|
-
if (def.id && def.type.kind === "object" && def !== type) {
|
|
1287
|
-
const name = def.id;
|
|
1288
|
-
if (!defs[name]) {
|
|
1289
|
-
hasDefs = true;
|
|
1290
|
-
defs[name] = {};
|
|
1291
|
-
defs[name] = buildObject(def);
|
|
1292
|
-
}
|
|
1293
|
-
return { $ref: `#/$defs/${name}` };
|
|
1294
|
-
}
|
|
1295
|
-
const meta = def.metadata;
|
|
1296
|
-
return forAnnotatedType(def, {
|
|
1297
|
-
phantom() {
|
|
1298
|
-
return {};
|
|
1299
|
-
},
|
|
1300
|
-
object(d) {
|
|
1301
|
-
return buildObject(d);
|
|
1302
|
-
},
|
|
1303
|
-
array(d) {
|
|
1304
|
-
const schema$1 = {
|
|
1305
|
-
type: "array",
|
|
1306
|
-
items: build(d.type.of)
|
|
1307
|
-
};
|
|
1308
|
-
const minLength = meta.get("expect.minLength");
|
|
1309
|
-
if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
|
|
1310
|
-
const maxLength = meta.get("expect.maxLength");
|
|
1311
|
-
if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1312
|
-
return schema$1;
|
|
1313
|
-
},
|
|
1314
|
-
union(d) {
|
|
1315
|
-
const disc = detectDiscriminator(d.type.items);
|
|
1316
|
-
if (disc) {
|
|
1317
|
-
const oneOf = d.type.items.map(build);
|
|
1318
|
-
const mapping = {};
|
|
1319
|
-
for (const [val, idx] of Object.entries(disc.indexMapping)) {
|
|
1320
|
-
const item = d.type.items[idx];
|
|
1321
|
-
mapping[val] = item.id && defs[item.id] ? `#/$defs/${item.id}` : `#/oneOf/${idx}`;
|
|
1322
|
-
}
|
|
1323
|
-
return {
|
|
1324
|
-
oneOf,
|
|
1325
|
-
discriminator: {
|
|
1326
|
-
propertyName: disc.propertyName,
|
|
1327
|
-
mapping
|
|
1328
|
-
}
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
return { anyOf: d.type.items.map(build) };
|
|
1332
|
-
},
|
|
1333
|
-
intersection(d) {
|
|
1334
|
-
return { allOf: d.type.items.map(build) };
|
|
1335
|
-
},
|
|
1336
|
-
tuple(d) {
|
|
1337
|
-
return {
|
|
1338
|
-
type: "array",
|
|
1339
|
-
items: d.type.items.map(build),
|
|
1340
|
-
additionalItems: false
|
|
1341
|
-
};
|
|
1342
|
-
},
|
|
1343
|
-
final(d) {
|
|
1344
|
-
const schema$1 = {};
|
|
1345
|
-
if (d.type.value !== undefined) schema$1.const = d.type.value;
|
|
1346
|
-
if (d.type.designType && d.type.designType !== "any") {
|
|
1347
|
-
schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
|
|
1348
|
-
if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
|
|
1349
|
-
}
|
|
1350
|
-
if (schema$1.type === "string") {
|
|
1351
|
-
if (meta.get("meta.required")) schema$1.minLength = 1;
|
|
1352
|
-
const minLength = meta.get("expect.minLength");
|
|
1353
|
-
if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
|
|
1354
|
-
const maxLength = meta.get("expect.maxLength");
|
|
1355
|
-
if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1356
|
-
const patterns = meta.get("expect.pattern");
|
|
1357
|
-
if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
|
|
1358
|
-
else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
|
|
1359
|
-
}
|
|
1360
|
-
if (schema$1.type === "number" || schema$1.type === "integer") {
|
|
1361
|
-
const min = meta.get("expect.min");
|
|
1362
|
-
if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
|
|
1363
|
-
const max = meta.get("expect.max");
|
|
1364
|
-
if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
|
|
1365
|
-
}
|
|
1366
|
-
return schema$1;
|
|
1367
|
-
}
|
|
1368
|
-
});
|
|
1369
|
-
};
|
|
1370
|
-
const schema = build(type);
|
|
1371
|
-
if (hasDefs) return {
|
|
1372
|
-
...schema,
|
|
1373
|
-
$defs: defs
|
|
1374
|
-
};
|
|
1375
|
-
return schema;
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
654
|
//#endregion
|
|
1379
655
|
//#region packages/typescript/src/codegen/js-renderer.ts
|
|
1380
656
|
function _define_property(obj, key, value) {
|
|
@@ -1387,6 +663,19 @@ function _define_property(obj, key, value) {
|
|
|
1387
663
|
else obj[key] = value;
|
|
1388
664
|
return obj;
|
|
1389
665
|
}
|
|
666
|
+
const QUERY_OP_MAP = {
|
|
667
|
+
"=": "$eq",
|
|
668
|
+
"!=": "$ne",
|
|
669
|
+
">": "$gt",
|
|
670
|
+
">=": "$gte",
|
|
671
|
+
"<": "$lt",
|
|
672
|
+
"<=": "$lte",
|
|
673
|
+
"in": "$in",
|
|
674
|
+
"not in": "$nin",
|
|
675
|
+
"matches": "$regex",
|
|
676
|
+
"exists": "$exists",
|
|
677
|
+
"not exists": "$exists"
|
|
678
|
+
};
|
|
1390
679
|
var JsRenderer = class extends BaseRenderer {
|
|
1391
680
|
pre() {
|
|
1392
681
|
this.writeln("// prettier-ignore-start");
|
|
@@ -1507,7 +796,7 @@ else def = def.getDefinition() || def;
|
|
|
1507
796
|
const mode = resolveJsonSchemaMode(this.opts);
|
|
1508
797
|
const hasAnnotation = node.countAnnotations("emit.jsonSchema") > 0;
|
|
1509
798
|
if (hasAnnotation || mode === "bundle") {
|
|
1510
|
-
const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
|
|
799
|
+
const schema = JSON.stringify(require_json_schema.buildJsonSchema(this.toAnnotatedType(node)));
|
|
1511
800
|
this.writeln("static toJsonSchema() {");
|
|
1512
801
|
this.indent().writeln(`return ${schema}`).unindent();
|
|
1513
802
|
this.writeln("}");
|
|
@@ -1532,7 +821,7 @@ else def = def.getDefinition() || def;
|
|
|
1532
821
|
return this.toAnnotatedHandle(node).$type;
|
|
1533
822
|
}
|
|
1534
823
|
toAnnotatedHandle(node, skipAnnotations = false) {
|
|
1535
|
-
if (!node) return defineAnnotatedType();
|
|
824
|
+
if (!node) return require_json_schema.defineAnnotatedType();
|
|
1536
825
|
switch (node.entity) {
|
|
1537
826
|
case "interface":
|
|
1538
827
|
case "type": {
|
|
@@ -1564,14 +853,14 @@ else def = def.getDefinition() || def;
|
|
|
1564
853
|
}
|
|
1565
854
|
case "primitive": {
|
|
1566
855
|
const prim = node;
|
|
1567
|
-
const handle = defineAnnotatedType();
|
|
856
|
+
const handle = require_json_schema.defineAnnotatedType();
|
|
1568
857
|
handle.designType(prim.id === "never" ? "never" : prim.config.type);
|
|
1569
858
|
if (!skipAnnotations) this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1570
859
|
return handle;
|
|
1571
860
|
}
|
|
1572
861
|
case "const": {
|
|
1573
862
|
const c = node;
|
|
1574
|
-
const handle = defineAnnotatedType();
|
|
863
|
+
const handle = require_json_schema.defineAnnotatedType();
|
|
1575
864
|
const t = c.token("identifier")?.type;
|
|
1576
865
|
handle.designType(t === "number" ? "number" : "string");
|
|
1577
866
|
handle.value(t === "number" ? Number(c.id) : c.id);
|
|
@@ -1579,7 +868,7 @@ else def = def.getDefinition() || def;
|
|
|
1579
868
|
}
|
|
1580
869
|
case "structure": {
|
|
1581
870
|
const struct = node;
|
|
1582
|
-
const handle = defineAnnotatedType("object");
|
|
871
|
+
const handle = require_json_schema.defineAnnotatedType("object");
|
|
1583
872
|
for (const prop of Array.from(struct.props.values())) {
|
|
1584
873
|
const propHandle = this.toAnnotatedHandle(prop);
|
|
1585
874
|
const pattern = prop.token("identifier")?.pattern;
|
|
@@ -1591,24 +880,24 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1591
880
|
case "group": {
|
|
1592
881
|
const group = node;
|
|
1593
882
|
const kind = group.op === "|" ? "union" : "intersection";
|
|
1594
|
-
const handle = defineAnnotatedType(kind);
|
|
883
|
+
const handle = require_json_schema.defineAnnotatedType(kind);
|
|
1595
884
|
for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
|
|
1596
885
|
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1597
886
|
}
|
|
1598
887
|
case "tuple": {
|
|
1599
888
|
const group = node;
|
|
1600
|
-
const handle = defineAnnotatedType("tuple");
|
|
889
|
+
const handle = require_json_schema.defineAnnotatedType("tuple");
|
|
1601
890
|
for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
|
|
1602
891
|
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1603
892
|
}
|
|
1604
893
|
case "array": {
|
|
1605
894
|
const arr = node;
|
|
1606
|
-
const handle = defineAnnotatedType("array");
|
|
895
|
+
const handle = require_json_schema.defineAnnotatedType("array");
|
|
1607
896
|
handle.of(this.toAnnotatedHandle(arr.getDefinition()).$type);
|
|
1608
897
|
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1609
898
|
}
|
|
1610
899
|
default: {
|
|
1611
|
-
const handle = defineAnnotatedType();
|
|
900
|
+
const handle = require_json_schema.defineAnnotatedType();
|
|
1612
901
|
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1613
902
|
}
|
|
1614
903
|
}
|
|
@@ -1882,6 +1171,68 @@ else if (resolved && (0, __atscript_core.isPrimitive)(resolved)) {
|
|
|
1882
1171
|
if (multiple) this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1883
1172
|
else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value})`);
|
|
1884
1173
|
}
|
|
1174
|
+
emitRefValue(text) {
|
|
1175
|
+
const dotIdx = text.indexOf(".");
|
|
1176
|
+
if (dotIdx === -1) return `() => ${text}`;
|
|
1177
|
+
const typeName = text.slice(0, dotIdx);
|
|
1178
|
+
const field = text.slice(dotIdx + 1);
|
|
1179
|
+
return `{ type: () => ${typeName}, field: "${escapeQuotes(field)}" }`;
|
|
1180
|
+
}
|
|
1181
|
+
emitArgValue(aSpec, argToken) {
|
|
1182
|
+
if (aSpec.type === "ref") return this.emitRefValue(argToken.text);
|
|
1183
|
+
if (aSpec.type === "query" && argToken.queryNode) return this.emitQueryTree(argToken.queryNode);
|
|
1184
|
+
return aSpec.type === "string" ? `"${escapeQuotes(argToken.text)}"` : argToken.text;
|
|
1185
|
+
}
|
|
1186
|
+
emitQueryTree(queryNode) {
|
|
1187
|
+
return this.emitQueryExpr(queryNode.expression);
|
|
1188
|
+
}
|
|
1189
|
+
emitQueryExpr(node) {
|
|
1190
|
+
if ((0, __atscript_core.isQueryLogical)(node)) return this.emitQueryLogical(node);
|
|
1191
|
+
return this.emitQueryComparison(node);
|
|
1192
|
+
}
|
|
1193
|
+
emitQueryLogical(node) {
|
|
1194
|
+
if (node.operator === "not") return `{ "$not": ${this.emitQueryExpr(node.operands[0])} }`;
|
|
1195
|
+
const key = node.operator === "and" ? "$and" : "$or";
|
|
1196
|
+
const items = node.operands.map((op) => this.emitQueryExpr(op)).join(", ");
|
|
1197
|
+
return `{ "${key}": [${items}] }`;
|
|
1198
|
+
}
|
|
1199
|
+
emitQueryComparison(node) {
|
|
1200
|
+
const left = this.emitQueryFieldRef(node.left);
|
|
1201
|
+
const mappedOp = QUERY_OP_MAP[node.operator] || node.operator;
|
|
1202
|
+
const parts = [`left: ${left}`, `op: "${mappedOp}"`];
|
|
1203
|
+
if (node.right) {
|
|
1204
|
+
if ("fieldRef" in node.right && node.right.fieldRef) parts.push(`right: ${this.emitQueryFieldRef(node.right)}`);
|
|
1205
|
+
else if ("values" in node.right && node.right.values) {
|
|
1206
|
+
const values = node.right.values.map((v) => this.emitQueryLiteral(v)).join(", ");
|
|
1207
|
+
parts.push(`right: [${values}]`);
|
|
1208
|
+
} else if ("valueToken" in node.right) parts.push(`right: ${this.emitQueryLiteral(node.right)}`);
|
|
1209
|
+
} else if (node.operator === "exists") parts.push("right: true");
|
|
1210
|
+
else if (node.operator === "not exists") parts.push("right: false");
|
|
1211
|
+
return `{ ${parts.join(", ")} }`;
|
|
1212
|
+
}
|
|
1213
|
+
emitQueryFieldRef(node) {
|
|
1214
|
+
const parts = [];
|
|
1215
|
+
if (node.typeRef) parts.push(`type: () => ${node.typeRef.text}`);
|
|
1216
|
+
parts.push(`field: "${escapeQuotes(node.fieldRef.text)}"`);
|
|
1217
|
+
return `{ ${parts.join(", ")} }`;
|
|
1218
|
+
}
|
|
1219
|
+
emitQueryLiteral(node) {
|
|
1220
|
+
const token = node.valueToken;
|
|
1221
|
+
switch (token.type) {
|
|
1222
|
+
case "text": return `"${escapeQuotes(token.text)}"`;
|
|
1223
|
+
case "number": return token.text;
|
|
1224
|
+
case "regexp": {
|
|
1225
|
+
const match = /^\/(.*)\/[a-z]*$/.exec(token.text);
|
|
1226
|
+
return `"${escapeQuotes(match ? match[1] : token.text)}"`;
|
|
1227
|
+
}
|
|
1228
|
+
case "identifier": {
|
|
1229
|
+
if (token.text === "true" || token.text === "false") return token.text;
|
|
1230
|
+
if (token.text === "null" || token.text === "undefined") return "null";
|
|
1231
|
+
return token.text;
|
|
1232
|
+
}
|
|
1233
|
+
default: return token.text;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1885
1236
|
computeAnnotationValue(node, an) {
|
|
1886
1237
|
const spec = this.doc.resolveAnnotation(an.name);
|
|
1887
1238
|
let targetValue = "true";
|
|
@@ -1893,13 +1244,13 @@ else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value})`);
|
|
|
1893
1244
|
targetValue = "{ ";
|
|
1894
1245
|
let i = 0;
|
|
1895
1246
|
for (const aSpec of spec.arguments) {
|
|
1896
|
-
if (an.args[i]) targetValue += `${wrapProp(aSpec.name)}: ${aSpec
|
|
1247
|
+
if (an.args[i]) targetValue += `${wrapProp(aSpec.name)}: ${this.emitArgValue(aSpec, an.args[i])}${i === length - 1 ? "" : ", "} `;
|
|
1897
1248
|
i++;
|
|
1898
1249
|
}
|
|
1899
1250
|
targetValue += "}";
|
|
1900
1251
|
} else {
|
|
1901
1252
|
const aSpec = spec.arguments[0];
|
|
1902
|
-
if (an.args[0]) targetValue = aSpec
|
|
1253
|
+
if (an.args[0]) targetValue = this.emitArgValue(aSpec, an.args[0]);
|
|
1903
1254
|
else targetValue = "true";
|
|
1904
1255
|
}
|
|
1905
1256
|
} else {
|
|
@@ -2070,8 +1421,8 @@ const tsPlugin = (opts) => {
|
|
|
2070
1421
|
for (const [key, val] of Object.entries(annotations)) {
|
|
2071
1422
|
const multiple = val.multiple;
|
|
2072
1423
|
const typeLine = Array.from(val.types).map((t) => {
|
|
2073
|
-
if (t.type === "object") return `{ ${Object.entries(t.props).map(([k, v]) => `${wrapProp(k)}${v.optional ? "?" : ""}: ${v.type}`).join(", ")} }`;
|
|
2074
|
-
else return t.optional ? `${t.type} | true` : t.type;
|
|
1424
|
+
if (t.type === "object" && "props" in t) return `{ ${Object.entries(t.props).map(([k, v]) => `${wrapProp(k)}${v.optional ? "?" : ""}: ${v.type}`).join(", ")} }`;
|
|
1425
|
+
else return "optional" in t && t.optional ? `${t.type} | true` : t.type;
|
|
2075
1426
|
}).join(" | ");
|
|
2076
1427
|
rendered.push(`${wrapProp(key)}: ${multiple ? "(" : ""}${typeLine}${multiple ? ")[]" : ""}`);
|
|
2077
1428
|
}
|