@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/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) return this.stackErrors[this.stackErrors.length - 1].length >= this.opts.errorLimit;
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?.length) popped.forEach((error) => {
46
- this.error(error.message, error.path, error.details);
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
- const errors = this.stackErrors[this.stackErrors.length - 1] || this.errors;
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.path,
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.path);
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.stackPath.slice(1).join(".");
91
+ return this.cachedPath;
102
92
  }
103
93
  validateAnnotatedType(def, value) {
104
- return forAnnotatedType(def, {
105
- final: (d) => this.validatePrimitive(d, value),
106
- phantom: () => true,
107
- object: (d) => this.validateObject(d, value),
108
- array: (d) => this.validateArray(d, value),
109
- union: (d) => this.validateUnion(d, value),
110
- intersection: (d) => this.validateIntersection(d, value),
111
- tuple: (d) => this.validateTuple(d, value)
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
- const skipList = new Set();
214
+ let skipList;
198
215
  if (this.opts.skipList) {
199
- const path = this.stackPath.length > 1 ? `${this.path}.` : "";
200
- this.opts.skipList.forEach((item) => {
201
- if (item.startsWith(path)) {
202
- const key = item.slice(path.length);
203
- skipList.add(key);
204
- valueKeys.delete(key);
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.path);
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: def$1 } of matched) if (this.validateSafe(def$1, value[key])) {
234
- this.pop(false);
235
- keyPassed = true;
236
- break;
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) return this.stackErrors[this.stackErrors.length - 1].length >= this.opts.errorLimit;
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?.length) popped.forEach((error) => {
45
- this.error(error.message, error.path, error.details);
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
- const errors = this.stackErrors[this.stackErrors.length - 1] || this.errors;
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.path,
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.path);
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.stackPath.slice(1).join(".");
90
+ return this.cachedPath;
101
91
  }
102
92
  validateAnnotatedType(def, value) {
103
- return forAnnotatedType(def, {
104
- final: (d) => this.validatePrimitive(d, value),
105
- phantom: () => true,
106
- object: (d) => this.validateObject(d, value),
107
- array: (d) => this.validateArray(d, value),
108
- union: (d) => this.validateUnion(d, value),
109
- intersection: (d) => this.validateIntersection(d, value),
110
- tuple: (d) => this.validateTuple(d, value)
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
- const skipList = new Set();
213
+ let skipList;
197
214
  if (this.opts.skipList) {
198
- const path = this.stackPath.length > 1 ? `${this.path}.` : "";
199
- this.opts.skipList.forEach((item) => {
200
- if (item.startsWith(path)) {
201
- const key = item.slice(path.length);
202
- skipList.add(key);
203
- valueKeys.delete(key);
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.path);
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: def$1 } of matched) if (this.validateSafe(def$1, value[key])) {
233
- this.pop(false);
234
- keyPassed = true;
235
- break;
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.30",
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.30"
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` | _(none)_ | Mark field as key inside array (string/number types only) |
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