@hazeljs/data 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +192 -0
- package/README.md +308 -0
- 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.module.d.ts +30 -0
- package/dist/data.module.d.ts.map +1 -0
- package/dist/data.module.js +120 -0
- package/dist/data.module.test.d.ts +2 -0
- package/dist/data.module.test.d.ts.map +1 -0
- package/dist/data.module.test.js +28 -0
- package/dist/data.types.d.ts +67 -0
- package/dist/data.types.d.ts.map +1 -0
- package/dist/data.types.js +5 -0
- package/dist/decorators/index.d.ts +6 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +24 -0
- 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.d.ts +22 -0
- package/dist/decorators/pipeline.decorator.d.ts.map +1 -0
- package/dist/decorators/pipeline.decorator.js +42 -0
- package/dist/decorators/pipeline.decorator.test.d.ts +2 -0
- package/dist/decorators/pipeline.decorator.test.d.ts.map +1 -0
- package/dist/decorators/pipeline.decorator.test.js +104 -0
- package/dist/decorators/stream.decorator.d.ts +31 -0
- package/dist/decorators/stream.decorator.d.ts.map +1 -0
- package/dist/decorators/stream.decorator.js +48 -0
- package/dist/decorators/transform.decorator.d.ts +29 -0
- package/dist/decorators/transform.decorator.d.ts.map +1 -0
- package/dist/decorators/transform.decorator.js +41 -0
- package/dist/decorators/validate.decorator.d.ts +34 -0
- package/dist/decorators/validate.decorator.d.ts.map +1 -0
- package/dist/decorators/validate.decorator.js +49 -0
- package/dist/flink.service.d.ts +80 -0
- package/dist/flink.service.d.ts.map +1 -0
- package/dist/flink.service.js +134 -0
- package/dist/flink.service.test.d.ts +2 -0
- package/dist/flink.service.test.d.ts.map +1 -0
- package/dist/flink.service.test.js +60 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +96 -0
- package/dist/pipelines/etl.service.d.ts +59 -0
- package/dist/pipelines/etl.service.d.ts.map +1 -0
- package/dist/pipelines/etl.service.js +223 -0
- package/dist/pipelines/etl.service.test.d.ts +2 -0
- package/dist/pipelines/etl.service.test.d.ts.map +1 -0
- package/dist/pipelines/etl.service.test.js +319 -0
- package/dist/pipelines/pipeline.base.d.ts +24 -0
- package/dist/pipelines/pipeline.base.d.ts.map +1 -0
- package/dist/pipelines/pipeline.base.js +29 -0
- package/dist/pipelines/pipeline.base.test.d.ts +2 -0
- package/dist/pipelines/pipeline.base.test.d.ts.map +1 -0
- package/dist/pipelines/pipeline.base.test.js +38 -0
- package/dist/pipelines/pipeline.builder.d.ts +95 -0
- package/dist/pipelines/pipeline.builder.d.ts.map +1 -0
- package/dist/pipelines/pipeline.builder.js +212 -0
- package/dist/pipelines/pipeline.builder.test.d.ts +2 -0
- package/dist/pipelines/pipeline.builder.test.d.ts.map +1 -0
- package/dist/pipelines/pipeline.builder.test.js +185 -0
- package/dist/pipelines/stream.service.d.ts +12 -0
- package/dist/pipelines/stream.service.d.ts.map +1 -0
- package/dist/pipelines/stream.service.js +58 -0
- package/dist/pipelines/stream.service.test.d.ts +2 -0
- package/dist/pipelines/stream.service.test.d.ts.map +1 -0
- package/dist/pipelines/stream.service.test.js +103 -0
- package/dist/quality/quality.service.d.ts +87 -0
- package/dist/quality/quality.service.d.ts.map +1 -0
- package/dist/quality/quality.service.js +326 -0
- package/dist/quality/quality.service.test.d.ts +2 -0
- package/dist/quality/quality.service.test.d.ts.map +1 -0
- package/dist/quality/quality.service.test.js +128 -0
- package/dist/schema/schema.d.ts +127 -0
- package/dist/schema/schema.d.ts.map +1 -0
- package/dist/schema/schema.js +487 -0
- package/dist/schema/schema.test.d.ts +2 -0
- package/dist/schema/schema.test.d.ts.map +1 -0
- package/dist/schema/schema.test.js +411 -0
- package/dist/streaming/flink/flink.client.d.ts +96 -0
- package/dist/streaming/flink/flink.client.d.ts.map +1 -0
- package/dist/streaming/flink/flink.client.js +267 -0
- package/dist/streaming/flink/flink.client.test.d.ts +2 -0
- package/dist/streaming/flink/flink.client.test.d.ts.map +1 -0
- package/dist/streaming/flink/flink.client.test.js +59 -0
- package/dist/streaming/flink/flink.job.d.ts +29 -0
- package/dist/streaming/flink/flink.job.d.ts.map +1 -0
- package/dist/streaming/flink/flink.job.js +27 -0
- package/dist/streaming/flink/flink.job.test.d.ts +2 -0
- package/dist/streaming/flink/flink.job.test.d.ts.map +1 -0
- package/dist/streaming/flink/flink.job.test.js +37 -0
- package/dist/streaming/flink/flink.operators.d.ts +35 -0
- package/dist/streaming/flink/flink.operators.d.ts.map +1 -0
- package/dist/streaming/flink/flink.operators.js +43 -0
- package/dist/streaming/flink/flink.operators.test.d.ts +2 -0
- package/dist/streaming/flink/flink.operators.test.d.ts.map +1 -0
- package/dist/streaming/flink/flink.operators.test.js +38 -0
- package/dist/streaming/stream.builder.d.ts +22 -0
- package/dist/streaming/stream.builder.d.ts.map +1 -0
- package/dist/streaming/stream.builder.js +50 -0
- package/dist/streaming/stream.builder.test.d.ts +2 -0
- package/dist/streaming/stream.builder.test.d.ts.map +1 -0
- package/dist/streaming/stream.builder.test.js +59 -0
- package/dist/streaming/stream.processor.d.ts +66 -0
- package/dist/streaming/stream.processor.d.ts.map +1 -0
- package/dist/streaming/stream.processor.js +178 -0
- package/dist/streaming/stream.processor.test.d.ts +2 -0
- package/dist/streaming/stream.processor.test.d.ts.map +1 -0
- package/dist/streaming/stream.processor.test.js +151 -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.d.ts +12 -0
- package/dist/transformers/built-in.transformers.d.ts.map +1 -0
- package/dist/transformers/built-in.transformers.js +75 -0
- package/dist/transformers/built-in.transformers.test.d.ts +2 -0
- package/dist/transformers/built-in.transformers.test.d.ts.map +1 -0
- package/dist/transformers/built-in.transformers.test.js +85 -0
- package/dist/transformers/transformer.service.d.ts +14 -0
- package/dist/transformers/transformer.service.d.ts.map +1 -0
- package/dist/transformers/transformer.service.js +65 -0
- package/dist/transformers/transformer.service.test.d.ts +2 -0
- package/dist/transformers/transformer.service.test.d.ts.map +1 -0
- package/dist/transformers/transformer.service.test.js +42 -0
- package/dist/validators/schema.validator.d.ts +21 -0
- package/dist/validators/schema.validator.d.ts.map +1 -0
- package/dist/validators/schema.validator.js +40 -0
- package/dist/validators/schema.validator.test.d.ts +2 -0
- package/dist/validators/schema.validator.test.d.ts.map +1 -0
- package/dist/validators/schema.validator.test.js +42 -0
- package/package.json +53 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
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()
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.Schema = void 0;
|
|
12
|
+
// ─── Core buildSchema helper ──────────────────────────────────────────────────
|
|
13
|
+
function buildSchema(syncValidate, jsonSchemaFn, refinements = [], asyncRefinements = []) {
|
|
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) => {
|
|
91
|
+
if (typeof value !== 'string') {
|
|
92
|
+
return { success: false, errors: [{ path: '', message: 'Expected string' }] };
|
|
93
|
+
}
|
|
94
|
+
let v = value;
|
|
95
|
+
for (const pre of preprocessors)
|
|
96
|
+
v = pre(v);
|
|
97
|
+
for (const c of constraints) {
|
|
98
|
+
const err = c(v);
|
|
99
|
+
if (err)
|
|
100
|
+
return { success: false, errors: [{ path: '', message: err }] };
|
|
101
|
+
}
|
|
102
|
+
return { success: true, data: v };
|
|
103
|
+
};
|
|
104
|
+
const jsonSchemaFn = () => ({ type: 'string' });
|
|
105
|
+
const base = buildSchema(syncValidate, jsonSchemaFn, refinements, asyncRefinements);
|
|
106
|
+
const addConstraint = (c) => createStringSchema([...constraints, c], preprocessors, refinements, asyncRefinements);
|
|
107
|
+
const schema = {
|
|
108
|
+
...base,
|
|
109
|
+
email() {
|
|
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
|
+
});
|
|
125
|
+
},
|
|
126
|
+
min(length) {
|
|
127
|
+
return addConstraint((v) => (v.length >= length ? null : `Min length ${length}`));
|
|
128
|
+
},
|
|
129
|
+
max(length) {
|
|
130
|
+
return addConstraint((v) => (v.length <= length ? null : `Max length ${length}`));
|
|
131
|
+
},
|
|
132
|
+
uuid() {
|
|
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
|
+
});
|
|
137
|
+
},
|
|
138
|
+
oneOf(values) {
|
|
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 },
|
|
157
|
+
]);
|
|
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
|
+
},
|
|
171
|
+
};
|
|
172
|
+
return schema;
|
|
173
|
+
}
|
|
174
|
+
// ─── Number Schema Factory ────────────────────────────────────────────────────
|
|
175
|
+
function createNumberSchema(constraints = [], refinements = [], asyncRefinements = []) {
|
|
176
|
+
const syncValidate = (value) => {
|
|
177
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
178
|
+
return { success: false, errors: [{ path: '', message: 'Expected number' }] };
|
|
179
|
+
}
|
|
180
|
+
for (const c of constraints) {
|
|
181
|
+
const err = c(value);
|
|
182
|
+
if (err)
|
|
183
|
+
return { success: false, errors: [{ path: '', message: err }] };
|
|
184
|
+
}
|
|
185
|
+
return { success: true, data: value };
|
|
186
|
+
};
|
|
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,
|
|
192
|
+
min(n) {
|
|
193
|
+
return addConstraint((v) => (v >= n ? null : `Min ${n}`));
|
|
194
|
+
},
|
|
195
|
+
max(n) {
|
|
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' };
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
return schema;
|
|
230
|
+
}
|
|
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' }));
|
|
240
|
+
return {
|
|
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' }] };
|
|
265
|
+
}
|
|
266
|
+
date = d;
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
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' };
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function createObjectSchema(shape, strictMode = false) {
|
|
303
|
+
const syncValidate = (value) => {
|
|
304
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
305
|
+
return { success: false, errors: [{ path: '', message: 'Expected object' }] };
|
|
306
|
+
}
|
|
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
|
+
}
|
|
317
|
+
const data = {};
|
|
318
|
+
const errors = [];
|
|
319
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
320
|
+
const result = fieldSchema.validate(obj[key]);
|
|
321
|
+
if (result.success) {
|
|
322
|
+
data[key] = result.data;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
errors.push(...result.errors.map((e) => ({
|
|
326
|
+
path: key + (e.path ? '.' + e.path : ''),
|
|
327
|
+
message: e.message,
|
|
328
|
+
})));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (errors.length > 0)
|
|
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
|
+
}
|
|
392
|
+
return { success: true, data };
|
|
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]);
|
|
400
|
+
return {
|
|
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
|
+
},
|
|
414
|
+
};
|
|
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 ─────────────────────────────────────────────────────────
|
|
462
|
+
exports.Schema = {
|
|
463
|
+
string() {
|
|
464
|
+
return createStringSchema();
|
|
465
|
+
},
|
|
466
|
+
number() {
|
|
467
|
+
return createNumberSchema();
|
|
468
|
+
},
|
|
469
|
+
boolean() {
|
|
470
|
+
return createBooleanSchema();
|
|
471
|
+
},
|
|
472
|
+
date() {
|
|
473
|
+
return createDateSchema();
|
|
474
|
+
},
|
|
475
|
+
object(shape) {
|
|
476
|
+
return createObjectSchema(shape);
|
|
477
|
+
},
|
|
478
|
+
array(itemSchema) {
|
|
479
|
+
return createArraySchema(itemSchema);
|
|
480
|
+
},
|
|
481
|
+
literal(value) {
|
|
482
|
+
return createLiteralSchema(value);
|
|
483
|
+
},
|
|
484
|
+
union(schemas) {
|
|
485
|
+
return createUnionSchema(schemas);
|
|
486
|
+
},
|
|
487
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.test.d.ts","sourceRoot":"","sources":["../../src/schema/schema.test.ts"],"names":[],"mappings":""}
|