@hazeljs/data 0.2.0-beta.68 → 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
@@ -13,8 +13,21 @@ exports.QualityService = void 0;
13
13
  const core_1 = require("@hazeljs/core");
14
14
  const core_2 = __importDefault(require("@hazeljs/core"));
15
15
  /**
16
- * Quality Service - Data quality checks
17
- * Validates data completeness, consistency, and integrity
16
+ * Quality Service data quality checks, profiling, and anomaly detection.
17
+ *
18
+ * Built-in check factories:
19
+ * - completeness(fields[])
20
+ * - notNull(fields[])
21
+ * - uniqueness(fields[])
22
+ * - range(field, { min, max })
23
+ * - pattern(field, regex, message?)
24
+ * - referentialIntegrity(field, allowedValues[])
25
+ *
26
+ * Profiling:
27
+ * - profile(dataset, records[]) → DataProfile
28
+ *
29
+ * Anomaly detection:
30
+ * - detectAnomalies(records[], fields[], threshold?) → AnomalyResult[]
18
31
  */
19
32
  let QualityService = class QualityService {
20
33
  constructor() {
@@ -37,49 +50,275 @@ let QualityService = class QualityService {
37
50
  results.push({
38
51
  name,
39
52
  passed: false,
53
+ score: 0,
40
54
  message: error instanceof Error ? error.message : 'Check failed',
41
55
  });
42
56
  }
43
57
  }
44
58
  const passed = results.every((r) => r.passed);
45
- return {
46
- timestamp: new Date(),
47
- dataset,
48
- totalRows,
49
- checks: results,
50
- passed,
51
- };
59
+ const scores = results.map((r) => (r.score !== undefined ? r.score : r.passed ? 100 : 0));
60
+ const score = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
61
+ return { timestamp: new Date(), dataset, totalRows, checks: results, passed, score };
52
62
  }
63
+ // ─── Built-in check factories ─────────────────────────────────────────────
53
64
  completeness(requiredFields) {
54
65
  return (data) => {
55
- if (data === null || typeof data !== 'object') {
56
- return { name: 'completeness', passed: false, message: 'Data is not an object' };
66
+ const items = Array.isArray(data) ? data : [data];
67
+ let totalMissing = 0;
68
+ const missingByField = {};
69
+ for (const item of items) {
70
+ if (item === null || typeof item !== 'object')
71
+ continue;
72
+ const obj = item;
73
+ for (const f of requiredFields) {
74
+ if (obj[f] === undefined || obj[f] === null) {
75
+ totalMissing++;
76
+ missingByField[f] = (missingByField[f] ?? 0) + 1;
77
+ }
78
+ }
57
79
  }
58
- const obj = data;
59
- const missing = requiredFields.filter((f) => obj[f] === undefined || obj[f] === null);
80
+ const total = items.length * requiredFields.length;
81
+ const score = total > 0 ? Math.round(((total - totalMissing) / total) * 100) : 100;
60
82
  return {
61
83
  name: 'completeness',
62
- passed: missing.length === 0,
63
- message: missing.length > 0 ? `Missing fields: ${missing.join(', ')}` : undefined,
64
- details: { missing, required: requiredFields },
84
+ passed: totalMissing === 0,
85
+ score,
86
+ message: totalMissing > 0
87
+ ? `${totalMissing} missing values across ${Object.keys(missingByField).length} fields`
88
+ : undefined,
89
+ details: { missingByField, required: requiredFields },
65
90
  };
66
91
  };
67
92
  }
68
93
  notNull(fields) {
69
94
  return (data) => {
70
- if (data === null || typeof data !== 'object') {
71
- return { name: 'notNull', passed: false, message: 'Data is not an object' };
95
+ const items = Array.isArray(data) ? data : [data];
96
+ const nullFields = [];
97
+ for (const item of items) {
98
+ if (item === null || typeof item !== 'object')
99
+ continue;
100
+ const obj = item;
101
+ for (const f of fields) {
102
+ if ((obj[f] === null || obj[f] === undefined) && !nullFields.includes(f)) {
103
+ nullFields.push(f);
104
+ }
105
+ }
72
106
  }
73
- const obj = data;
74
- const nullFields = fields.filter((f) => obj[f] === null || obj[f] === undefined);
75
107
  return {
76
108
  name: 'notNull',
77
109
  passed: nullFields.length === 0,
110
+ score: nullFields.length === 0
111
+ ? 100
112
+ : Math.round(((fields.length - nullFields.length) / fields.length) * 100),
78
113
  message: nullFields.length > 0 ? `Null fields: ${nullFields.join(', ')}` : undefined,
79
114
  details: { nullFields },
80
115
  };
81
116
  };
82
117
  }
118
+ uniqueness(fields) {
119
+ return (data) => {
120
+ const items = Array.isArray(data) ? data : [data];
121
+ const seen = {};
122
+ const duplicates = {};
123
+ for (const f of fields)
124
+ seen[f] = new Set();
125
+ for (const item of items) {
126
+ if (item === null || typeof item !== 'object')
127
+ continue;
128
+ const obj = item;
129
+ for (const f of fields) {
130
+ const v = obj[f];
131
+ if (seen[f].has(v)) {
132
+ duplicates[f] = (duplicates[f] ?? 0) + 1;
133
+ }
134
+ else {
135
+ seen[f].add(v);
136
+ }
137
+ }
138
+ }
139
+ const totalDups = Object.values(duplicates).reduce((a, b) => a + b, 0);
140
+ const score = items.length > 0 ? Math.round(((items.length - totalDups) / items.length) * 100) : 100;
141
+ return {
142
+ name: 'uniqueness',
143
+ passed: totalDups === 0,
144
+ score,
145
+ message: totalDups > 0 ? `${totalDups} duplicate values found` : undefined,
146
+ details: { duplicates, fields },
147
+ };
148
+ };
149
+ }
150
+ range(field, options) {
151
+ return (data) => {
152
+ const items = Array.isArray(data) ? data : [data];
153
+ const violations = [];
154
+ items.forEach((item, idx) => {
155
+ if (item === null || typeof item !== 'object')
156
+ return;
157
+ const obj = item;
158
+ const v = obj[field];
159
+ if (typeof v !== 'number')
160
+ return;
161
+ if (options.min !== undefined && v < options.min)
162
+ violations.push({ index: idx, value: v });
163
+ else if (options.max !== undefined && v > options.max)
164
+ violations.push({ index: idx, value: v });
165
+ });
166
+ const score = items.length > 0
167
+ ? Math.round(((items.length - violations.length) / items.length) * 100)
168
+ : 100;
169
+ return {
170
+ name: `range:${field}`,
171
+ passed: violations.length === 0,
172
+ score,
173
+ message: violations.length > 0
174
+ ? `${violations.length} values out of range [${options.min ?? '−∞'}, ${options.max ?? '+∞'}]`
175
+ : undefined,
176
+ details: { field, violations: violations.slice(0, 10), options },
177
+ };
178
+ };
179
+ }
180
+ pattern(field, regex, message) {
181
+ return (data) => {
182
+ const items = Array.isArray(data) ? data : [data];
183
+ const violations = [];
184
+ items.forEach((item, idx) => {
185
+ if (item === null || typeof item !== 'object')
186
+ return;
187
+ const obj = item;
188
+ const v = obj[field];
189
+ if (typeof v === 'string' && !regex.test(v))
190
+ violations.push({ index: idx, value: v });
191
+ });
192
+ const score = items.length > 0
193
+ ? Math.round(((items.length - violations.length) / items.length) * 100)
194
+ : 100;
195
+ return {
196
+ name: `pattern:${field}`,
197
+ passed: violations.length === 0,
198
+ score,
199
+ message: violations.length > 0
200
+ ? (message ?? `${violations.length} values don't match pattern ${regex.source}`)
201
+ : undefined,
202
+ details: { field, regex: regex.source, violations: violations.slice(0, 10) },
203
+ };
204
+ };
205
+ }
206
+ referentialIntegrity(field, allowedValues) {
207
+ const allowed = new Set(allowedValues);
208
+ return (data) => {
209
+ const items = Array.isArray(data) ? data : [data];
210
+ const violations = [];
211
+ items.forEach((item, idx) => {
212
+ if (item === null || typeof item !== 'object')
213
+ return;
214
+ const obj = item;
215
+ const v = obj[field];
216
+ if (v !== undefined && v !== null && !allowed.has(v)) {
217
+ violations.push({ index: idx, value: v });
218
+ }
219
+ });
220
+ const score = items.length > 0
221
+ ? Math.round(((items.length - violations.length) / items.length) * 100)
222
+ : 100;
223
+ return {
224
+ name: `referentialIntegrity:${field}`,
225
+ passed: violations.length === 0,
226
+ score,
227
+ message: violations.length > 0 ? `${violations.length} values not in allowed set` : undefined,
228
+ details: { field, violations: violations.slice(0, 10), allowedCount: allowedValues.length },
229
+ };
230
+ };
231
+ }
232
+ // ─── Data Profiling ───────────────────────────────────────────────────────
233
+ profile(dataset, records) {
234
+ if (records.length === 0) {
235
+ return { dataset, totalRows: 0, fields: {}, generatedAt: new Date() };
236
+ }
237
+ const fieldNames = Array.from(new Set(records.flatMap((r) => Object.keys(r))));
238
+ const fields = {};
239
+ for (const field of fieldNames) {
240
+ const values = records.map((r) => r[field]);
241
+ const nonNull = values.filter((v) => v !== null && v !== undefined);
242
+ const nums = nonNull.filter((v) => typeof v === 'number');
243
+ const uniqueSet = new Set(values.map((v) => JSON.stringify(v)));
244
+ // Top values by frequency
245
+ const freq = new Map();
246
+ for (const v of values) {
247
+ const k = JSON.stringify(v);
248
+ const entry = freq.get(k) ?? { value: v, count: 0 };
249
+ entry.count++;
250
+ freq.set(k, entry);
251
+ }
252
+ const topValues = Array.from(freq.values())
253
+ .sort((a, b) => b.count - a.count)
254
+ .slice(0, 10);
255
+ let mean;
256
+ let stddev;
257
+ let min;
258
+ let max;
259
+ if (nums.length > 0) {
260
+ mean = nums.reduce((a, b) => a + b, 0) / nums.length;
261
+ stddev = Math.sqrt(nums.reduce((acc, v) => acc + Math.pow(v - mean, 2), 0) / nums.length);
262
+ min = Math.min(...nums);
263
+ max = Math.max(...nums);
264
+ }
265
+ else {
266
+ const strs = nonNull.filter((v) => typeof v === 'string');
267
+ if (strs.length > 0) {
268
+ const sorted = [...strs].sort();
269
+ min = sorted[0];
270
+ max = sorted[sorted.length - 1];
271
+ }
272
+ }
273
+ fields[field] = {
274
+ count: values.length,
275
+ nullCount: values.length - nonNull.length,
276
+ nullPct: parseFloat(((1 - nonNull.length / values.length) * 100).toFixed(2)),
277
+ uniqueCount: uniqueSet.size,
278
+ cardinality: parseFloat((uniqueSet.size / values.length).toFixed(4)),
279
+ min,
280
+ max,
281
+ mean: mean !== undefined ? parseFloat(mean.toFixed(4)) : undefined,
282
+ stddev: stddev !== undefined ? parseFloat(stddev.toFixed(4)) : undefined,
283
+ topValues,
284
+ };
285
+ }
286
+ return { dataset, totalRows: records.length, fields, generatedAt: new Date() };
287
+ }
288
+ // ─── Anomaly Detection ────────────────────────────────────────────────────
289
+ /**
290
+ * Detect statistical anomalies using Z-score.
291
+ * @param records Dataset rows
292
+ * @param fields Numeric fields to analyze
293
+ * @param threshold Z-score threshold (default: 3.0)
294
+ */
295
+ detectAnomalies(records, fields, threshold = 3.0) {
296
+ const anomalies = [];
297
+ for (const field of fields) {
298
+ const nums = records
299
+ .map((r, i) => ({ value: r[field], index: i }))
300
+ .filter((x) => typeof x.value === 'number');
301
+ if (nums.length < 3)
302
+ continue;
303
+ const mean = nums.reduce((a, b) => a + b.value, 0) / nums.length;
304
+ const stddev = Math.sqrt(nums.reduce((acc, b) => acc + Math.pow(b.value - mean, 2), 0) / nums.length);
305
+ if (stddev === 0)
306
+ continue;
307
+ for (const { value, index } of nums) {
308
+ const zScore = Math.abs((value - mean) / stddev);
309
+ if (zScore > threshold) {
310
+ anomalies.push({
311
+ field,
312
+ rowIndex: index,
313
+ value,
314
+ zScore: parseFloat(zScore.toFixed(3)),
315
+ message: `Field "${field}" value ${value} is ${zScore.toFixed(2)} stddevs from mean (${mean.toFixed(2)})`,
316
+ });
317
+ }
318
+ }
319
+ }
320
+ return anomalies.sort((a, b) => b.zScore - a.zScore);
321
+ }
83
322
  };
84
323
  exports.QualityService = QualityService;
85
324
  exports.QualityService = QualityService = __decorate([
@@ -31,4 +31,98 @@ describe('QualityService', () => {
31
31
  const report = await service.runChecks('ds', [{}, {}]);
32
32
  expect(report.totalRows).toBe(2);
33
33
  });
34
+ it('report includes quality score', async () => {
35
+ service.registerCheck('custom', (_data) => ({
36
+ name: 'custom',
37
+ passed: true,
38
+ score: 100,
39
+ }));
40
+ const report = await service.runChecks('ds', [{}]);
41
+ expect(report.score).toBe(100);
42
+ });
43
+ it('uniqueness check', () => {
44
+ const check = service.uniqueness(['id']);
45
+ expect(check([{ id: 1 }, { id: 2 }]).passed).toBe(true);
46
+ expect(check([{ id: 1 }, { id: 1 }]).passed).toBe(false);
47
+ });
48
+ it('range check', () => {
49
+ const check = service.range('age', { min: 0, max: 120 });
50
+ expect(check([{ age: 25 }]).passed).toBe(true);
51
+ expect(check([{ age: 150 }]).passed).toBe(false);
52
+ });
53
+ it('pattern check', () => {
54
+ const check = service.pattern('phone', /^\d{10}$/);
55
+ expect(check([{ phone: '1234567890' }]).passed).toBe(true);
56
+ expect(check([{ phone: '123' }]).passed).toBe(false);
57
+ });
58
+ it('pattern check with custom message', () => {
59
+ const check = service.pattern('code', /^[A-Z]+$/, 'Must be uppercase');
60
+ const result = check([{ code: 'abc' }]);
61
+ expect(result.passed).toBe(false);
62
+ expect(result.message).toContain('Must be uppercase');
63
+ });
64
+ it('range check with only min', () => {
65
+ const check = service.range('age', { min: 18 });
66
+ expect(check([{ age: 25 }]).passed).toBe(true);
67
+ expect(check([{ age: 10 }]).passed).toBe(false);
68
+ });
69
+ it('range check with only max', () => {
70
+ const check = service.range('age', { max: 65 });
71
+ expect(check([{ age: 50 }]).passed).toBe(true);
72
+ expect(check([{ age: 100 }]).passed).toBe(false);
73
+ });
74
+ it('referentialIntegrity check', () => {
75
+ const check = service.referentialIntegrity('status', ['active', 'inactive']);
76
+ expect(check([{ status: 'active' }]).passed).toBe(true);
77
+ expect(check([{ status: 'unknown' }]).passed).toBe(false);
78
+ });
79
+ it('profile returns field stats', () => {
80
+ const records = [
81
+ { name: 'a', age: 10 },
82
+ { name: 'b', age: 20 },
83
+ { name: 'c', age: 30 },
84
+ ];
85
+ const profile = service.profile('test', records);
86
+ expect(profile.totalRows).toBe(3);
87
+ expect(profile.fields).toHaveProperty('name');
88
+ expect(profile.fields).toHaveProperty('age');
89
+ expect(profile.fields.age.mean).toBe(20);
90
+ });
91
+ it('detectAnomalies flags outliers', () => {
92
+ const records = [{ value: 10 }, { value: 11 }, { value: 12 }, { value: 1000 }];
93
+ const anomalies = service.detectAnomalies(records, ['value'], 1.5);
94
+ expect(anomalies.length).toBeGreaterThan(0);
95
+ expect(anomalies.some((a) => a.value === 1000)).toBe(true);
96
+ });
97
+ it('completeness skips non-object items and returns passed when no objects processed', () => {
98
+ const check = service.completeness(['email']);
99
+ const result = check('not an object');
100
+ expect(result.passed).toBe(true);
101
+ expect(result.score).toBe(100);
102
+ });
103
+ it('runChecks handles check throwing', async () => {
104
+ service.registerCheck('throws', () => {
105
+ throw new Error('Check failed');
106
+ });
107
+ const report = await service.runChecks('ds', [{}]);
108
+ expect(report.checks).toHaveLength(1);
109
+ expect(report.checks[0].passed).toBe(false);
110
+ expect(report.checks[0].message).toBe('Check failed');
111
+ });
112
+ it('runChecks handles check throwing non-Error', async () => {
113
+ service.registerCheck('throws', () => {
114
+ throw 'string error';
115
+ });
116
+ const report = await service.runChecks('ds', [{}]);
117
+ expect(report.checks[0].message).toBe('Check failed');
118
+ });
119
+ it('runChecks with no checks returns score 100', async () => {
120
+ const report = await service.runChecks('ds', [{}]);
121
+ expect(report.score).toBe(100);
122
+ });
123
+ it('profile returns empty for empty records', () => {
124
+ const profile = service.profile('empty', []);
125
+ expect(profile.totalRows).toBe(0);
126
+ expect(profile.fields).toEqual({});
127
+ });
34
128
  });
@@ -1,5 +1,10 @@
1
1
  /**
2
- * Schema builder for data validation - fluent API similar to Zod
2
+ * Schema builder for data validation - fluent API
3
+ * Zero runtime dependencies. TypeScript-first with full type inference.
4
+ *
5
+ * Supported types: string, number, boolean, date, object, array, literal, union
6
+ * Modifiers: optional, nullable, default, transform, refine, refineAsync
7
+ * Utilities: toJsonSchema(), Infer<T>, validateAsync()
3
8
  */
4
9
  export interface SchemaValidationError {
5
10
  path: string;
@@ -12,36 +17,111 @@ export type SchemaValidator<T = unknown> = (value: unknown) => {
12
17
  success: false;
13
18
  errors: SchemaValidationError[];
14
19
  };
20
+ type SyncResult<T> = {
21
+ success: true;
22
+ data: T;
23
+ } | {
24
+ success: false;
25
+ errors: SchemaValidationError[];
26
+ };
15
27
  export interface BaseSchema<T = unknown> {
16
- _type?: T;
17
- validate(value: unknown): {
18
- success: true;
19
- data: T;
20
- } | {
21
- success: false;
22
- errors: SchemaValidationError[];
23
- };
28
+ readonly _type: T;
29
+ validate(value: unknown): SyncResult<T>;
30
+ validateAsync(value: unknown): Promise<SyncResult<T>>;
31
+ optional(): BaseSchema<T | undefined>;
32
+ nullable(): BaseSchema<T | null>;
33
+ default(value: NonNullable<T>): BaseSchema<T>;
34
+ transform<U>(fn: (value: T) => U): BaseSchema<U>;
35
+ refine(fn: (value: T) => boolean, message: string): BaseSchema<T>;
36
+ refineAsync(fn: (value: T) => Promise<boolean>, message: string): BaseSchema<T>;
37
+ toJsonSchema(): Record<string, unknown>;
24
38
  }
39
+ /**
40
+ * Infer the output TypeScript type from a schema.
41
+ * @example
42
+ * const UserSchema = Schema.object({ name: Schema.string(), age: Schema.number() });
43
+ * type User = Infer<typeof UserSchema>; // { name: string; age: number }
44
+ */
45
+ export type Infer<T extends BaseSchema<unknown>> = T extends BaseSchema<infer U> ? U : never;
25
46
  export interface StringSchema extends BaseSchema<string> {
26
47
  email(): StringSchema;
48
+ url(): StringSchema;
27
49
  min(length: number): StringSchema;
28
50
  max(length: number): StringSchema;
29
51
  uuid(): StringSchema;
30
52
  oneOf(values: string[]): StringSchema;
53
+ pattern(regex: RegExp, message?: string): StringSchema;
54
+ required(): StringSchema;
55
+ trim(): StringSchema;
56
+ refine(fn: (value: string) => boolean, message: string): StringSchema;
57
+ refineAsync(fn: (value: string) => Promise<boolean>, message: string): StringSchema;
58
+ optional(): BaseSchema<string | undefined>;
59
+ nullable(): BaseSchema<string | null>;
60
+ default(value: string): StringSchema;
61
+ transform<U>(fn: (value: string) => U): BaseSchema<U>;
31
62
  }
32
63
  export interface NumberSchema extends BaseSchema<number> {
33
64
  min(n: number): NumberSchema;
34
65
  max(n: number): NumberSchema;
66
+ integer(): NumberSchema;
67
+ positive(): NumberSchema;
68
+ negative(): NumberSchema;
69
+ multipleOf(n: number): NumberSchema;
70
+ refine(fn: (value: number) => boolean, message: string): NumberSchema;
71
+ refineAsync(fn: (value: number) => Promise<boolean>, message: string): NumberSchema;
72
+ optional(): BaseSchema<number | undefined>;
73
+ nullable(): BaseSchema<number | null>;
74
+ default(value: number): NumberSchema;
75
+ transform<U>(fn: (value: number) => U): BaseSchema<U>;
76
+ }
77
+ export interface BooleanSchema extends BaseSchema<boolean> {
78
+ optional(): BaseSchema<boolean | undefined>;
79
+ nullable(): BaseSchema<boolean | null>;
80
+ default(value: boolean): BooleanSchema;
81
+ transform<U>(fn: (value: boolean) => U): BaseSchema<U>;
82
+ }
83
+ export interface DateSchema extends BaseSchema<Date> {
84
+ min(date: Date): DateSchema;
85
+ max(date: Date): DateSchema;
86
+ optional(): BaseSchema<Date | undefined>;
87
+ nullable(): BaseSchema<Date | null>;
88
+ default(value: Date): DateSchema;
89
+ transform<U>(fn: (value: Date) => U): BaseSchema<U>;
35
90
  }
36
- export type DateSchema = BaseSchema<Date>;
37
91
  export interface ObjectSchema<T = Record<string, unknown>> extends BaseSchema<T> {
38
92
  shape: Record<string, BaseSchema>;
93
+ strict(): ObjectSchema<T>;
94
+ pick<K extends keyof T>(keys: K[]): ObjectSchema<Pick<T, K>>;
95
+ omit<K extends keyof T>(keys: K[]): ObjectSchema<Omit<T, K>>;
96
+ extend<E extends Record<string, BaseSchema>>(extra: E): ObjectSchema<T & {
97
+ [K in keyof E]: Infer<E[K]>;
98
+ }>;
99
+ optional(): BaseSchema<T | undefined>;
100
+ nullable(): BaseSchema<T | null>;
39
101
  }
102
+ export interface ArraySchema<T = unknown> extends BaseSchema<T[]> {
103
+ min(length: number): ArraySchema<T>;
104
+ max(length: number): ArraySchema<T>;
105
+ nonempty(): ArraySchema<T>;
106
+ optional(): BaseSchema<T[] | undefined>;
107
+ nullable(): BaseSchema<T[] | null>;
108
+ }
109
+ export interface LiteralSchema<T extends string | number | boolean> extends BaseSchema<T> {
110
+ readonly value: T;
111
+ }
112
+ export type UnionSchema<T> = BaseSchema<T>;
113
+ type ShapeToType<S extends Record<string, BaseSchema>> = {
114
+ [K in keyof S]: Infer<S[K]>;
115
+ };
40
116
  export declare const Schema: {
41
117
  string(): StringSchema;
42
118
  number(): NumberSchema;
119
+ boolean(): BooleanSchema;
43
120
  date(): DateSchema;
44
- object<T extends Record<string, BaseSchema>>(shape: T): ObjectSchema;
45
- array(itemSchema: BaseSchema): BaseSchema<unknown[]>;
121
+ object<S extends Record<string, BaseSchema>>(shape: S): ObjectSchema<ShapeToType<S>>;
122
+ array<T>(itemSchema: BaseSchema<T>): ArraySchema<T>;
123
+ literal<T extends string | number | boolean>(value: T): LiteralSchema<T>;
124
+ union<T extends BaseSchema<unknown>[]>(schemas: T): UnionSchema<Infer<T[number]>>;
46
125
  };
126
+ export {};
47
127
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/schema/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,eAAe,CAAC,CAAC,GAAG,OAAO,IAAI,CACzC,KAAK,EAAE,OAAO,KACX;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAC;AAGtF,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,QAAQ,CACN,KAAK,EAAE,OAAO,GACb;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,qBAAqB,EAAE,CAAA;KAAE,CAAC;CACrF;AAGD,MAAM,WAAW,YAAa,SAAQ,UAAU,CAAC,MAAM,CAAC;IACtD,KAAK,IAAI,YAAY,CAAC;IACtB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC;IAClC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC;IAClC,IAAI,IAAI,YAAY,CAAC;IACrB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC;CACvC;AAGD,MAAM,WAAW,YAAa,SAAQ,UAAU,CAAC,MAAM,CAAC;IACtD,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAC7B,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;CAC9B;AAGD,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;AAG1C,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IAC9E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACnC;AAsJD,eAAO,MAAM,MAAM;cACP,YAAY;cAGZ,YAAY;YAGd,UAAU;WAGX,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,GAAG,YAAY;sBAGlD,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;CA2BrD,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/schema/schema.ts"],"names":[],"mappings":"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"}