@hazeljs/data 0.2.0-beta.67 → 0.2.0-beta.69

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.
Files changed (99) hide show
  1. package/README.md +175 -61
  2. package/dist/connectors/connector.interface.d.ts +29 -0
  3. package/dist/connectors/connector.interface.d.ts.map +1 -0
  4. package/dist/connectors/connector.interface.js +6 -0
  5. package/dist/connectors/csv.connector.d.ts +63 -0
  6. package/dist/connectors/csv.connector.d.ts.map +1 -0
  7. package/dist/connectors/csv.connector.js +147 -0
  8. package/dist/connectors/http.connector.d.ts +68 -0
  9. package/dist/connectors/http.connector.d.ts.map +1 -0
  10. package/dist/connectors/http.connector.js +131 -0
  11. package/dist/connectors/index.d.ts +7 -0
  12. package/dist/connectors/index.d.ts.map +1 -0
  13. package/dist/connectors/index.js +12 -0
  14. package/dist/connectors/memory.connector.d.ts +38 -0
  15. package/dist/connectors/memory.connector.d.ts.map +1 -0
  16. package/dist/connectors/memory.connector.js +56 -0
  17. package/dist/connectors/memory.connector.test.d.ts +2 -0
  18. package/dist/connectors/memory.connector.test.d.ts.map +1 -0
  19. package/dist/connectors/memory.connector.test.js +43 -0
  20. package/dist/data.types.d.ts +16 -0
  21. package/dist/data.types.d.ts.map +1 -1
  22. package/dist/decorators/index.d.ts +1 -0
  23. package/dist/decorators/index.d.ts.map +1 -1
  24. package/dist/decorators/index.js +8 -1
  25. package/dist/decorators/pii.decorator.d.ts +59 -0
  26. package/dist/decorators/pii.decorator.d.ts.map +1 -0
  27. package/dist/decorators/pii.decorator.js +197 -0
  28. package/dist/decorators/pii.decorator.test.d.ts +2 -0
  29. package/dist/decorators/pii.decorator.test.d.ts.map +1 -0
  30. package/dist/decorators/pii.decorator.test.js +150 -0
  31. package/dist/decorators/pipeline.decorator.js +1 -1
  32. package/dist/decorators/pipeline.decorator.test.js +8 -0
  33. package/dist/decorators/transform.decorator.d.ts +9 -1
  34. package/dist/decorators/transform.decorator.d.ts.map +1 -1
  35. package/dist/decorators/transform.decorator.js +4 -0
  36. package/dist/decorators/validate.decorator.d.ts +5 -1
  37. package/dist/decorators/validate.decorator.d.ts.map +1 -1
  38. package/dist/decorators/validate.decorator.js +4 -0
  39. package/dist/flink.service.d.ts +30 -0
  40. package/dist/flink.service.d.ts.map +1 -1
  41. package/dist/flink.service.js +50 -2
  42. package/dist/index.d.ts +13 -7
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +36 -8
  45. package/dist/pipelines/etl.service.d.ts +41 -2
  46. package/dist/pipelines/etl.service.d.ts.map +1 -1
  47. package/dist/pipelines/etl.service.js +143 -6
  48. package/dist/pipelines/etl.service.test.js +215 -0
  49. package/dist/pipelines/pipeline.builder.d.ts +86 -13
  50. package/dist/pipelines/pipeline.builder.d.ts.map +1 -1
  51. package/dist/pipelines/pipeline.builder.js +177 -27
  52. package/dist/pipelines/pipeline.builder.test.js +160 -12
  53. package/dist/pipelines/stream.service.test.js +49 -0
  54. package/dist/quality/quality.service.d.ts +67 -5
  55. package/dist/quality/quality.service.d.ts.map +1 -1
  56. package/dist/quality/quality.service.js +259 -20
  57. package/dist/quality/quality.service.test.js +94 -0
  58. package/dist/schema/schema.d.ts +92 -12
  59. package/dist/schema/schema.d.ts.map +1 -1
  60. package/dist/schema/schema.js +395 -83
  61. package/dist/schema/schema.test.js +292 -0
  62. package/dist/streaming/flink/flink.client.d.ts +41 -3
  63. package/dist/streaming/flink/flink.client.d.ts.map +1 -1
  64. package/dist/streaming/flink/flink.client.js +171 -8
  65. package/dist/streaming/flink/flink.client.test.js +2 -2
  66. package/dist/streaming/flink/flink.job.d.ts +2 -1
  67. package/dist/streaming/flink/flink.job.d.ts.map +1 -1
  68. package/dist/streaming/flink/flink.job.js +2 -2
  69. package/dist/streaming/stream.processor.d.ts +56 -2
  70. package/dist/streaming/stream.processor.d.ts.map +1 -1
  71. package/dist/streaming/stream.processor.js +149 -2
  72. package/dist/streaming/stream.processor.test.js +99 -0
  73. package/dist/streaming/stream.processor.windowing.test.d.ts +2 -0
  74. package/dist/streaming/stream.processor.windowing.test.d.ts.map +1 -0
  75. package/dist/streaming/stream.processor.windowing.test.js +69 -0
  76. package/dist/telemetry/telemetry.d.ts +124 -0
  77. package/dist/telemetry/telemetry.d.ts.map +1 -0
  78. package/dist/telemetry/telemetry.js +259 -0
  79. package/dist/telemetry/telemetry.test.d.ts +2 -0
  80. package/dist/telemetry/telemetry.test.d.ts.map +1 -0
  81. package/dist/telemetry/telemetry.test.js +51 -0
  82. package/dist/testing/index.d.ts +12 -0
  83. package/dist/testing/index.d.ts.map +1 -0
  84. package/dist/testing/index.js +18 -0
  85. package/dist/testing/pipeline-test-harness.d.ts +40 -0
  86. package/dist/testing/pipeline-test-harness.d.ts.map +1 -0
  87. package/dist/testing/pipeline-test-harness.js +55 -0
  88. package/dist/testing/pipeline-test-harness.test.d.ts +2 -0
  89. package/dist/testing/pipeline-test-harness.test.d.ts.map +1 -0
  90. package/dist/testing/pipeline-test-harness.test.js +102 -0
  91. package/dist/testing/schema-faker.d.ts +32 -0
  92. package/dist/testing/schema-faker.d.ts.map +1 -0
  93. package/dist/testing/schema-faker.js +91 -0
  94. package/dist/testing/schema-faker.test.d.ts +2 -0
  95. package/dist/testing/schema-faker.test.d.ts.map +1 -0
  96. package/dist/testing/schema-faker.test.js +66 -0
  97. package/dist/transformers/built-in.transformers.test.js +28 -0
  98. package/dist/transformers/transformer.service.test.js +10 -0
  99. package/package.json +2 -2
@@ -1,66 +1,179 @@
1
1
  "use strict";
2
2
  /**
3
- * Schema builder for data validation - fluent API similar to Zod
3
+ * Schema builder for data validation - fluent API
4
+ * Zero runtime dependencies. TypeScript-first with full type inference.
5
+ *
6
+ * Supported types: string, number, boolean, date, object, array, literal, union
7
+ * Modifiers: optional, nullable, default, transform, refine, refineAsync
8
+ * Utilities: toJsonSchema(), Infer<T>, validateAsync()
4
9
  */
5
10
  Object.defineProperty(exports, "__esModule", { value: true });
6
11
  exports.Schema = void 0;
7
- // Schema factory
8
- function createStringSchema(constraints = []) {
12
+ // ─── Core buildSchema helper ──────────────────────────────────────────────────
13
+ function buildSchema(syncValidate, jsonSchemaFn, refinements = [], asyncRefinements = []) {
9
14
  const validate = (value) => {
15
+ const result = syncValidate(value);
16
+ if (!result.success)
17
+ return result;
18
+ for (const r of refinements) {
19
+ if (!r.fn(result.data)) {
20
+ return { success: false, errors: [{ path: '', message: r.message }] };
21
+ }
22
+ }
23
+ return result;
24
+ };
25
+ return {
26
+ _type: undefined,
27
+ validate,
28
+ async validateAsync(value) {
29
+ const result = validate(value);
30
+ if (!result.success)
31
+ return result;
32
+ for (const r of asyncRefinements) {
33
+ const ok = await r.fn(result.data);
34
+ if (!ok) {
35
+ return { success: false, errors: [{ path: '', message: r.message }] };
36
+ }
37
+ }
38
+ return result;
39
+ },
40
+ optional() {
41
+ return buildSchema((v) => v === undefined
42
+ ? { success: true, data: undefined }
43
+ : validate(v), () => {
44
+ const js = jsonSchemaFn();
45
+ return { ...js, _optional: true };
46
+ });
47
+ },
48
+ nullable() {
49
+ return buildSchema((v) => (v === null ? { success: true, data: null } : validate(v)), () => {
50
+ const js = jsonSchemaFn();
51
+ const t = js['type'];
52
+ return { ...js, type: t ? [t, 'null'] : ['null'] };
53
+ });
54
+ },
55
+ default(defaultValue) {
56
+ return buildSchema((v) => (v === undefined ? { success: true, data: defaultValue } : validate(v)), () => ({ ...jsonSchemaFn(), default: defaultValue }));
57
+ },
58
+ transform(fn) {
59
+ return buildSchema((v) => {
60
+ const result = validate(v);
61
+ if (!result.success)
62
+ return result;
63
+ try {
64
+ return { success: true, data: fn(result.data) };
65
+ }
66
+ catch (e) {
67
+ return {
68
+ success: false,
69
+ errors: [{ path: '', message: e instanceof Error ? e.message : 'Transform failed' }],
70
+ };
71
+ }
72
+ }, jsonSchemaFn);
73
+ },
74
+ refine(fn, message) {
75
+ return buildSchema(syncValidate, jsonSchemaFn, [...refinements, { fn, message }], asyncRefinements);
76
+ },
77
+ refineAsync(fn, message) {
78
+ return buildSchema(syncValidate, jsonSchemaFn, refinements, [
79
+ ...asyncRefinements,
80
+ { fn, message },
81
+ ]);
82
+ },
83
+ toJsonSchema() {
84
+ return jsonSchemaFn();
85
+ },
86
+ };
87
+ }
88
+ // ─── String Schema Factory ────────────────────────────────────────────────────
89
+ function createStringSchema(constraints = [], preprocessors = [], refinements = [], asyncRefinements = []) {
90
+ const syncValidate = (value) => {
10
91
  if (typeof value !== 'string') {
11
92
  return { success: false, errors: [{ path: '', message: 'Expected string' }] };
12
93
  }
94
+ let v = value;
95
+ for (const pre of preprocessors)
96
+ v = pre(v);
13
97
  for (const c of constraints) {
14
- const err = c(value);
98
+ const err = c(v);
15
99
  if (err)
16
100
  return { success: false, errors: [{ path: '', message: err }] };
17
101
  }
18
- return { success: true, data: value };
102
+ return { success: true, data: v };
19
103
  };
104
+ const jsonSchemaFn = () => ({ type: 'string' });
105
+ const base = buildSchema(syncValidate, jsonSchemaFn, refinements, asyncRefinements);
106
+ const addConstraint = (c) => createStringSchema([...constraints, c], preprocessors, refinements, asyncRefinements);
20
107
  const schema = {
21
- _type: undefined,
22
- validate,
108
+ ...base,
23
109
  email() {
24
- return createStringSchema([
25
- ...constraints,
26
- (v) => {
27
- const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
28
- return emailRe.test(v) ? null : 'Invalid email';
29
- },
30
- ]);
110
+ return addConstraint((v) => {
111
+ const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
112
+ return re.test(v) ? null : 'Invalid email';
113
+ });
114
+ },
115
+ url() {
116
+ return addConstraint((v) => {
117
+ try {
118
+ new URL(v);
119
+ return null;
120
+ }
121
+ catch {
122
+ return 'Invalid URL';
123
+ }
124
+ });
31
125
  },
32
126
  min(length) {
33
- return createStringSchema([
34
- ...constraints,
35
- (v) => (v.length >= length ? null : `Min length ${length}`),
36
- ]);
127
+ return addConstraint((v) => (v.length >= length ? null : `Min length ${length}`));
37
128
  },
38
129
  max(length) {
39
- return createStringSchema([
40
- ...constraints,
41
- (v) => (v.length <= length ? null : `Max length ${length}`),
42
- ]);
130
+ return addConstraint((v) => (v.length <= length ? null : `Max length ${length}`));
43
131
  },
44
132
  uuid() {
45
- return createStringSchema([
46
- ...constraints,
47
- (v) => {
48
- const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
49
- return uuidRe.test(v) ? null : 'Invalid UUID';
50
- },
51
- ]);
133
+ return addConstraint((v) => {
134
+ const re = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
135
+ return re.test(v) ? null : 'Invalid UUID';
136
+ });
52
137
  },
53
138
  oneOf(values) {
54
- return createStringSchema([
55
- ...constraints,
56
- (v) => values.includes(v) ? null : `Must be one of: ${values.join(', ')}`,
139
+ return addConstraint((v) => values.includes(v) ? null : `Must be one of: ${values.join(', ')}`);
140
+ },
141
+ pattern(regex, message = 'Invalid format') {
142
+ return addConstraint((v) => (regex.test(v) ? null : message));
143
+ },
144
+ required() {
145
+ return addConstraint((v) => (v.length > 0 ? null : 'Value is required'));
146
+ },
147
+ trim() {
148
+ return createStringSchema(constraints, [...preprocessors, (v) => v.trim()], refinements, asyncRefinements);
149
+ },
150
+ refine(fn, message) {
151
+ return createStringSchema(constraints, preprocessors, [...refinements, { fn, message }], asyncRefinements);
152
+ },
153
+ refineAsync(fn, message) {
154
+ return createStringSchema(constraints, preprocessors, refinements, [
155
+ ...asyncRefinements,
156
+ { fn, message },
57
157
  ]);
58
158
  },
159
+ default(value) {
160
+ const next = createStringSchema(constraints, preprocessors, refinements, asyncRefinements);
161
+ const originalValidate = next.validate.bind(next);
162
+ return {
163
+ ...next,
164
+ validate: (v) => v === undefined ? { success: true, data: value } : originalValidate(v),
165
+ toJsonSchema: () => ({ type: 'string', default: value }),
166
+ };
167
+ },
168
+ toJsonSchema() {
169
+ return { type: 'string' };
170
+ },
59
171
  };
60
172
  return schema;
61
173
  }
62
- function createNumberSchema(constraints = []) {
63
- const validate = (value) => {
174
+ // ─── Number Schema Factory ────────────────────────────────────────────────────
175
+ function createNumberSchema(constraints = [], refinements = [], asyncRefinements = []) {
176
+ const syncValidate = (value) => {
64
177
  if (typeof value !== 'number' || Number.isNaN(value)) {
65
178
  return { success: false, errors: [{ path: '', message: 'Expected number' }] };
66
179
  }
@@ -71,45 +184,136 @@ function createNumberSchema(constraints = []) {
71
184
  }
72
185
  return { success: true, data: value };
73
186
  };
74
- return {
75
- _type: undefined,
76
- validate,
187
+ const jsonSchemaFn = () => ({ type: 'number' });
188
+ const base = buildSchema(syncValidate, jsonSchemaFn, refinements, asyncRefinements);
189
+ const addConstraint = (c) => createNumberSchema([...constraints, c], refinements, asyncRefinements);
190
+ const schema = {
191
+ ...base,
77
192
  min(n) {
78
- return createNumberSchema([
79
- ...constraints,
80
- (v) => (v >= n ? null : `Min ${n}`),
81
- ]);
193
+ return addConstraint((v) => (v >= n ? null : `Min ${n}`));
82
194
  },
83
195
  max(n) {
84
- return createNumberSchema([
85
- ...constraints,
86
- (v) => (v <= n ? null : `Max ${n}`),
87
- ]);
196
+ return addConstraint((v) => (v <= n ? null : `Max ${n}`));
197
+ },
198
+ integer() {
199
+ return addConstraint((v) => (Number.isInteger(v) ? null : 'Must be an integer'));
200
+ },
201
+ positive() {
202
+ return addConstraint((v) => (v > 0 ? null : 'Must be positive'));
203
+ },
204
+ negative() {
205
+ return addConstraint((v) => (v < 0 ? null : 'Must be negative'));
206
+ },
207
+ multipleOf(n) {
208
+ return addConstraint((v) => (v % n === 0 ? null : `Must be a multiple of ${n}`));
209
+ },
210
+ refine(fn, message) {
211
+ return createNumberSchema(constraints, [...refinements, { fn, message }], asyncRefinements);
212
+ },
213
+ refineAsync(fn, message) {
214
+ return createNumberSchema(constraints, refinements, [...asyncRefinements, { fn, message }]);
215
+ },
216
+ default(value) {
217
+ const next = createNumberSchema(constraints, refinements, asyncRefinements);
218
+ const originalValidate = next.validate.bind(next);
219
+ return {
220
+ ...next,
221
+ validate: (v) => v === undefined ? { success: true, data: value } : originalValidate(v),
222
+ toJsonSchema: () => ({ type: 'number', default: value }),
223
+ };
224
+ },
225
+ toJsonSchema() {
226
+ return { type: 'number' };
88
227
  },
89
228
  };
229
+ return schema;
90
230
  }
91
- function createDateSchema() {
231
+ // ─── Boolean Schema Factory ───────────────────────────────────────────────────
232
+ function createBooleanSchema() {
233
+ const syncValidate = (value) => {
234
+ if (typeof value !== 'boolean') {
235
+ return { success: false, errors: [{ path: '', message: 'Expected boolean' }] };
236
+ }
237
+ return { success: true, data: value };
238
+ };
239
+ const base = buildSchema(syncValidate, () => ({ type: 'boolean' }));
92
240
  return {
93
- _type: undefined,
94
- validate(value) {
95
- if (value instanceof Date && !Number.isNaN(value.getTime())) {
96
- return { success: true, data: value };
97
- }
98
- if (typeof value === 'string' || typeof value === 'number') {
99
- const d = new Date(value);
100
- if (!Number.isNaN(d.getTime()))
101
- return { success: true, data: d };
241
+ ...base,
242
+ default(value) {
243
+ return {
244
+ ...createBooleanSchema(),
245
+ validate: (v) => v === undefined ? { success: true, data: value } : syncValidate(v),
246
+ toJsonSchema: () => ({ type: 'boolean', default: value }),
247
+ };
248
+ },
249
+ toJsonSchema() {
250
+ return { type: 'boolean' };
251
+ },
252
+ };
253
+ }
254
+ // ─── Date Schema Factory ──────────────────────────────────────────────────────
255
+ function createDateSchema(constraints = []) {
256
+ const syncValidate = (value) => {
257
+ let date;
258
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
259
+ date = value;
260
+ }
261
+ else if (typeof value === 'string' || typeof value === 'number') {
262
+ const d = new Date(value);
263
+ if (Number.isNaN(d.getTime())) {
264
+ return { success: false, errors: [{ path: '', message: 'Expected date' }] };
102
265
  }
266
+ date = d;
267
+ }
268
+ else {
103
269
  return { success: false, errors: [{ path: '', message: 'Expected date' }] };
270
+ }
271
+ for (const c of constraints) {
272
+ const err = c(date);
273
+ if (err)
274
+ return { success: false, errors: [{ path: '', message: err }] };
275
+ }
276
+ return { success: true, data: date };
277
+ };
278
+ const base = buildSchema(syncValidate, () => ({ type: 'string', format: 'date-time' }));
279
+ const addConstraint = (c) => createDateSchema([...constraints, c]);
280
+ return {
281
+ ...base,
282
+ min(date) {
283
+ return addConstraint((v) => (v >= date ? null : `Date must be after ${date.toISOString()}`));
284
+ },
285
+ max(date) {
286
+ return addConstraint((v) => (v <= date ? null : `Date must be before ${date.toISOString()}`));
287
+ },
288
+ default(value) {
289
+ const next = createDateSchema(constraints);
290
+ const originalValidate = next.validate.bind(next);
291
+ return {
292
+ ...next,
293
+ validate: (v) => v === undefined ? { success: true, data: value } : originalValidate(v),
294
+ toJsonSchema: () => ({ type: 'string', format: 'date-time', default: value.toISOString() }),
295
+ };
296
+ },
297
+ toJsonSchema() {
298
+ return { type: 'string', format: 'date-time' };
104
299
  },
105
300
  };
106
301
  }
107
- function createObjectSchema(shape) {
108
- const validate = (value) => {
302
+ function createObjectSchema(shape, strictMode = false) {
303
+ const syncValidate = (value) => {
109
304
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
110
305
  return { success: false, errors: [{ path: '', message: 'Expected object' }] };
111
306
  }
112
307
  const obj = value;
308
+ if (strictMode) {
309
+ const extraKeys = Object.keys(obj).filter((k) => !(k in shape));
310
+ if (extraKeys.length > 0) {
311
+ return {
312
+ success: false,
313
+ errors: [{ path: '', message: `Unknown keys: ${extraKeys.join(', ')}` }],
314
+ };
315
+ }
316
+ }
113
317
  const data = {};
114
318
  const errors = [];
115
319
  for (const [key, fieldSchema] of Object.entries(shape)) {
@@ -126,14 +330,135 @@ function createObjectSchema(shape) {
126
330
  }
127
331
  if (errors.length > 0)
128
332
  return { success: false, errors };
333
+ return { success: true, data: data };
334
+ };
335
+ const jsonSchemaFn = () => ({
336
+ type: 'object',
337
+ properties: Object.fromEntries(Object.entries(shape).map(([k, s]) => [k, s.toJsonSchema()])),
338
+ required: Object.keys(shape),
339
+ additionalProperties: !strictMode,
340
+ });
341
+ const base = buildSchema(syncValidate, jsonSchemaFn);
342
+ const objSchema = {
343
+ ...base,
344
+ shape: shape,
345
+ strict() {
346
+ return createObjectSchema(shape, true);
347
+ },
348
+ pick(keys) {
349
+ const pickedShape = Object.fromEntries(keys.filter((k) => k in shape).map((k) => [k, shape[k]]));
350
+ return createObjectSchema(pickedShape);
351
+ },
352
+ omit(keys) {
353
+ const omittedShape = Object.fromEntries(Object.entries(shape).filter(([k]) => !keys.includes(k)));
354
+ return createObjectSchema(omittedShape);
355
+ },
356
+ extend(extra) {
357
+ return createObjectSchema({ ...shape, ...extra });
358
+ },
359
+ toJsonSchema() {
360
+ return jsonSchemaFn();
361
+ },
362
+ };
363
+ return objSchema;
364
+ }
365
+ // ─── Array Schema Factory ─────────────────────────────────────────────────────
366
+ function createArraySchema(itemSchema, constraints = []) {
367
+ const syncValidate = (value) => {
368
+ if (!Array.isArray(value)) {
369
+ return { success: false, errors: [{ path: '', message: 'Expected array' }] };
370
+ }
371
+ const data = [];
372
+ const errors = [];
373
+ for (let i = 0; i < value.length; i++) {
374
+ const result = itemSchema.validate(value[i]);
375
+ if (result.success) {
376
+ data.push(result.data);
377
+ }
378
+ else {
379
+ errors.push(...result.errors.map((e) => ({
380
+ path: `[${i}]${e.path ? '.' + e.path : ''}`,
381
+ message: e.message,
382
+ })));
383
+ }
384
+ }
385
+ if (errors.length > 0)
386
+ return { success: false, errors };
387
+ for (const c of constraints) {
388
+ const err = c(data);
389
+ if (err)
390
+ return { success: false, errors: [{ path: '', message: err }] };
391
+ }
129
392
  return { success: true, data };
130
393
  };
394
+ const jsonSchemaFn = () => ({
395
+ type: 'array',
396
+ items: itemSchema.toJsonSchema(),
397
+ });
398
+ const base = buildSchema(syncValidate, jsonSchemaFn);
399
+ const addConstraint = (c) => createArraySchema(itemSchema, [...constraints, c]);
131
400
  return {
132
- _type: undefined,
133
- shape,
134
- validate,
401
+ ...base,
402
+ min(length) {
403
+ return addConstraint((v) => v.length >= length ? null : `Array must have at least ${length} items`);
404
+ },
405
+ max(length) {
406
+ return addConstraint((v) => v.length <= length ? null : `Array must have at most ${length} items`);
407
+ },
408
+ nonempty() {
409
+ return addConstraint((v) => (v.length > 0 ? null : 'Array must not be empty'));
410
+ },
411
+ toJsonSchema() {
412
+ return jsonSchemaFn();
413
+ },
135
414
  };
136
415
  }
416
+ // ─── Literal Schema Factory ───────────────────────────────────────────────────
417
+ function createLiteralSchema(literalValue) {
418
+ const syncValidate = (value) => {
419
+ if (value !== literalValue) {
420
+ return {
421
+ success: false,
422
+ errors: [{ path: '', message: `Expected literal ${JSON.stringify(literalValue)}` }],
423
+ };
424
+ }
425
+ return { success: true, data: value };
426
+ };
427
+ const base = buildSchema(syncValidate, () => ({ const: literalValue }));
428
+ return {
429
+ ...base,
430
+ value: literalValue,
431
+ toJsonSchema() {
432
+ return { const: literalValue };
433
+ },
434
+ };
435
+ }
436
+ // ─── Union Schema Factory ─────────────────────────────────────────────────────
437
+ function createUnionSchema(schemas) {
438
+ const syncValidate = (value) => {
439
+ const allErrors = [];
440
+ for (const s of schemas) {
441
+ const result = s.validate(value);
442
+ if (result.success)
443
+ return { success: true, data: result.data };
444
+ allErrors.push(...result.errors);
445
+ }
446
+ return {
447
+ success: false,
448
+ errors: [{ path: '', message: 'Value did not match any schema in union' }],
449
+ };
450
+ };
451
+ const base = buildSchema(syncValidate, () => ({
452
+ oneOf: schemas.map((s) => s.toJsonSchema()),
453
+ }));
454
+ return {
455
+ ...base,
456
+ toJsonSchema() {
457
+ return { oneOf: schemas.map((s) => s.toJsonSchema()) };
458
+ },
459
+ };
460
+ }
461
+ // ─── Schema Namespace ─────────────────────────────────────────────────────────
137
462
  exports.Schema = {
138
463
  string() {
139
464
  return createStringSchema();
@@ -141,6 +466,9 @@ exports.Schema = {
141
466
  number() {
142
467
  return createNumberSchema();
143
468
  },
469
+ boolean() {
470
+ return createBooleanSchema();
471
+ },
144
472
  date() {
145
473
  return createDateSchema();
146
474
  },
@@ -148,28 +476,12 @@ exports.Schema = {
148
476
  return createObjectSchema(shape);
149
477
  },
150
478
  array(itemSchema) {
151
- return {
152
- _type: undefined,
153
- validate(value) {
154
- if (!Array.isArray(value)) {
155
- return { success: false, errors: [{ path: '', message: 'Expected array' }] };
156
- }
157
- const data = [];
158
- const errors = [];
159
- for (let i = 0; i < value.length; i++) {
160
- const result = itemSchema.validate(value[i]);
161
- if (result.success)
162
- data.push(result.data);
163
- else
164
- errors.push(...result.errors.map((e) => ({
165
- path: `[${i}]${e.path ? '.' + e.path : ''}`,
166
- message: e.message,
167
- })));
168
- }
169
- if (errors.length > 0)
170
- return { success: false, errors };
171
- return { success: true, data };
172
- },
173
- };
479
+ return createArraySchema(itemSchema);
480
+ },
481
+ literal(value) {
482
+ return createLiteralSchema(value);
483
+ },
484
+ union(schemas) {
485
+ return createUnionSchema(schemas);
174
486
  },
175
487
  };