@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.
- package/README.md +175 -61
- package/dist/connectors/connector.interface.d.ts +29 -0
- package/dist/connectors/connector.interface.d.ts.map +1 -0
- package/dist/connectors/connector.interface.js +6 -0
- package/dist/connectors/csv.connector.d.ts +63 -0
- package/dist/connectors/csv.connector.d.ts.map +1 -0
- package/dist/connectors/csv.connector.js +147 -0
- package/dist/connectors/http.connector.d.ts +68 -0
- package/dist/connectors/http.connector.d.ts.map +1 -0
- package/dist/connectors/http.connector.js +131 -0
- package/dist/connectors/index.d.ts +7 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +12 -0
- package/dist/connectors/memory.connector.d.ts +38 -0
- package/dist/connectors/memory.connector.d.ts.map +1 -0
- package/dist/connectors/memory.connector.js +56 -0
- package/dist/connectors/memory.connector.test.d.ts +2 -0
- package/dist/connectors/memory.connector.test.d.ts.map +1 -0
- package/dist/connectors/memory.connector.test.js +43 -0
- package/dist/data.types.d.ts +16 -0
- package/dist/data.types.d.ts.map +1 -1
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +8 -1
- package/dist/decorators/pii.decorator.d.ts +59 -0
- package/dist/decorators/pii.decorator.d.ts.map +1 -0
- package/dist/decorators/pii.decorator.js +197 -0
- package/dist/decorators/pii.decorator.test.d.ts +2 -0
- package/dist/decorators/pii.decorator.test.d.ts.map +1 -0
- package/dist/decorators/pii.decorator.test.js +150 -0
- package/dist/decorators/pipeline.decorator.js +1 -1
- package/dist/decorators/pipeline.decorator.test.js +8 -0
- package/dist/decorators/transform.decorator.d.ts +9 -1
- package/dist/decorators/transform.decorator.d.ts.map +1 -1
- package/dist/decorators/transform.decorator.js +4 -0
- package/dist/decorators/validate.decorator.d.ts +5 -1
- package/dist/decorators/validate.decorator.d.ts.map +1 -1
- package/dist/decorators/validate.decorator.js +4 -0
- package/dist/flink.service.d.ts +30 -0
- package/dist/flink.service.d.ts.map +1 -1
- package/dist/flink.service.js +50 -2
- package/dist/index.d.ts +13 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -8
- package/dist/pipelines/etl.service.d.ts +41 -2
- package/dist/pipelines/etl.service.d.ts.map +1 -1
- package/dist/pipelines/etl.service.js +143 -6
- package/dist/pipelines/etl.service.test.js +215 -0
- package/dist/pipelines/pipeline.builder.d.ts +86 -13
- package/dist/pipelines/pipeline.builder.d.ts.map +1 -1
- package/dist/pipelines/pipeline.builder.js +177 -27
- package/dist/pipelines/pipeline.builder.test.js +160 -12
- package/dist/pipelines/stream.service.test.js +49 -0
- package/dist/quality/quality.service.d.ts +67 -5
- package/dist/quality/quality.service.d.ts.map +1 -1
- package/dist/quality/quality.service.js +259 -20
- package/dist/quality/quality.service.test.js +94 -0
- package/dist/schema/schema.d.ts +92 -12
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +395 -83
- package/dist/schema/schema.test.js +292 -0
- package/dist/streaming/flink/flink.client.d.ts +41 -3
- package/dist/streaming/flink/flink.client.d.ts.map +1 -1
- package/dist/streaming/flink/flink.client.js +171 -8
- package/dist/streaming/flink/flink.client.test.js +2 -2
- package/dist/streaming/flink/flink.job.d.ts +2 -1
- package/dist/streaming/flink/flink.job.d.ts.map +1 -1
- package/dist/streaming/flink/flink.job.js +2 -2
- package/dist/streaming/stream.processor.d.ts +56 -2
- package/dist/streaming/stream.processor.d.ts.map +1 -1
- package/dist/streaming/stream.processor.js +149 -2
- package/dist/streaming/stream.processor.test.js +99 -0
- package/dist/streaming/stream.processor.windowing.test.d.ts +2 -0
- package/dist/streaming/stream.processor.windowing.test.d.ts.map +1 -0
- package/dist/streaming/stream.processor.windowing.test.js +69 -0
- package/dist/telemetry/telemetry.d.ts +124 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +259 -0
- package/dist/telemetry/telemetry.test.d.ts +2 -0
- package/dist/telemetry/telemetry.test.d.ts.map +1 -0
- package/dist/telemetry/telemetry.test.js +51 -0
- package/dist/testing/index.d.ts +12 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +18 -0
- package/dist/testing/pipeline-test-harness.d.ts +40 -0
- package/dist/testing/pipeline-test-harness.d.ts.map +1 -0
- package/dist/testing/pipeline-test-harness.js +55 -0
- package/dist/testing/pipeline-test-harness.test.d.ts +2 -0
- package/dist/testing/pipeline-test-harness.test.d.ts.map +1 -0
- package/dist/testing/pipeline-test-harness.test.js +102 -0
- package/dist/testing/schema-faker.d.ts +32 -0
- package/dist/testing/schema-faker.d.ts.map +1 -0
- package/dist/testing/schema-faker.js +91 -0
- package/dist/testing/schema-faker.test.d.ts +2 -0
- package/dist/testing/schema-faker.test.d.ts.map +1 -0
- package/dist/testing/schema-faker.test.js +66 -0
- package/dist/transformers/built-in.transformers.test.js +28 -0
- package/dist/transformers/transformer.service.test.js +10 -0
- package/package.json +2 -2
package/dist/schema/schema.js
CHANGED
|
@@ -1,66 +1,179 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Schema builder for data validation - fluent API
|
|
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
|
-
//
|
|
8
|
-
function
|
|
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(
|
|
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:
|
|
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
|
-
|
|
22
|
-
validate,
|
|
108
|
+
...base,
|
|
23
109
|
email() {
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
(v)
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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
|
|
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
|
|
46
|
-
|
|
47
|
-
(v)
|
|
48
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
};
|