@atscript/typescript 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +254 -182
- package/dist/index.cjs +254 -182
- package/dist/index.mjs +255 -183
- package/dist/utils.cjs +93 -55
- package/dist/utils.d.ts +27 -3
- package/dist/utils.mjs +93 -55
- package/package.json +2 -2
- package/skills/atscript-typescript/annotations.md +2 -1
package/dist/utils.cjs
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
//#region packages/typescript/src/traverse.ts
|
|
4
|
-
function forAnnotatedType(def, handlers) {
|
|
5
|
-
switch (def.type.kind) {
|
|
6
|
-
case "": {
|
|
7
|
-
const typed = def;
|
|
8
|
-
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
9
|
-
return handlers.final(typed);
|
|
10
|
-
}
|
|
11
|
-
case "object": return handlers.object(def);
|
|
12
|
-
case "array": return handlers.array(def);
|
|
13
|
-
case "union": return handlers.union(def);
|
|
14
|
-
case "intersection": return handlers.intersection(def);
|
|
15
|
-
case "tuple": return handlers.tuple(def);
|
|
16
|
-
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
//#endregion
|
|
21
3
|
//#region packages/typescript/src/validator.ts
|
|
22
4
|
function _define_property(obj, key, value) {
|
|
23
5
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
@@ -32,28 +14,35 @@ else obj[key] = value;
|
|
|
32
14
|
const regexCache = new Map();
|
|
33
15
|
var Validator = class {
|
|
34
16
|
isLimitExceeded() {
|
|
35
|
-
if (this.stackErrors.length > 0)
|
|
17
|
+
if (this.stackErrors.length > 0) {
|
|
18
|
+
const top = this.stackErrors[this.stackErrors.length - 1];
|
|
19
|
+
return top !== null && top.length >= this.opts.errorLimit;
|
|
20
|
+
}
|
|
36
21
|
return this.errors.length >= this.opts.errorLimit;
|
|
37
22
|
}
|
|
38
23
|
push(name) {
|
|
39
24
|
this.stackPath.push(name);
|
|
40
|
-
this.stackErrors.push(
|
|
25
|
+
this.stackErrors.push(null);
|
|
26
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
41
27
|
}
|
|
42
28
|
pop(saveErrors) {
|
|
43
29
|
this.stackPath.pop();
|
|
44
30
|
const popped = this.stackErrors.pop();
|
|
45
|
-
if (saveErrors && popped
|
|
46
|
-
|
|
47
|
-
});
|
|
31
|
+
if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
|
|
32
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
48
33
|
return popped;
|
|
49
34
|
}
|
|
50
35
|
clear() {
|
|
51
|
-
this.stackErrors[this.stackErrors.length - 1] =
|
|
36
|
+
this.stackErrors[this.stackErrors.length - 1] = null;
|
|
52
37
|
}
|
|
53
38
|
error(message, path, details) {
|
|
54
|
-
|
|
39
|
+
let errors = this.stackErrors[this.stackErrors.length - 1];
|
|
40
|
+
if (!errors) if (this.stackErrors.length > 0) {
|
|
41
|
+
errors = [];
|
|
42
|
+
this.stackErrors[this.stackErrors.length - 1] = errors;
|
|
43
|
+
} else errors = this.errors;
|
|
55
44
|
const error = {
|
|
56
|
-
path: path || this.
|
|
45
|
+
path: path || this.cachedPath,
|
|
57
46
|
message
|
|
58
47
|
};
|
|
59
48
|
if (details?.length) error.details = details;
|
|
@@ -73,9 +62,10 @@ var Validator = class {
|
|
|
73
62
|
* @returns `true` if the value matches the type definition.
|
|
74
63
|
* @throws {ValidatorError} When validation fails and `safe` is not `true`.
|
|
75
64
|
*/ validate(value, safe, context) {
|
|
76
|
-
this.push("");
|
|
77
65
|
this.errors = [];
|
|
78
66
|
this.stackErrors = [];
|
|
67
|
+
this.stackPath = [""];
|
|
68
|
+
this.cachedPath = "";
|
|
79
69
|
this.context = context;
|
|
80
70
|
const passed = this.validateSafe(this.def, value);
|
|
81
71
|
this.pop(!passed);
|
|
@@ -89,7 +79,7 @@ var Validator = class {
|
|
|
89
79
|
validateSafe(def, value) {
|
|
90
80
|
if (this.isLimitExceeded()) return false;
|
|
91
81
|
if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
|
|
92
|
-
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.
|
|
82
|
+
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
|
|
93
83
|
if (def.optional && value === undefined) return true;
|
|
94
84
|
for (const plugin of this.opts.plugins) {
|
|
95
85
|
const result = plugin(this, def, value);
|
|
@@ -98,18 +88,21 @@ var Validator = class {
|
|
|
98
88
|
return this.validateAnnotatedType(def, value);
|
|
99
89
|
}
|
|
100
90
|
get path() {
|
|
101
|
-
return this.
|
|
91
|
+
return this.cachedPath;
|
|
102
92
|
}
|
|
103
93
|
validateAnnotatedType(def, value) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
94
|
+
switch (def.type.kind) {
|
|
95
|
+
case "": {
|
|
96
|
+
if (def.type.designType === "phantom") return true;
|
|
97
|
+
return this.validatePrimitive(def, value);
|
|
98
|
+
}
|
|
99
|
+
case "object": return this.validateObject(def, value);
|
|
100
|
+
case "array": return this.validateArray(def, value);
|
|
101
|
+
case "union": return this.validateUnion(def, value);
|
|
102
|
+
case "intersection": return this.validateIntersection(def, value);
|
|
103
|
+
case "tuple": return this.validateTuple(def, value);
|
|
104
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
105
|
+
}
|
|
113
106
|
}
|
|
114
107
|
validateUnion(def, value) {
|
|
115
108
|
let i = 0;
|
|
@@ -173,6 +166,30 @@ var Validator = class {
|
|
|
173
166
|
return false;
|
|
174
167
|
}
|
|
175
168
|
}
|
|
169
|
+
const uniqueItems = def.metadata.get("expect.array.uniqueItems");
|
|
170
|
+
if (uniqueItems) {
|
|
171
|
+
const separator = "▼↩";
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const keyProps = new Set();
|
|
174
|
+
if (def.type.of.type.kind === "object") {
|
|
175
|
+
for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
|
|
176
|
+
}
|
|
177
|
+
for (let idx = 0; idx < value.length; idx++) {
|
|
178
|
+
const item = value[idx];
|
|
179
|
+
let key;
|
|
180
|
+
if (keyProps.size > 0) {
|
|
181
|
+
key = "";
|
|
182
|
+
for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
|
|
183
|
+
} else key = JSON.stringify(item);
|
|
184
|
+
if (seen.has(key)) {
|
|
185
|
+
this.push(String(idx));
|
|
186
|
+
this.error(uniqueItems.message || "Duplicate items are not allowed");
|
|
187
|
+
this.pop(true);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
seen.add(key);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
176
193
|
let i = 0;
|
|
177
194
|
let passed = true;
|
|
178
195
|
for (const item of value) {
|
|
@@ -194,21 +211,20 @@ var Validator = class {
|
|
|
194
211
|
let passed = true;
|
|
195
212
|
const valueKeys = new Set(Object.keys(value));
|
|
196
213
|
const typeKeys = new Set();
|
|
197
|
-
|
|
214
|
+
let skipList;
|
|
198
215
|
if (this.opts.skipList) {
|
|
199
|
-
const path = this.stackPath.length > 1 ? `${this.
|
|
200
|
-
this.opts.skipList.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
});
|
|
216
|
+
const path = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
|
|
217
|
+
for (const item of this.opts.skipList) if (item.startsWith(path)) {
|
|
218
|
+
const key = item.slice(path.length);
|
|
219
|
+
if (!skipList) skipList = new Set();
|
|
220
|
+
skipList.add(key);
|
|
221
|
+
valueKeys.delete(key);
|
|
222
|
+
}
|
|
207
223
|
}
|
|
208
224
|
let partialFunctionMatched = false;
|
|
209
|
-
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.
|
|
225
|
+
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
|
|
210
226
|
for (const [key, item] of def.type.props.entries()) {
|
|
211
|
-
if (skipList.has(key) || isPhantomType(item)) continue;
|
|
227
|
+
if (skipList && skipList.has(key) || isPhantomType(item)) continue;
|
|
212
228
|
typeKeys.add(key);
|
|
213
229
|
if (value[key] === undefined) {
|
|
214
230
|
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
@@ -229,19 +245,21 @@ else {
|
|
|
229
245
|
def: propDef
|
|
230
246
|
});
|
|
231
247
|
if (matched.length > 0) {
|
|
248
|
+
this.push(key);
|
|
232
249
|
let keyPassed = false;
|
|
233
|
-
for (const { def:
|
|
234
|
-
this.
|
|
235
|
-
|
|
236
|
-
|
|
250
|
+
for (const { def: propDef } of matched) {
|
|
251
|
+
if (this.validateSafe(propDef, value[key])) {
|
|
252
|
+
keyPassed = true;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
this.clear();
|
|
237
256
|
}
|
|
238
257
|
if (!keyPassed) {
|
|
239
|
-
this.push(key);
|
|
240
258
|
this.validateSafe(matched[0].def, value[key]);
|
|
241
259
|
this.pop(true);
|
|
242
260
|
passed = false;
|
|
243
261
|
if (this.isLimitExceeded()) return false;
|
|
244
|
-
}
|
|
262
|
+
} else this.pop(false);
|
|
245
263
|
} else if (this.opts.unknownProps !== "ignore") {
|
|
246
264
|
if (this.opts.unknownProps === "error") {
|
|
247
265
|
this.push(key);
|
|
@@ -394,11 +412,13 @@ else {
|
|
|
394
412
|
/** Validation errors collected during the last {@link validate} call. */ _define_property(this, "errors", void 0);
|
|
395
413
|
_define_property(this, "stackErrors", void 0);
|
|
396
414
|
_define_property(this, "stackPath", void 0);
|
|
415
|
+
_define_property(this, "cachedPath", void 0);
|
|
397
416
|
_define_property(this, "context", void 0);
|
|
398
417
|
this.def = def;
|
|
399
418
|
this.errors = [];
|
|
400
419
|
this.stackErrors = [];
|
|
401
420
|
this.stackPath = [];
|
|
421
|
+
this.cachedPath = "";
|
|
402
422
|
this.opts = {
|
|
403
423
|
partial: false,
|
|
404
424
|
unknownProps: "error",
|
|
@@ -612,6 +632,24 @@ function isAnnotatedTypeOfPrimitive(t) {
|
|
|
612
632
|
return false;
|
|
613
633
|
}
|
|
614
634
|
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region packages/typescript/src/traverse.ts
|
|
637
|
+
function forAnnotatedType(def, handlers) {
|
|
638
|
+
switch (def.type.kind) {
|
|
639
|
+
case "": {
|
|
640
|
+
const typed = def;
|
|
641
|
+
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
642
|
+
return handlers.final(typed);
|
|
643
|
+
}
|
|
644
|
+
case "object": return handlers.object(def);
|
|
645
|
+
case "array": return handlers.array(def);
|
|
646
|
+
case "union": return handlers.union(def);
|
|
647
|
+
case "intersection": return handlers.intersection(def);
|
|
648
|
+
case "tuple": return handlers.tuple(def);
|
|
649
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
615
653
|
//#endregion
|
|
616
654
|
//#region packages/typescript/src/json-schema.ts
|
|
617
655
|
/**
|
package/dist/utils.d.ts
CHANGED
|
@@ -54,12 +54,13 @@ declare class Validator<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedTyp
|
|
|
54
54
|
constructor(def: T, opts?: Partial<TValidatorOptions>);
|
|
55
55
|
/** Validation errors collected during the last {@link validate} call. */
|
|
56
56
|
errors: TError[];
|
|
57
|
-
protected stackErrors: TError[]
|
|
57
|
+
protected stackErrors: Array<TError[] | null>;
|
|
58
58
|
protected stackPath: string[];
|
|
59
|
+
protected cachedPath: string;
|
|
59
60
|
protected context: unknown;
|
|
60
61
|
protected isLimitExceeded(): boolean;
|
|
61
62
|
protected push(name: string): void;
|
|
62
|
-
protected pop(saveErrors: boolean): TError[] | undefined;
|
|
63
|
+
protected pop(saveErrors: boolean): TError[] | null | undefined;
|
|
63
64
|
protected clear(): void;
|
|
64
65
|
protected error(message: string, path?: string, details?: TError[]): void;
|
|
65
66
|
protected throw(): void;
|
|
@@ -535,5 +536,28 @@ declare function serializeAnnotatedType(type: TAtscriptAnnotatedType, options?:
|
|
|
535
536
|
*/
|
|
536
537
|
declare function deserializeAnnotatedType(data: TSerializedAnnotatedType): TAtscriptAnnotatedType;
|
|
537
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Extracts the flat dot-notation type map from an Atscript annotated type.
|
|
541
|
+
* If the type has a `__flat` static property (emitted for `@db.table` interfaces),
|
|
542
|
+
* returns that flat map. Otherwise falls back to `TAtscriptDataType<T>`.
|
|
543
|
+
*
|
|
544
|
+
* Use this for type-safe filters and selects with dot-notation field paths.
|
|
545
|
+
*/
|
|
546
|
+
type FlatOf<T> = T extends {
|
|
547
|
+
__flat: infer F;
|
|
548
|
+
} ? F : T extends TAtscriptAnnotatedType ? TAtscriptDataType<T> : unknown;
|
|
549
|
+
/**
|
|
550
|
+
* Extracts the primary key type from an Atscript annotated type.
|
|
551
|
+
* If the type has a `__pk` static property (emitted for `@db.table` interfaces
|
|
552
|
+
* with `@meta.id` fields), returns that type. Otherwise falls back to `unknown`.
|
|
553
|
+
*
|
|
554
|
+
* - Single `@meta.id` field → scalar type (e.g., `string`, `number`)
|
|
555
|
+
* - Multiple `@meta.id` fields → object type (e.g., `{ userId: string; orderId: number }`)
|
|
556
|
+
* - No `@meta.id` fields → `unknown`
|
|
557
|
+
*/
|
|
558
|
+
type PrimaryKeyOf<T> = T extends {
|
|
559
|
+
__pk: infer PK;
|
|
560
|
+
} ? PK : unknown;
|
|
561
|
+
|
|
538
562
|
export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
|
|
539
|
-
export type { InferDataType, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TJsonSchema, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
|
|
563
|
+
export type { FlatOf, InferDataType, PrimaryKeyOf, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TJsonSchema, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
|
package/dist/utils.mjs
CHANGED
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
//#region packages/typescript/src/traverse.ts
|
|
3
|
-
function forAnnotatedType(def, handlers) {
|
|
4
|
-
switch (def.type.kind) {
|
|
5
|
-
case "": {
|
|
6
|
-
const typed = def;
|
|
7
|
-
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
8
|
-
return handlers.final(typed);
|
|
9
|
-
}
|
|
10
|
-
case "object": return handlers.object(def);
|
|
11
|
-
case "array": return handlers.array(def);
|
|
12
|
-
case "union": return handlers.union(def);
|
|
13
|
-
case "intersection": return handlers.intersection(def);
|
|
14
|
-
case "tuple": return handlers.tuple(def);
|
|
15
|
-
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
//#endregion
|
|
20
2
|
//#region packages/typescript/src/validator.ts
|
|
21
3
|
function _define_property(obj, key, value) {
|
|
22
4
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
@@ -31,28 +13,35 @@ else obj[key] = value;
|
|
|
31
13
|
const regexCache = new Map();
|
|
32
14
|
var Validator = class {
|
|
33
15
|
isLimitExceeded() {
|
|
34
|
-
if (this.stackErrors.length > 0)
|
|
16
|
+
if (this.stackErrors.length > 0) {
|
|
17
|
+
const top = this.stackErrors[this.stackErrors.length - 1];
|
|
18
|
+
return top !== null && top.length >= this.opts.errorLimit;
|
|
19
|
+
}
|
|
35
20
|
return this.errors.length >= this.opts.errorLimit;
|
|
36
21
|
}
|
|
37
22
|
push(name) {
|
|
38
23
|
this.stackPath.push(name);
|
|
39
|
-
this.stackErrors.push(
|
|
24
|
+
this.stackErrors.push(null);
|
|
25
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
40
26
|
}
|
|
41
27
|
pop(saveErrors) {
|
|
42
28
|
this.stackPath.pop();
|
|
43
29
|
const popped = this.stackErrors.pop();
|
|
44
|
-
if (saveErrors && popped
|
|
45
|
-
|
|
46
|
-
});
|
|
30
|
+
if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
|
|
31
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
47
32
|
return popped;
|
|
48
33
|
}
|
|
49
34
|
clear() {
|
|
50
|
-
this.stackErrors[this.stackErrors.length - 1] =
|
|
35
|
+
this.stackErrors[this.stackErrors.length - 1] = null;
|
|
51
36
|
}
|
|
52
37
|
error(message, path, details) {
|
|
53
|
-
|
|
38
|
+
let errors = this.stackErrors[this.stackErrors.length - 1];
|
|
39
|
+
if (!errors) if (this.stackErrors.length > 0) {
|
|
40
|
+
errors = [];
|
|
41
|
+
this.stackErrors[this.stackErrors.length - 1] = errors;
|
|
42
|
+
} else errors = this.errors;
|
|
54
43
|
const error = {
|
|
55
|
-
path: path || this.
|
|
44
|
+
path: path || this.cachedPath,
|
|
56
45
|
message
|
|
57
46
|
};
|
|
58
47
|
if (details?.length) error.details = details;
|
|
@@ -72,9 +61,10 @@ var Validator = class {
|
|
|
72
61
|
* @returns `true` if the value matches the type definition.
|
|
73
62
|
* @throws {ValidatorError} When validation fails and `safe` is not `true`.
|
|
74
63
|
*/ validate(value, safe, context) {
|
|
75
|
-
this.push("");
|
|
76
64
|
this.errors = [];
|
|
77
65
|
this.stackErrors = [];
|
|
66
|
+
this.stackPath = [""];
|
|
67
|
+
this.cachedPath = "";
|
|
78
68
|
this.context = context;
|
|
79
69
|
const passed = this.validateSafe(this.def, value);
|
|
80
70
|
this.pop(!passed);
|
|
@@ -88,7 +78,7 @@ var Validator = class {
|
|
|
88
78
|
validateSafe(def, value) {
|
|
89
79
|
if (this.isLimitExceeded()) return false;
|
|
90
80
|
if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
|
|
91
|
-
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.
|
|
81
|
+
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
|
|
92
82
|
if (def.optional && value === undefined) return true;
|
|
93
83
|
for (const plugin of this.opts.plugins) {
|
|
94
84
|
const result = plugin(this, def, value);
|
|
@@ -97,18 +87,21 @@ var Validator = class {
|
|
|
97
87
|
return this.validateAnnotatedType(def, value);
|
|
98
88
|
}
|
|
99
89
|
get path() {
|
|
100
|
-
return this.
|
|
90
|
+
return this.cachedPath;
|
|
101
91
|
}
|
|
102
92
|
validateAnnotatedType(def, value) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
93
|
+
switch (def.type.kind) {
|
|
94
|
+
case "": {
|
|
95
|
+
if (def.type.designType === "phantom") return true;
|
|
96
|
+
return this.validatePrimitive(def, value);
|
|
97
|
+
}
|
|
98
|
+
case "object": return this.validateObject(def, value);
|
|
99
|
+
case "array": return this.validateArray(def, value);
|
|
100
|
+
case "union": return this.validateUnion(def, value);
|
|
101
|
+
case "intersection": return this.validateIntersection(def, value);
|
|
102
|
+
case "tuple": return this.validateTuple(def, value);
|
|
103
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
104
|
+
}
|
|
112
105
|
}
|
|
113
106
|
validateUnion(def, value) {
|
|
114
107
|
let i = 0;
|
|
@@ -172,6 +165,30 @@ var Validator = class {
|
|
|
172
165
|
return false;
|
|
173
166
|
}
|
|
174
167
|
}
|
|
168
|
+
const uniqueItems = def.metadata.get("expect.array.uniqueItems");
|
|
169
|
+
if (uniqueItems) {
|
|
170
|
+
const separator = "▼↩";
|
|
171
|
+
const seen = new Set();
|
|
172
|
+
const keyProps = new Set();
|
|
173
|
+
if (def.type.of.type.kind === "object") {
|
|
174
|
+
for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
|
|
175
|
+
}
|
|
176
|
+
for (let idx = 0; idx < value.length; idx++) {
|
|
177
|
+
const item = value[idx];
|
|
178
|
+
let key;
|
|
179
|
+
if (keyProps.size > 0) {
|
|
180
|
+
key = "";
|
|
181
|
+
for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
|
|
182
|
+
} else key = JSON.stringify(item);
|
|
183
|
+
if (seen.has(key)) {
|
|
184
|
+
this.push(String(idx));
|
|
185
|
+
this.error(uniqueItems.message || "Duplicate items are not allowed");
|
|
186
|
+
this.pop(true);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
seen.add(key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
175
192
|
let i = 0;
|
|
176
193
|
let passed = true;
|
|
177
194
|
for (const item of value) {
|
|
@@ -193,21 +210,20 @@ var Validator = class {
|
|
|
193
210
|
let passed = true;
|
|
194
211
|
const valueKeys = new Set(Object.keys(value));
|
|
195
212
|
const typeKeys = new Set();
|
|
196
|
-
|
|
213
|
+
let skipList;
|
|
197
214
|
if (this.opts.skipList) {
|
|
198
|
-
const path = this.stackPath.length > 1 ? `${this.
|
|
199
|
-
this.opts.skipList.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
});
|
|
215
|
+
const path = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
|
|
216
|
+
for (const item of this.opts.skipList) if (item.startsWith(path)) {
|
|
217
|
+
const key = item.slice(path.length);
|
|
218
|
+
if (!skipList) skipList = new Set();
|
|
219
|
+
skipList.add(key);
|
|
220
|
+
valueKeys.delete(key);
|
|
221
|
+
}
|
|
206
222
|
}
|
|
207
223
|
let partialFunctionMatched = false;
|
|
208
|
-
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.
|
|
224
|
+
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
|
|
209
225
|
for (const [key, item] of def.type.props.entries()) {
|
|
210
|
-
if (skipList.has(key) || isPhantomType(item)) continue;
|
|
226
|
+
if (skipList && skipList.has(key) || isPhantomType(item)) continue;
|
|
211
227
|
typeKeys.add(key);
|
|
212
228
|
if (value[key] === undefined) {
|
|
213
229
|
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
@@ -228,19 +244,21 @@ else {
|
|
|
228
244
|
def: propDef
|
|
229
245
|
});
|
|
230
246
|
if (matched.length > 0) {
|
|
247
|
+
this.push(key);
|
|
231
248
|
let keyPassed = false;
|
|
232
|
-
for (const { def:
|
|
233
|
-
this.
|
|
234
|
-
|
|
235
|
-
|
|
249
|
+
for (const { def: propDef } of matched) {
|
|
250
|
+
if (this.validateSafe(propDef, value[key])) {
|
|
251
|
+
keyPassed = true;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
this.clear();
|
|
236
255
|
}
|
|
237
256
|
if (!keyPassed) {
|
|
238
|
-
this.push(key);
|
|
239
257
|
this.validateSafe(matched[0].def, value[key]);
|
|
240
258
|
this.pop(true);
|
|
241
259
|
passed = false;
|
|
242
260
|
if (this.isLimitExceeded()) return false;
|
|
243
|
-
}
|
|
261
|
+
} else this.pop(false);
|
|
244
262
|
} else if (this.opts.unknownProps !== "ignore") {
|
|
245
263
|
if (this.opts.unknownProps === "error") {
|
|
246
264
|
this.push(key);
|
|
@@ -393,11 +411,13 @@ else {
|
|
|
393
411
|
/** Validation errors collected during the last {@link validate} call. */ _define_property(this, "errors", void 0);
|
|
394
412
|
_define_property(this, "stackErrors", void 0);
|
|
395
413
|
_define_property(this, "stackPath", void 0);
|
|
414
|
+
_define_property(this, "cachedPath", void 0);
|
|
396
415
|
_define_property(this, "context", void 0);
|
|
397
416
|
this.def = def;
|
|
398
417
|
this.errors = [];
|
|
399
418
|
this.stackErrors = [];
|
|
400
419
|
this.stackPath = [];
|
|
420
|
+
this.cachedPath = "";
|
|
401
421
|
this.opts = {
|
|
402
422
|
partial: false,
|
|
403
423
|
unknownProps: "error",
|
|
@@ -611,6 +631,24 @@ function isAnnotatedTypeOfPrimitive(t) {
|
|
|
611
631
|
return false;
|
|
612
632
|
}
|
|
613
633
|
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region packages/typescript/src/traverse.ts
|
|
636
|
+
function forAnnotatedType(def, handlers) {
|
|
637
|
+
switch (def.type.kind) {
|
|
638
|
+
case "": {
|
|
639
|
+
const typed = def;
|
|
640
|
+
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
641
|
+
return handlers.final(typed);
|
|
642
|
+
}
|
|
643
|
+
case "object": return handlers.object(def);
|
|
644
|
+
case "array": return handlers.array(def);
|
|
645
|
+
case "union": return handlers.union(def);
|
|
646
|
+
case "intersection": return handlers.intersection(def);
|
|
647
|
+
case "tuple": return handlers.tuple(def);
|
|
648
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
614
652
|
//#endregion
|
|
615
653
|
//#region packages/typescript/src/json-schema.ts
|
|
616
654
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/typescript",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "Atscript: typescript-gen support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotations",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"vitest": "3.2.4"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@atscript/core": "^0.1.
|
|
67
|
+
"@atscript/core": "^0.1.32"
|
|
68
68
|
},
|
|
69
69
|
"build": [
|
|
70
70
|
{},
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| `@meta.required` | `message?: string` | Required field. Strings: non-whitespace. Booleans: must be `true` |
|
|
18
18
|
| `@meta.default` | `value: string` | Default value (strings as-is, others parsed as JSON) |
|
|
19
19
|
| `@meta.example` | `value: string` | Example value (strings as-is, others parsed as JSON) |
|
|
20
|
-
| `@expect.array.key` |
|
|
20
|
+
| `@expect.array.key` | `message?: string` | Mark field as key inside array (string/number only, non-optional). Multiple = composite key |
|
|
21
21
|
|
|
22
22
|
### `@expect.*` — Validation Constraints
|
|
23
23
|
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
| `@expect.max` | `maxValue: number`, `message?: string` | number | Maximum value |
|
|
30
30
|
| `@expect.int` | _(none)_ | number | Must be integer |
|
|
31
31
|
| `@expect.pattern` | `pattern: string`, `flags?: string`, `message?: string` | string | Regex validation. **Multiple allowed** (all must pass) |
|
|
32
|
+
| `@expect.array.uniqueItems` | `message?: string` | array | No duplicate items (by key fields or deep equality) |
|
|
32
33
|
|
|
33
34
|
### `@ui.*` — UI / Presentation Hints
|
|
34
35
|
|