@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.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { buildJsonSchema, defineAnnotatedType } from "./json-schema-0UUPoHud.mjs";
|
|
1
2
|
import path from "path";
|
|
2
|
-
import { DEFAULT_FORMAT, flattenInterfaceNode, isArray, isConst, isGroup, isInterface, isPrimitive, isRef, isStructure } from "@atscript/core";
|
|
3
|
+
import { DEFAULT_FORMAT, flattenInterfaceNode, hasNavPropAnnotation, isArray, isConst, isGroup, isInterface, isPrimitive, isQueryLogical, isRef, isStructure } from "@atscript/core";
|
|
3
4
|
|
|
4
5
|
//#region packages/typescript/src/codegen/code-printer.ts
|
|
5
|
-
function _define_property$
|
|
6
|
+
function _define_property$3(obj, key, value) {
|
|
6
7
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
7
8
|
value,
|
|
8
9
|
enumerable: true,
|
|
@@ -95,17 +96,17 @@ else this.write(closing);
|
|
|
95
96
|
return " ".repeat(this.indentLevel * this.indentSize);
|
|
96
97
|
}
|
|
97
98
|
constructor() {
|
|
98
|
-
_define_property$
|
|
99
|
-
_define_property$
|
|
100
|
-
_define_property$
|
|
101
|
-
_define_property$
|
|
102
|
-
_define_property$
|
|
99
|
+
_define_property$3(this, "lines", []);
|
|
100
|
+
_define_property$3(this, "currentLine", "");
|
|
101
|
+
_define_property$3(this, "indentLevel", 0);
|
|
102
|
+
_define_property$3(this, "indentSize", 2);
|
|
103
|
+
_define_property$3(this, "blockStack", []);
|
|
103
104
|
}
|
|
104
105
|
};
|
|
105
106
|
|
|
106
107
|
//#endregion
|
|
107
108
|
//#region packages/typescript/src/codegen/base-renderer.ts
|
|
108
|
-
function _define_property$
|
|
109
|
+
function _define_property$2(obj, key, value) {
|
|
109
110
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
110
111
|
value,
|
|
111
112
|
enumerable: true,
|
|
@@ -172,7 +173,7 @@ var BaseRenderer = class extends CodePrinter {
|
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
constructor(doc) {
|
|
175
|
-
super(), _define_property$
|
|
176
|
+
super(), _define_property$2(this, "doc", void 0), _define_property$2(this, "_unused", void 0), this.doc = doc;
|
|
176
177
|
}
|
|
177
178
|
};
|
|
178
179
|
|
|
@@ -189,7 +190,7 @@ function escapeQuotes(str) {
|
|
|
189
190
|
|
|
190
191
|
//#endregion
|
|
191
192
|
//#region packages/typescript/src/codegen/type-renderer.ts
|
|
192
|
-
function _define_property$
|
|
193
|
+
function _define_property$1(obj, key, value) {
|
|
193
194
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
194
195
|
value,
|
|
195
196
|
enumerable: true,
|
|
@@ -314,8 +315,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
314
315
|
this.writeln("static toJsonSchema: () => any");
|
|
315
316
|
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
316
317
|
this.writeln("static toExampleData?: () => any");
|
|
317
|
-
if (interfaceNode && this.
|
|
318
|
+
if (interfaceNode && this.hasDbEntity(interfaceNode)) {
|
|
318
319
|
this.renderFlat(interfaceNode);
|
|
320
|
+
this.renderOwnProps(interfaceNode);
|
|
321
|
+
this.renderNavProps(interfaceNode);
|
|
319
322
|
this.renderPk(interfaceNode);
|
|
320
323
|
}
|
|
321
324
|
}
|
|
@@ -406,14 +409,13 @@ else if (isPrimitive(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
|
|
|
406
409
|
this.popln();
|
|
407
410
|
}
|
|
408
411
|
/**
|
|
409
|
-
* Checks whether an interface
|
|
412
|
+
* Checks whether an interface is a DB entity (`@db.table` or `@db.view`).
|
|
410
413
|
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
return !!node.annotations?.some((a) => a.name === "db.table");
|
|
414
|
+
* Only DB entities get `__flat`, `__pk`, `__ownProps`, and `__navProps` static properties.
|
|
415
|
+
* These exist solely to improve type-safety for filter expressions, `$select`/`$sort`,
|
|
416
|
+
* and `$with` operations in the DB layer.
|
|
417
|
+
*/ hasDbEntity(node) {
|
|
418
|
+
return !!node.annotations?.some((a) => a.name === "db.table" || a.name === "db.view" || a.name === "db.view.for");
|
|
417
419
|
}
|
|
418
420
|
/**
|
|
419
421
|
* Renders the `static __flat` property — a map of all dot-notation paths
|
|
@@ -430,9 +432,27 @@ else if (isPrimitive(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
|
|
|
430
432
|
* not individually queryable)
|
|
431
433
|
* - **Leaf fields** → their original TypeScript type
|
|
432
434
|
*/ renderFlat(node) {
|
|
433
|
-
|
|
435
|
+
this.renderFlatMap("__flat", flattenInterfaceNode(this.doc, node));
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Renders the `static __ownProps` property — table-owned fields only (no nav props).
|
|
439
|
+
*/ renderOwnProps(node) {
|
|
440
|
+
this.renderFlatMap("__ownProps", flattenInterfaceNode(this.doc, node, { skipNavProps: true }), {
|
|
441
|
+
leadingNewline: true,
|
|
442
|
+
trailingNewline: true
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Shared renderer for flat-map static properties (`__flat`, `__ownProps`).
|
|
447
|
+
*
|
|
448
|
+
* Special rendering rules:
|
|
449
|
+
* - **Intermediate paths** (structures, arrays of structures) → `never`
|
|
450
|
+
* - **`@db.json` fields** → `string` (stored as serialized JSON in DB)
|
|
451
|
+
* - **Leaf fields** → their original TypeScript type
|
|
452
|
+
*/ renderFlatMap(propName, flatMap, opts) {
|
|
434
453
|
if (flatMap.size === 0) return;
|
|
435
|
-
this.
|
|
454
|
+
if (opts?.leadingNewline) this.writeln();
|
|
455
|
+
this.write(`static ${propName}: `);
|
|
436
456
|
this.blockln("{}");
|
|
437
457
|
for (const [path$1, descriptor] of flatMap) {
|
|
438
458
|
this.write(`"${escapeQuotes(path$1)}"`);
|
|
@@ -447,19 +467,46 @@ else {
|
|
|
447
467
|
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
448
468
|
}
|
|
449
469
|
}
|
|
450
|
-
this.
|
|
470
|
+
if (opts?.trailingNewline) this.popln();
|
|
471
|
+
else this.pop();
|
|
451
472
|
}
|
|
452
473
|
/**
|
|
453
|
-
* Renders the `static
|
|
454
|
-
*
|
|
474
|
+
* Renders the `static __navProps` property — a map of navigation property names
|
|
475
|
+
* to their declared TypeScript types.
|
|
455
476
|
*
|
|
456
|
-
* -
|
|
457
|
-
*
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
477
|
+
* This enables type-safe `$with` in queries: `name` is constrained to known
|
|
478
|
+
* navigation property keys, and nested filter/controls are typed to the target entity.
|
|
479
|
+
*/ renderNavProps(node) {
|
|
480
|
+
let struct;
|
|
481
|
+
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
482
|
+
if (!struct) struct = node.getDefinition();
|
|
483
|
+
if (!struct || !isStructure(struct)) return;
|
|
484
|
+
const structNode = struct;
|
|
485
|
+
const navProps = [];
|
|
486
|
+
for (const [name, prop] of structNode.props) {
|
|
487
|
+
if (prop.token("identifier")?.pattern) continue;
|
|
488
|
+
if (hasNavPropAnnotation(prop)) navProps.push({
|
|
489
|
+
name,
|
|
490
|
+
prop
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
if (navProps.length === 0) return;
|
|
494
|
+
this.writeln();
|
|
495
|
+
this.write("static __navProps: ");
|
|
496
|
+
this.blockln("{}");
|
|
497
|
+
for (const { name, prop } of navProps) {
|
|
498
|
+
this.write(`"${escapeQuotes(name)}"`);
|
|
499
|
+
if (prop.token("optional")) this.write("?");
|
|
500
|
+
this.write(": ");
|
|
501
|
+
const propDef = prop.getDefinition();
|
|
502
|
+
if (propDef) {
|
|
503
|
+
const renderedDef = this.renderTypeDefString(propDef);
|
|
504
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
505
|
+
} else this.writeln("unknown");
|
|
506
|
+
}
|
|
507
|
+
this.popln();
|
|
508
|
+
}
|
|
509
|
+
renderPk(node) {
|
|
463
510
|
const isMongoCollection = !!node.annotations?.some((a) => a.name === "db.mongo.collection");
|
|
464
511
|
let struct;
|
|
465
512
|
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
@@ -559,7 +606,7 @@ else if (pkProps.length === 1) {
|
|
|
559
606
|
this.writeln(` */`);
|
|
560
607
|
}
|
|
561
608
|
constructor(doc, opts) {
|
|
562
|
-
super(doc), _define_property$
|
|
609
|
+
super(doc), _define_property$1(this, "opts", void 0), this.opts = opts;
|
|
563
610
|
}
|
|
564
611
|
};
|
|
565
612
|
function renderPrimitiveTypeDef(def) {
|
|
@@ -579,777 +626,6 @@ function renderPrimitiveTypeDef(def) {
|
|
|
579
626
|
}
|
|
580
627
|
}
|
|
581
628
|
|
|
582
|
-
//#endregion
|
|
583
|
-
//#region packages/typescript/src/validator.ts
|
|
584
|
-
function _define_property$1(obj, key, value) {
|
|
585
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
586
|
-
value,
|
|
587
|
-
enumerable: true,
|
|
588
|
-
configurable: true,
|
|
589
|
-
writable: true
|
|
590
|
-
});
|
|
591
|
-
else obj[key] = value;
|
|
592
|
-
return obj;
|
|
593
|
-
}
|
|
594
|
-
const regexCache = new Map();
|
|
595
|
-
var Validator = class {
|
|
596
|
-
isLimitExceeded() {
|
|
597
|
-
if (this.stackErrors.length > 0) {
|
|
598
|
-
const top = this.stackErrors[this.stackErrors.length - 1];
|
|
599
|
-
return top !== null && top.length >= this.opts.errorLimit;
|
|
600
|
-
}
|
|
601
|
-
return this.errors.length >= this.opts.errorLimit;
|
|
602
|
-
}
|
|
603
|
-
push(name) {
|
|
604
|
-
this.stackPath.push(name);
|
|
605
|
-
this.stackErrors.push(null);
|
|
606
|
-
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
607
|
-
}
|
|
608
|
-
pop(saveErrors) {
|
|
609
|
-
this.stackPath.pop();
|
|
610
|
-
const popped = this.stackErrors.pop();
|
|
611
|
-
if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
|
|
612
|
-
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
613
|
-
return popped;
|
|
614
|
-
}
|
|
615
|
-
clear() {
|
|
616
|
-
this.stackErrors[this.stackErrors.length - 1] = null;
|
|
617
|
-
}
|
|
618
|
-
error(message, path$1, details) {
|
|
619
|
-
let errors = this.stackErrors[this.stackErrors.length - 1];
|
|
620
|
-
if (!errors) if (this.stackErrors.length > 0) {
|
|
621
|
-
errors = [];
|
|
622
|
-
this.stackErrors[this.stackErrors.length - 1] = errors;
|
|
623
|
-
} else errors = this.errors;
|
|
624
|
-
const error = {
|
|
625
|
-
path: path$1 || this.cachedPath,
|
|
626
|
-
message
|
|
627
|
-
};
|
|
628
|
-
if (details?.length) error.details = details;
|
|
629
|
-
errors.push(error);
|
|
630
|
-
}
|
|
631
|
-
throw() {
|
|
632
|
-
throw new ValidatorError(this.errors);
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Validates a value against the type definition.
|
|
636
|
-
*
|
|
637
|
-
* Acts as a TypeScript type guard — when it returns `true`, the value
|
|
638
|
-
* is narrowed to `DataType`.
|
|
639
|
-
*
|
|
640
|
-
* @param value - The value to validate.
|
|
641
|
-
* @param safe - If `true`, returns `false` on failure instead of throwing.
|
|
642
|
-
* @returns `true` if the value matches the type definition.
|
|
643
|
-
* @throws {ValidatorError} When validation fails and `safe` is not `true`.
|
|
644
|
-
*/ validate(value, safe, context) {
|
|
645
|
-
this.errors = [];
|
|
646
|
-
this.stackErrors = [];
|
|
647
|
-
this.stackPath = [""];
|
|
648
|
-
this.cachedPath = "";
|
|
649
|
-
this.context = context;
|
|
650
|
-
const passed = this.validateSafe(this.def, value);
|
|
651
|
-
this.pop(!passed);
|
|
652
|
-
this.context = undefined;
|
|
653
|
-
if (!passed) {
|
|
654
|
-
if (safe) return false;
|
|
655
|
-
this.throw();
|
|
656
|
-
}
|
|
657
|
-
return true;
|
|
658
|
-
}
|
|
659
|
-
validateSafe(def, value) {
|
|
660
|
-
if (this.isLimitExceeded()) return false;
|
|
661
|
-
if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
|
|
662
|
-
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
|
|
663
|
-
if (def.optional && value === undefined) return true;
|
|
664
|
-
for (const plugin of this.opts.plugins) {
|
|
665
|
-
const result = plugin(this, def, value);
|
|
666
|
-
if (result === false || result === true) return result;
|
|
667
|
-
}
|
|
668
|
-
return this.validateAnnotatedType(def, value);
|
|
669
|
-
}
|
|
670
|
-
get path() {
|
|
671
|
-
return this.cachedPath;
|
|
672
|
-
}
|
|
673
|
-
validateAnnotatedType(def, value) {
|
|
674
|
-
switch (def.type.kind) {
|
|
675
|
-
case "": {
|
|
676
|
-
if (def.type.designType === "phantom") return true;
|
|
677
|
-
return this.validatePrimitive(def, value);
|
|
678
|
-
}
|
|
679
|
-
case "object": return this.validateObject(def, value);
|
|
680
|
-
case "array": return this.validateArray(def, value);
|
|
681
|
-
case "union": return this.validateUnion(def, value);
|
|
682
|
-
case "intersection": return this.validateIntersection(def, value);
|
|
683
|
-
case "tuple": return this.validateTuple(def, value);
|
|
684
|
-
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
validateUnion(def, value) {
|
|
688
|
-
let i = 0;
|
|
689
|
-
const popped = [];
|
|
690
|
-
for (const item of def.type.items) {
|
|
691
|
-
this.push(`[${item.type.kind || item.type.designType}(${i})]`);
|
|
692
|
-
if (this.validateSafe(item, value)) {
|
|
693
|
-
this.pop(false);
|
|
694
|
-
return true;
|
|
695
|
-
}
|
|
696
|
-
const errors = this.pop(false);
|
|
697
|
-
if (errors) popped.push(...errors);
|
|
698
|
-
i++;
|
|
699
|
-
}
|
|
700
|
-
this.clear();
|
|
701
|
-
const expected = def.type.items.map((item, i$1) => `[${item.type.kind || item.type.designType}(${i$1})]`).join(", ");
|
|
702
|
-
this.error(`Value does not match any of the allowed types: ${expected}`, undefined, popped);
|
|
703
|
-
return false;
|
|
704
|
-
}
|
|
705
|
-
validateIntersection(def, value) {
|
|
706
|
-
for (const item of def.type.items) if (!this.validateSafe(item, value)) return false;
|
|
707
|
-
return true;
|
|
708
|
-
}
|
|
709
|
-
validateTuple(def, value) {
|
|
710
|
-
if (!Array.isArray(value) || value.length !== def.type.items.length) {
|
|
711
|
-
this.error(`Expected array of length ${def.type.items.length}`);
|
|
712
|
-
return false;
|
|
713
|
-
}
|
|
714
|
-
let i = 0;
|
|
715
|
-
for (const item of def.type.items) {
|
|
716
|
-
this.push(String(i));
|
|
717
|
-
if (!this.validateSafe(item, value[i])) {
|
|
718
|
-
this.pop(true);
|
|
719
|
-
return false;
|
|
720
|
-
}
|
|
721
|
-
this.pop(false);
|
|
722
|
-
i++;
|
|
723
|
-
}
|
|
724
|
-
return true;
|
|
725
|
-
}
|
|
726
|
-
validateArray(def, value) {
|
|
727
|
-
if (!Array.isArray(value)) {
|
|
728
|
-
this.error("Expected array");
|
|
729
|
-
return false;
|
|
730
|
-
}
|
|
731
|
-
const minLength = def.metadata.get("expect.minLength");
|
|
732
|
-
if (minLength) {
|
|
733
|
-
const length = typeof minLength === "number" ? minLength : minLength.length;
|
|
734
|
-
if (value.length < length) {
|
|
735
|
-
const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} items, got ${value.length} items`;
|
|
736
|
-
this.error(message);
|
|
737
|
-
return false;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
const maxLength = def.metadata.get("expect.maxLength");
|
|
741
|
-
if (maxLength) {
|
|
742
|
-
const length = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
743
|
-
if (value.length > length) {
|
|
744
|
-
const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} items, got ${value.length} items`;
|
|
745
|
-
this.error(message);
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
const uniqueItems = def.metadata.get("expect.array.uniqueItems");
|
|
750
|
-
if (uniqueItems) {
|
|
751
|
-
const separator = "▼↩";
|
|
752
|
-
const seen = new Set();
|
|
753
|
-
const keyProps = new Set();
|
|
754
|
-
if (def.type.of.type.kind === "object") {
|
|
755
|
-
for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
|
|
756
|
-
}
|
|
757
|
-
for (let idx = 0; idx < value.length; idx++) {
|
|
758
|
-
const item = value[idx];
|
|
759
|
-
let key;
|
|
760
|
-
if (keyProps.size > 0) {
|
|
761
|
-
key = "";
|
|
762
|
-
for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
|
|
763
|
-
} else key = JSON.stringify(item);
|
|
764
|
-
if (seen.has(key)) {
|
|
765
|
-
this.push(String(idx));
|
|
766
|
-
this.error(uniqueItems.message || "Duplicate items are not allowed");
|
|
767
|
-
this.pop(true);
|
|
768
|
-
return false;
|
|
769
|
-
}
|
|
770
|
-
seen.add(key);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
let i = 0;
|
|
774
|
-
let passed = true;
|
|
775
|
-
for (const item of value) {
|
|
776
|
-
this.push(String(i));
|
|
777
|
-
if (!this.validateSafe(def.type.of, item)) {
|
|
778
|
-
passed = false;
|
|
779
|
-
this.pop(true);
|
|
780
|
-
if (this.isLimitExceeded()) return false;
|
|
781
|
-
} else this.pop(false);
|
|
782
|
-
i++;
|
|
783
|
-
}
|
|
784
|
-
return passed;
|
|
785
|
-
}
|
|
786
|
-
validateObject(def, value) {
|
|
787
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
788
|
-
this.error("Expected object");
|
|
789
|
-
return false;
|
|
790
|
-
}
|
|
791
|
-
let passed = true;
|
|
792
|
-
const valueKeys = new Set(Object.keys(value));
|
|
793
|
-
const typeKeys = new Set();
|
|
794
|
-
let skipList;
|
|
795
|
-
if (this.opts.skipList) {
|
|
796
|
-
const path$1 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
|
|
797
|
-
for (const item of this.opts.skipList) if (item.startsWith(path$1)) {
|
|
798
|
-
const key = item.slice(path$1.length);
|
|
799
|
-
if (!skipList) skipList = new Set();
|
|
800
|
-
skipList.add(key);
|
|
801
|
-
valueKeys.delete(key);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
let partialFunctionMatched = false;
|
|
805
|
-
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
|
|
806
|
-
for (const [key, item] of def.type.props.entries()) {
|
|
807
|
-
if (skipList && skipList.has(key) || isPhantomType(item)) continue;
|
|
808
|
-
typeKeys.add(key);
|
|
809
|
-
if (value[key] === undefined) {
|
|
810
|
-
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
811
|
-
}
|
|
812
|
-
this.push(key);
|
|
813
|
-
if (this.validateSafe(item, value[key])) this.pop(false);
|
|
814
|
-
else {
|
|
815
|
-
passed = false;
|
|
816
|
-
this.pop(true);
|
|
817
|
-
if (this.isLimitExceeded()) return false;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
for (const key of valueKeys)
|
|
821
|
-
/** matched patterns for unknown keys */ if (!typeKeys.has(key)) {
|
|
822
|
-
const matched = [];
|
|
823
|
-
for (const { pattern, def: propDef } of def.type.propsPatterns) if (pattern.test(key)) matched.push({
|
|
824
|
-
pattern,
|
|
825
|
-
def: propDef
|
|
826
|
-
});
|
|
827
|
-
if (matched.length > 0) {
|
|
828
|
-
this.push(key);
|
|
829
|
-
let keyPassed = false;
|
|
830
|
-
for (const { def: propDef } of matched) {
|
|
831
|
-
if (this.validateSafe(propDef, value[key])) {
|
|
832
|
-
keyPassed = true;
|
|
833
|
-
break;
|
|
834
|
-
}
|
|
835
|
-
this.clear();
|
|
836
|
-
}
|
|
837
|
-
if (!keyPassed) {
|
|
838
|
-
this.validateSafe(matched[0].def, value[key]);
|
|
839
|
-
this.pop(true);
|
|
840
|
-
passed = false;
|
|
841
|
-
if (this.isLimitExceeded()) return false;
|
|
842
|
-
} else this.pop(false);
|
|
843
|
-
} else if (this.opts.unknownProps !== "ignore") {
|
|
844
|
-
if (this.opts.unknownProps === "error") {
|
|
845
|
-
this.push(key);
|
|
846
|
-
this.error(`Unexpected property`);
|
|
847
|
-
this.pop(true);
|
|
848
|
-
if (this.isLimitExceeded()) return false;
|
|
849
|
-
passed = false;
|
|
850
|
-
} else if (this.opts.unknownProps === "strip") delete value[key];
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
return passed;
|
|
854
|
-
}
|
|
855
|
-
validatePrimitive(def, value) {
|
|
856
|
-
if (def.type.value !== undefined) {
|
|
857
|
-
if (value !== def.type.value) {
|
|
858
|
-
this.error(`Expected ${def.type.value}, got ${value}`);
|
|
859
|
-
return false;
|
|
860
|
-
}
|
|
861
|
-
return true;
|
|
862
|
-
}
|
|
863
|
-
const typeOfValue = Array.isArray(value) ? "array" : typeof value;
|
|
864
|
-
switch (def.type.designType) {
|
|
865
|
-
case "never": {
|
|
866
|
-
this.error(`This type is impossible, must be an internal problem`);
|
|
867
|
-
return false;
|
|
868
|
-
}
|
|
869
|
-
case "any": return true;
|
|
870
|
-
case "string": {
|
|
871
|
-
if (typeOfValue !== def.type.designType) {
|
|
872
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
873
|
-
return false;
|
|
874
|
-
}
|
|
875
|
-
return this.validateString(def, value);
|
|
876
|
-
}
|
|
877
|
-
case "number": {
|
|
878
|
-
if (typeOfValue !== def.type.designType) {
|
|
879
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
880
|
-
return false;
|
|
881
|
-
}
|
|
882
|
-
return this.validateNumber(def, value);
|
|
883
|
-
}
|
|
884
|
-
case "boolean": {
|
|
885
|
-
if (typeOfValue !== def.type.designType) {
|
|
886
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
887
|
-
return false;
|
|
888
|
-
}
|
|
889
|
-
return this.validateBoolean(def, value);
|
|
890
|
-
}
|
|
891
|
-
case "undefined": {
|
|
892
|
-
if (value !== undefined) {
|
|
893
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
894
|
-
return false;
|
|
895
|
-
}
|
|
896
|
-
return true;
|
|
897
|
-
}
|
|
898
|
-
case "null": {
|
|
899
|
-
if (value !== null) {
|
|
900
|
-
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
901
|
-
return false;
|
|
902
|
-
}
|
|
903
|
-
return true;
|
|
904
|
-
}
|
|
905
|
-
default: throw new Error(`Unknown type "${def.type.designType}"`);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
validateString(def, value) {
|
|
909
|
-
const filled = def.metadata.get("meta.required");
|
|
910
|
-
if (filled) {
|
|
911
|
-
if (value.trim().length === 0) {
|
|
912
|
-
const message = typeof filled === "object" && filled.message ? filled.message : `Must not be empty`;
|
|
913
|
-
this.error(message);
|
|
914
|
-
return false;
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
const minLength = def.metadata.get("expect.minLength");
|
|
918
|
-
if (minLength) {
|
|
919
|
-
const length = typeof minLength === "number" ? minLength : minLength.length;
|
|
920
|
-
if (value.length < length) {
|
|
921
|
-
const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} characters, got ${value.length} characters`;
|
|
922
|
-
this.error(message);
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
const maxLength = def.metadata.get("expect.maxLength");
|
|
927
|
-
if (maxLength) {
|
|
928
|
-
const length = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
929
|
-
if (value.length > length) {
|
|
930
|
-
const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} characters, got ${value.length} characters`;
|
|
931
|
-
this.error(message);
|
|
932
|
-
return false;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
const patterns = def.metadata.get("expect.pattern");
|
|
936
|
-
for (const { pattern, flags, message } of patterns || []) {
|
|
937
|
-
if (!pattern) continue;
|
|
938
|
-
const cacheKey = `${pattern}//${flags || ""}`;
|
|
939
|
-
let regex = regexCache.get(cacheKey);
|
|
940
|
-
if (!regex) {
|
|
941
|
-
regex = new RegExp(pattern, flags);
|
|
942
|
-
regexCache.set(cacheKey, regex);
|
|
943
|
-
}
|
|
944
|
-
if (!regex.test(value)) {
|
|
945
|
-
this.error(message || `Value is expected to match pattern "${pattern}"`);
|
|
946
|
-
return false;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
return true;
|
|
950
|
-
}
|
|
951
|
-
validateNumber(def, value) {
|
|
952
|
-
const int = def.metadata.get("expect.int");
|
|
953
|
-
if (int && value % 1 !== 0) {
|
|
954
|
-
const message = typeof int === "object" && int.message ? int.message : `Expected integer, got ${value}`;
|
|
955
|
-
this.error(message);
|
|
956
|
-
return false;
|
|
957
|
-
}
|
|
958
|
-
const min = def.metadata.get("expect.min");
|
|
959
|
-
if (min) {
|
|
960
|
-
const minValue = typeof min === "number" ? min : min.minValue;
|
|
961
|
-
if (value < minValue) {
|
|
962
|
-
const message = typeof min === "object" && min.message ? min.message : `Expected minimum ${minValue}, got ${value}`;
|
|
963
|
-
this.error(message);
|
|
964
|
-
return false;
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
const max = def.metadata.get("expect.max");
|
|
968
|
-
if (max) {
|
|
969
|
-
const maxValue = typeof max === "number" ? max : max.maxValue;
|
|
970
|
-
if (value > maxValue) {
|
|
971
|
-
const message = typeof max === "object" && max.message ? max.message : `Expected maximum ${maxValue}, got ${value}`;
|
|
972
|
-
this.error(message);
|
|
973
|
-
return false;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
return true;
|
|
977
|
-
}
|
|
978
|
-
validateBoolean(def, value) {
|
|
979
|
-
const filled = def.metadata.get("meta.required");
|
|
980
|
-
if (filled) {
|
|
981
|
-
if (value !== true) {
|
|
982
|
-
const message = typeof filled === "object" && filled.message ? filled.message : `Must be checked`;
|
|
983
|
-
this.error(message);
|
|
984
|
-
return false;
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
return true;
|
|
988
|
-
}
|
|
989
|
-
constructor(def, opts) {
|
|
990
|
-
_define_property$1(this, "def", void 0);
|
|
991
|
-
_define_property$1(this, "opts", void 0);
|
|
992
|
-
/** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
|
|
993
|
-
_define_property$1(this, "stackErrors", void 0);
|
|
994
|
-
_define_property$1(this, "stackPath", void 0);
|
|
995
|
-
_define_property$1(this, "cachedPath", void 0);
|
|
996
|
-
_define_property$1(this, "context", void 0);
|
|
997
|
-
this.def = def;
|
|
998
|
-
this.errors = [];
|
|
999
|
-
this.stackErrors = [];
|
|
1000
|
-
this.stackPath = [];
|
|
1001
|
-
this.cachedPath = "";
|
|
1002
|
-
this.opts = {
|
|
1003
|
-
partial: false,
|
|
1004
|
-
unknownProps: "error",
|
|
1005
|
-
errorLimit: 10,
|
|
1006
|
-
...opts,
|
|
1007
|
-
plugins: opts?.plugins || []
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
};
|
|
1011
|
-
var ValidatorError = class extends Error {
|
|
1012
|
-
constructor(errors) {
|
|
1013
|
-
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";
|
|
1014
|
-
}
|
|
1015
|
-
};
|
|
1016
|
-
|
|
1017
|
-
//#endregion
|
|
1018
|
-
//#region packages/typescript/src/annotated-type.ts
|
|
1019
|
-
const COMPLEX_KINDS = new Set([
|
|
1020
|
-
"union",
|
|
1021
|
-
"intersection",
|
|
1022
|
-
"tuple"
|
|
1023
|
-
]);
|
|
1024
|
-
/** Shared validator method reused by all annotated type nodes. */ function validatorMethod(opts) {
|
|
1025
|
-
return new Validator(this, opts);
|
|
1026
|
-
}
|
|
1027
|
-
function createAnnotatedTypeNode(type, metadata, opts) {
|
|
1028
|
-
return {
|
|
1029
|
-
__is_atscript_annotated_type: true,
|
|
1030
|
-
type,
|
|
1031
|
-
metadata,
|
|
1032
|
-
validator: validatorMethod,
|
|
1033
|
-
id: opts?.id,
|
|
1034
|
-
optional: opts?.optional,
|
|
1035
|
-
ref: opts?.ref
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
function isAnnotatedType(type) {
|
|
1039
|
-
return type && type.__is_atscript_annotated_type;
|
|
1040
|
-
}
|
|
1041
|
-
function annotate(metadata, key, value, asArray) {
|
|
1042
|
-
if (!metadata) return;
|
|
1043
|
-
if (asArray) if (metadata.has(key)) {
|
|
1044
|
-
const a = metadata.get(key);
|
|
1045
|
-
if (Array.isArray(a)) a.push(value);
|
|
1046
|
-
else metadata.set(key, [a, value]);
|
|
1047
|
-
} else metadata.set(key, [value]);
|
|
1048
|
-
else metadata.set(key, value);
|
|
1049
|
-
}
|
|
1050
|
-
function defineAnnotatedType(_kind, base) {
|
|
1051
|
-
const kind = _kind || "";
|
|
1052
|
-
const type = base?.type || {};
|
|
1053
|
-
type.kind = kind;
|
|
1054
|
-
if (COMPLEX_KINDS.has(kind)) type.items = [];
|
|
1055
|
-
if (kind === "object") {
|
|
1056
|
-
type.props = new Map();
|
|
1057
|
-
type.propsPatterns = [];
|
|
1058
|
-
}
|
|
1059
|
-
type.tags = new Set();
|
|
1060
|
-
const metadata = base?.metadata || new Map();
|
|
1061
|
-
const payload = {
|
|
1062
|
-
__is_atscript_annotated_type: true,
|
|
1063
|
-
metadata,
|
|
1064
|
-
type,
|
|
1065
|
-
validator: validatorMethod
|
|
1066
|
-
};
|
|
1067
|
-
base = base ? Object.assign(base, payload) : payload;
|
|
1068
|
-
const handle = {
|
|
1069
|
-
$type: base,
|
|
1070
|
-
$def: type,
|
|
1071
|
-
$metadata: metadata,
|
|
1072
|
-
tags(...tags) {
|
|
1073
|
-
for (const tag of tags) this.$def.tags.add(tag);
|
|
1074
|
-
return this;
|
|
1075
|
-
},
|
|
1076
|
-
designType(value) {
|
|
1077
|
-
this.$def.designType = value;
|
|
1078
|
-
return this;
|
|
1079
|
-
},
|
|
1080
|
-
value(value) {
|
|
1081
|
-
this.$def.value = value;
|
|
1082
|
-
return this;
|
|
1083
|
-
},
|
|
1084
|
-
of(value) {
|
|
1085
|
-
this.$def.of = value;
|
|
1086
|
-
return this;
|
|
1087
|
-
},
|
|
1088
|
-
item(value) {
|
|
1089
|
-
this.$def.items.push(value);
|
|
1090
|
-
return this;
|
|
1091
|
-
},
|
|
1092
|
-
prop(name, value) {
|
|
1093
|
-
this.$def.props.set(name, value);
|
|
1094
|
-
return this;
|
|
1095
|
-
},
|
|
1096
|
-
propPattern(pattern, def) {
|
|
1097
|
-
this.$def.propsPatterns.push({
|
|
1098
|
-
pattern,
|
|
1099
|
-
def
|
|
1100
|
-
});
|
|
1101
|
-
return this;
|
|
1102
|
-
},
|
|
1103
|
-
optional(value = true) {
|
|
1104
|
-
this.$type.optional = value;
|
|
1105
|
-
return this;
|
|
1106
|
-
},
|
|
1107
|
-
copyMetadata(fromMetadata, ignore) {
|
|
1108
|
-
for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
|
|
1109
|
-
return this;
|
|
1110
|
-
},
|
|
1111
|
-
refTo(type$1, chain) {
|
|
1112
|
-
if (isAnnotatedType(type$1)) {
|
|
1113
|
-
let newBase = type$1;
|
|
1114
|
-
const typeName = type$1.name || "Unknown";
|
|
1115
|
-
if (chain) for (let i = 0; i < chain.length; i++) {
|
|
1116
|
-
const c = chain[i];
|
|
1117
|
-
if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
|
|
1118
|
-
else {
|
|
1119
|
-
const keys = chain.slice(0, i + 1).map((k) => `["${k}"]`).join("");
|
|
1120
|
-
throw new Error(`Can't find prop ${typeName}${keys}`);
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
this.$type = createAnnotatedTypeNode(newBase.type, metadata, {
|
|
1124
|
-
id: newBase.id,
|
|
1125
|
-
ref: chain && chain.length > 0 ? {
|
|
1126
|
-
type: () => type$1,
|
|
1127
|
-
field: chain.join(".")
|
|
1128
|
-
} : undefined
|
|
1129
|
-
});
|
|
1130
|
-
} else if (typeof type$1 === "function") {
|
|
1131
|
-
const lazyType = type$1;
|
|
1132
|
-
this.$type = createAnnotatedTypeNode({ kind: "" }, metadata, { ref: {
|
|
1133
|
-
type: lazyType,
|
|
1134
|
-
field: chain ? chain.join(".") : ""
|
|
1135
|
-
} });
|
|
1136
|
-
const node = this.$type;
|
|
1137
|
-
const placeholder = node.type;
|
|
1138
|
-
Object.defineProperty(node, "type", {
|
|
1139
|
-
get() {
|
|
1140
|
-
const t = lazyType();
|
|
1141
|
-
if (!isAnnotatedType(t)) {
|
|
1142
|
-
Object.defineProperty(node, "type", {
|
|
1143
|
-
value: placeholder,
|
|
1144
|
-
writable: false,
|
|
1145
|
-
configurable: true
|
|
1146
|
-
});
|
|
1147
|
-
return placeholder;
|
|
1148
|
-
}
|
|
1149
|
-
let target = t;
|
|
1150
|
-
if (chain) for (const c of chain) if (target.type.kind === "object" && target.type.props.has(c)) target = target.type.props.get(c);
|
|
1151
|
-
else return t.type;
|
|
1152
|
-
node.id = target.id || t.id;
|
|
1153
|
-
Object.defineProperty(node, "type", {
|
|
1154
|
-
value: target.type,
|
|
1155
|
-
writable: false,
|
|
1156
|
-
configurable: true
|
|
1157
|
-
});
|
|
1158
|
-
return target.type;
|
|
1159
|
-
},
|
|
1160
|
-
configurable: true
|
|
1161
|
-
});
|
|
1162
|
-
} else throw new Error(`${type$1} is not annotated type`);
|
|
1163
|
-
return this;
|
|
1164
|
-
},
|
|
1165
|
-
annotate(key, value, asArray) {
|
|
1166
|
-
annotate(this.$metadata, key, value, asArray);
|
|
1167
|
-
return this;
|
|
1168
|
-
},
|
|
1169
|
-
id(value) {
|
|
1170
|
-
this.$type.id = value;
|
|
1171
|
-
return this;
|
|
1172
|
-
}
|
|
1173
|
-
};
|
|
1174
|
-
return handle;
|
|
1175
|
-
}
|
|
1176
|
-
function isPhantomType(def) {
|
|
1177
|
-
return def.type.kind === "" && def.type.designType === "phantom";
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
//#endregion
|
|
1181
|
-
//#region packages/typescript/src/traverse.ts
|
|
1182
|
-
function forAnnotatedType(def, handlers) {
|
|
1183
|
-
switch (def.type.kind) {
|
|
1184
|
-
case "": {
|
|
1185
|
-
const typed = def;
|
|
1186
|
-
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
1187
|
-
return handlers.final(typed);
|
|
1188
|
-
}
|
|
1189
|
-
case "object": return handlers.object(def);
|
|
1190
|
-
case "array": return handlers.array(def);
|
|
1191
|
-
case "union": return handlers.union(def);
|
|
1192
|
-
case "intersection": return handlers.intersection(def);
|
|
1193
|
-
case "tuple": return handlers.tuple(def);
|
|
1194
|
-
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
//#endregion
|
|
1199
|
-
//#region packages/typescript/src/json-schema.ts
|
|
1200
|
-
/**
|
|
1201
|
-
* Detects a discriminator property across union items.
|
|
1202
|
-
*
|
|
1203
|
-
* Scans all items for object-typed members that share a common property
|
|
1204
|
-
* with distinct const/literal values. If exactly one such property exists,
|
|
1205
|
-
* it is returned as the discriminator.
|
|
1206
|
-
*/ function detectDiscriminator(items) {
|
|
1207
|
-
if (items.length < 2) return null;
|
|
1208
|
-
for (const item of items) if (item.type.kind !== "object") return null;
|
|
1209
|
-
const firstObj = items[0].type;
|
|
1210
|
-
const candidates = [];
|
|
1211
|
-
for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
|
|
1212
|
-
let result = null;
|
|
1213
|
-
for (const candidate of candidates) {
|
|
1214
|
-
const values = new Set();
|
|
1215
|
-
const indexMapping = {};
|
|
1216
|
-
let valid = true;
|
|
1217
|
-
for (let i = 0; i < items.length; i++) {
|
|
1218
|
-
const obj = items[i].type;
|
|
1219
|
-
const prop = obj.props.get(candidate);
|
|
1220
|
-
if (!prop || prop.type.kind !== "" || prop.type.value === undefined) {
|
|
1221
|
-
valid = false;
|
|
1222
|
-
break;
|
|
1223
|
-
}
|
|
1224
|
-
const val = prop.type.value;
|
|
1225
|
-
if (values.has(val)) {
|
|
1226
|
-
valid = false;
|
|
1227
|
-
break;
|
|
1228
|
-
}
|
|
1229
|
-
values.add(val);
|
|
1230
|
-
indexMapping[String(val)] = i;
|
|
1231
|
-
}
|
|
1232
|
-
if (valid) {
|
|
1233
|
-
if (result) return null;
|
|
1234
|
-
result = {
|
|
1235
|
-
propertyName: candidate,
|
|
1236
|
-
indexMapping
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
return result;
|
|
1241
|
-
}
|
|
1242
|
-
function buildJsonSchema(type) {
|
|
1243
|
-
const defs = {};
|
|
1244
|
-
let hasDefs = false;
|
|
1245
|
-
const buildObject = (d) => {
|
|
1246
|
-
const properties = {};
|
|
1247
|
-
const required = [];
|
|
1248
|
-
for (const [key, val] of d.type.props.entries()) {
|
|
1249
|
-
if (isPhantomType(val)) continue;
|
|
1250
|
-
properties[key] = build(val);
|
|
1251
|
-
if (!val.optional) required.push(key);
|
|
1252
|
-
}
|
|
1253
|
-
const schema$1 = {
|
|
1254
|
-
type: "object",
|
|
1255
|
-
properties
|
|
1256
|
-
};
|
|
1257
|
-
if (required.length > 0) schema$1.required = required;
|
|
1258
|
-
return schema$1;
|
|
1259
|
-
};
|
|
1260
|
-
const build = (def) => {
|
|
1261
|
-
if (def.id && def.type.kind === "object" && def !== type) {
|
|
1262
|
-
const name = def.id;
|
|
1263
|
-
if (!defs[name]) {
|
|
1264
|
-
hasDefs = true;
|
|
1265
|
-
defs[name] = {};
|
|
1266
|
-
defs[name] = buildObject(def);
|
|
1267
|
-
}
|
|
1268
|
-
return { $ref: `#/$defs/${name}` };
|
|
1269
|
-
}
|
|
1270
|
-
const meta = def.metadata;
|
|
1271
|
-
return forAnnotatedType(def, {
|
|
1272
|
-
phantom() {
|
|
1273
|
-
return {};
|
|
1274
|
-
},
|
|
1275
|
-
object(d) {
|
|
1276
|
-
return buildObject(d);
|
|
1277
|
-
},
|
|
1278
|
-
array(d) {
|
|
1279
|
-
const schema$1 = {
|
|
1280
|
-
type: "array",
|
|
1281
|
-
items: build(d.type.of)
|
|
1282
|
-
};
|
|
1283
|
-
const minLength = meta.get("expect.minLength");
|
|
1284
|
-
if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
|
|
1285
|
-
const maxLength = meta.get("expect.maxLength");
|
|
1286
|
-
if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1287
|
-
return schema$1;
|
|
1288
|
-
},
|
|
1289
|
-
union(d) {
|
|
1290
|
-
const disc = detectDiscriminator(d.type.items);
|
|
1291
|
-
if (disc) {
|
|
1292
|
-
const oneOf = d.type.items.map(build);
|
|
1293
|
-
const mapping = {};
|
|
1294
|
-
for (const [val, idx] of Object.entries(disc.indexMapping)) {
|
|
1295
|
-
const item = d.type.items[idx];
|
|
1296
|
-
mapping[val] = item.id && defs[item.id] ? `#/$defs/${item.id}` : `#/oneOf/${idx}`;
|
|
1297
|
-
}
|
|
1298
|
-
return {
|
|
1299
|
-
oneOf,
|
|
1300
|
-
discriminator: {
|
|
1301
|
-
propertyName: disc.propertyName,
|
|
1302
|
-
mapping
|
|
1303
|
-
}
|
|
1304
|
-
};
|
|
1305
|
-
}
|
|
1306
|
-
return { anyOf: d.type.items.map(build) };
|
|
1307
|
-
},
|
|
1308
|
-
intersection(d) {
|
|
1309
|
-
return { allOf: d.type.items.map(build) };
|
|
1310
|
-
},
|
|
1311
|
-
tuple(d) {
|
|
1312
|
-
return {
|
|
1313
|
-
type: "array",
|
|
1314
|
-
items: d.type.items.map(build),
|
|
1315
|
-
additionalItems: false
|
|
1316
|
-
};
|
|
1317
|
-
},
|
|
1318
|
-
final(d) {
|
|
1319
|
-
const schema$1 = {};
|
|
1320
|
-
if (d.type.value !== undefined) schema$1.const = d.type.value;
|
|
1321
|
-
if (d.type.designType && d.type.designType !== "any") {
|
|
1322
|
-
schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
|
|
1323
|
-
if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
|
|
1324
|
-
}
|
|
1325
|
-
if (schema$1.type === "string") {
|
|
1326
|
-
if (meta.get("meta.required")) schema$1.minLength = 1;
|
|
1327
|
-
const minLength = meta.get("expect.minLength");
|
|
1328
|
-
if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
|
|
1329
|
-
const maxLength = meta.get("expect.maxLength");
|
|
1330
|
-
if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1331
|
-
const patterns = meta.get("expect.pattern");
|
|
1332
|
-
if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
|
|
1333
|
-
else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
|
|
1334
|
-
}
|
|
1335
|
-
if (schema$1.type === "number" || schema$1.type === "integer") {
|
|
1336
|
-
const min = meta.get("expect.min");
|
|
1337
|
-
if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
|
|
1338
|
-
const max = meta.get("expect.max");
|
|
1339
|
-
if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
|
|
1340
|
-
}
|
|
1341
|
-
return schema$1;
|
|
1342
|
-
}
|
|
1343
|
-
});
|
|
1344
|
-
};
|
|
1345
|
-
const schema = build(type);
|
|
1346
|
-
if (hasDefs) return {
|
|
1347
|
-
...schema,
|
|
1348
|
-
$defs: defs
|
|
1349
|
-
};
|
|
1350
|
-
return schema;
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
629
|
//#endregion
|
|
1354
630
|
//#region packages/typescript/src/codegen/js-renderer.ts
|
|
1355
631
|
function _define_property(obj, key, value) {
|
|
@@ -1362,6 +638,19 @@ function _define_property(obj, key, value) {
|
|
|
1362
638
|
else obj[key] = value;
|
|
1363
639
|
return obj;
|
|
1364
640
|
}
|
|
641
|
+
const QUERY_OP_MAP = {
|
|
642
|
+
"=": "$eq",
|
|
643
|
+
"!=": "$ne",
|
|
644
|
+
">": "$gt",
|
|
645
|
+
">=": "$gte",
|
|
646
|
+
"<": "$lt",
|
|
647
|
+
"<=": "$lte",
|
|
648
|
+
"in": "$in",
|
|
649
|
+
"not in": "$nin",
|
|
650
|
+
"matches": "$regex",
|
|
651
|
+
"exists": "$exists",
|
|
652
|
+
"not exists": "$exists"
|
|
653
|
+
};
|
|
1365
654
|
var JsRenderer = class extends BaseRenderer {
|
|
1366
655
|
pre() {
|
|
1367
656
|
this.writeln("// prettier-ignore-start");
|
|
@@ -1857,6 +1146,68 @@ else if (resolved && isPrimitive(resolved)) {
|
|
|
1857
1146
|
if (multiple) this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1858
1147
|
else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value})`);
|
|
1859
1148
|
}
|
|
1149
|
+
emitRefValue(text) {
|
|
1150
|
+
const dotIdx = text.indexOf(".");
|
|
1151
|
+
if (dotIdx === -1) return `() => ${text}`;
|
|
1152
|
+
const typeName = text.slice(0, dotIdx);
|
|
1153
|
+
const field = text.slice(dotIdx + 1);
|
|
1154
|
+
return `{ type: () => ${typeName}, field: "${escapeQuotes(field)}" }`;
|
|
1155
|
+
}
|
|
1156
|
+
emitArgValue(aSpec, argToken) {
|
|
1157
|
+
if (aSpec.type === "ref") return this.emitRefValue(argToken.text);
|
|
1158
|
+
if (aSpec.type === "query" && argToken.queryNode) return this.emitQueryTree(argToken.queryNode);
|
|
1159
|
+
return aSpec.type === "string" ? `"${escapeQuotes(argToken.text)}"` : argToken.text;
|
|
1160
|
+
}
|
|
1161
|
+
emitQueryTree(queryNode) {
|
|
1162
|
+
return this.emitQueryExpr(queryNode.expression);
|
|
1163
|
+
}
|
|
1164
|
+
emitQueryExpr(node) {
|
|
1165
|
+
if (isQueryLogical(node)) return this.emitQueryLogical(node);
|
|
1166
|
+
return this.emitQueryComparison(node);
|
|
1167
|
+
}
|
|
1168
|
+
emitQueryLogical(node) {
|
|
1169
|
+
if (node.operator === "not") return `{ "$not": ${this.emitQueryExpr(node.operands[0])} }`;
|
|
1170
|
+
const key = node.operator === "and" ? "$and" : "$or";
|
|
1171
|
+
const items = node.operands.map((op) => this.emitQueryExpr(op)).join(", ");
|
|
1172
|
+
return `{ "${key}": [${items}] }`;
|
|
1173
|
+
}
|
|
1174
|
+
emitQueryComparison(node) {
|
|
1175
|
+
const left = this.emitQueryFieldRef(node.left);
|
|
1176
|
+
const mappedOp = QUERY_OP_MAP[node.operator] || node.operator;
|
|
1177
|
+
const parts = [`left: ${left}`, `op: "${mappedOp}"`];
|
|
1178
|
+
if (node.right) {
|
|
1179
|
+
if ("fieldRef" in node.right && node.right.fieldRef) parts.push(`right: ${this.emitQueryFieldRef(node.right)}`);
|
|
1180
|
+
else if ("values" in node.right && node.right.values) {
|
|
1181
|
+
const values = node.right.values.map((v) => this.emitQueryLiteral(v)).join(", ");
|
|
1182
|
+
parts.push(`right: [${values}]`);
|
|
1183
|
+
} else if ("valueToken" in node.right) parts.push(`right: ${this.emitQueryLiteral(node.right)}`);
|
|
1184
|
+
} else if (node.operator === "exists") parts.push("right: true");
|
|
1185
|
+
else if (node.operator === "not exists") parts.push("right: false");
|
|
1186
|
+
return `{ ${parts.join(", ")} }`;
|
|
1187
|
+
}
|
|
1188
|
+
emitQueryFieldRef(node) {
|
|
1189
|
+
const parts = [];
|
|
1190
|
+
if (node.typeRef) parts.push(`type: () => ${node.typeRef.text}`);
|
|
1191
|
+
parts.push(`field: "${escapeQuotes(node.fieldRef.text)}"`);
|
|
1192
|
+
return `{ ${parts.join(", ")} }`;
|
|
1193
|
+
}
|
|
1194
|
+
emitQueryLiteral(node) {
|
|
1195
|
+
const token = node.valueToken;
|
|
1196
|
+
switch (token.type) {
|
|
1197
|
+
case "text": return `"${escapeQuotes(token.text)}"`;
|
|
1198
|
+
case "number": return token.text;
|
|
1199
|
+
case "regexp": {
|
|
1200
|
+
const match = /^\/(.*)\/[a-z]*$/.exec(token.text);
|
|
1201
|
+
return `"${escapeQuotes(match ? match[1] : token.text)}"`;
|
|
1202
|
+
}
|
|
1203
|
+
case "identifier": {
|
|
1204
|
+
if (token.text === "true" || token.text === "false") return token.text;
|
|
1205
|
+
if (token.text === "null" || token.text === "undefined") return "null";
|
|
1206
|
+
return token.text;
|
|
1207
|
+
}
|
|
1208
|
+
default: return token.text;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1860
1211
|
computeAnnotationValue(node, an) {
|
|
1861
1212
|
const spec = this.doc.resolveAnnotation(an.name);
|
|
1862
1213
|
let targetValue = "true";
|
|
@@ -1868,13 +1219,13 @@ else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value})`);
|
|
|
1868
1219
|
targetValue = "{ ";
|
|
1869
1220
|
let i = 0;
|
|
1870
1221
|
for (const aSpec of spec.arguments) {
|
|
1871
|
-
if (an.args[i]) targetValue += `${wrapProp(aSpec.name)}: ${aSpec
|
|
1222
|
+
if (an.args[i]) targetValue += `${wrapProp(aSpec.name)}: ${this.emitArgValue(aSpec, an.args[i])}${i === length - 1 ? "" : ", "} `;
|
|
1872
1223
|
i++;
|
|
1873
1224
|
}
|
|
1874
1225
|
targetValue += "}";
|
|
1875
1226
|
} else {
|
|
1876
1227
|
const aSpec = spec.arguments[0];
|
|
1877
|
-
if (an.args[0]) targetValue = aSpec
|
|
1228
|
+
if (an.args[0]) targetValue = this.emitArgValue(aSpec, an.args[0]);
|
|
1878
1229
|
else targetValue = "true";
|
|
1879
1230
|
}
|
|
1880
1231
|
} else {
|
|
@@ -2045,8 +1396,8 @@ const tsPlugin = (opts) => {
|
|
|
2045
1396
|
for (const [key, val] of Object.entries(annotations)) {
|
|
2046
1397
|
const multiple = val.multiple;
|
|
2047
1398
|
const typeLine = Array.from(val.types).map((t) => {
|
|
2048
|
-
if (t.type === "object") return `{ ${Object.entries(t.props).map(([k, v]) => `${wrapProp(k)}${v.optional ? "?" : ""}: ${v.type}`).join(", ")} }`;
|
|
2049
|
-
else return t.optional ? `${t.type} | true` : t.type;
|
|
1399
|
+
if (t.type === "object" && "props" in t) return `{ ${Object.entries(t.props).map(([k, v]) => `${wrapProp(k)}${v.optional ? "?" : ""}: ${v.type}`).join(", ")} }`;
|
|
1400
|
+
else return "optional" in t && t.optional ? `${t.type} | true` : t.type;
|
|
2050
1401
|
}).join(" | ");
|
|
2051
1402
|
rendered.push(`${wrapProp(key)}: ${multiple ? "(" : ""}${typeLine}${multiple ? ")[]" : ""}`);
|
|
2052
1403
|
}
|