schematox 1.2.0 → 1.2.2-alpha

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 (63) hide show
  1. package/dist/constants.d.ts +20 -0
  2. package/dist/constants.d.ts.map +1 -0
  3. package/dist/constants.js +22 -0
  4. package/dist/constants.js.map +1 -0
  5. package/dist/index.d.ts +10 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +9 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/parse.d.ts +5 -0
  10. package/dist/parse.d.ts.map +1 -0
  11. package/dist/parse.js +328 -0
  12. package/dist/parse.js.map +1 -0
  13. package/dist/struct.d.ts +48 -0
  14. package/dist/struct.d.ts.map +1 -0
  15. package/dist/struct.js +111 -0
  16. package/dist/struct.js.map +1 -0
  17. package/dist/types/extensions.d.ts +13 -0
  18. package/dist/types/extensions.d.ts.map +1 -0
  19. package/dist/types/extensions.js +2 -0
  20. package/dist/types/extensions.js.map +1 -0
  21. package/dist/types/infer.d.ts +35 -0
  22. package/dist/types/infer.d.ts.map +1 -0
  23. package/dist/types/infer.js +2 -0
  24. package/dist/types/infer.js.map +1 -0
  25. package/dist/types/schema.d.ts +93 -0
  26. package/dist/types/schema.d.ts.map +1 -0
  27. package/dist/types/schema.js +2 -0
  28. package/dist/types/schema.js.map +1 -0
  29. package/dist/types/standard-schema.d.ts +35 -0
  30. package/dist/types/standard-schema.d.ts.map +1 -0
  31. package/dist/types/standard-schema.js +2 -0
  32. package/dist/types/standard-schema.js.map +1 -0
  33. package/dist/types/struct.d.ts +52 -0
  34. package/dist/types/struct.d.ts.map +1 -0
  35. package/dist/types/struct.js +2 -0
  36. package/dist/types/struct.js.map +1 -0
  37. package/dist/types/utils.d.ts +41 -0
  38. package/dist/types/utils.d.ts.map +1 -0
  39. package/dist/types/utils.js +2 -0
  40. package/dist/types/utils.js.map +1 -0
  41. package/dist/utils.d.ts +9 -0
  42. package/dist/utils.d.ts.map +1 -0
  43. package/dist/utils.js +14 -0
  44. package/dist/utils.js.map +1 -0
  45. package/package.json +16 -4
  46. package/src/tests/README.md +390 -0
  47. package/src/tests/by-struct/array.test.ts +1684 -0
  48. package/src/tests/by-struct/boolean.test.ts +741 -0
  49. package/src/tests/by-struct/literal.test.ts +755 -0
  50. package/src/tests/by-struct/number.test.ts +1234 -0
  51. package/src/tests/by-struct/object.test.ts +1484 -0
  52. package/src/tests/by-struct/record.test.ts +1802 -0
  53. package/src/tests/by-struct/string.test.ts +1252 -0
  54. package/src/tests/by-struct/tuple.test.ts +1341 -0
  55. package/src/tests/by-struct/union.test.ts +1284 -0
  56. package/src/tests/fixtures.ts +52 -0
  57. package/src/tests/fold-constants.ts +247 -0
  58. package/src/tests/fold-morph.ts +49 -0
  59. package/src/tests/type.ts +1 -0
  60. package/src/tests/types/extensions.test.ts +117 -0
  61. package/src/tests/types/infer.test.ts +1410 -0
  62. package/src/tests/utils.test.ts +191 -0
  63. package/CHANGELOG.md +0 -48
@@ -0,0 +1,390 @@
1
+ The testing strategy for schematox uses a **fold-based approach** to ensure consistency across all data type tests. The system tests three equivalent ways of creating and using schemas:
2
+
3
+ 1. **Raw schema objects** (e.g., `{ type: 'boolean' }`)
4
+ 2. **Constructs** created via `x.makeStruct(schema)`
5
+ 3. **Structs** created via fluent API (e.g., `x.boolean()`)
6
+
7
+ The key principle is that all three approaches should behave identically in terms of:
8
+ - Type inference
9
+ - Runtime parsing
10
+ - Error handling
11
+ - Schema immutability
12
+
13
+ **Folds** are reusable code blocks marked with comments like `foldA:`, `foldB:`, etc. These are automatically synchronized across all type tests using the `npm run morph` script with AST manipulation, ensuring consistency and reducing maintenance overhead.
14
+
15
+ # By struct scaffold
16
+
17
+ ```typescript
18
+ import * as x from '../../'
19
+ import * as fixture from '../fixtures'
20
+
21
+ describe('Type inference and parse by schema/construct/struct (foldA)', () => {
22
+ it.todo('required')
23
+ it.todo('optional')
24
+ it.todo('nullable')
25
+ it.todo('min')
26
+ it.todo('max')
27
+ it.todo('optional + nullable + minLength + maxLength')
28
+ })
29
+
30
+ describe('Struct parameter keys reduction and schema immutability (foldB)', () => {
31
+ it.todo('optional')
32
+ it.todo('optional + nullable')
33
+ it.todo('optional + nullable + brand')
34
+ it.todo('optional + nullable + brand + min')
35
+ it.todo('optional + nullable + brand + min')
36
+ it.todo('optional + nullable + brand + min + max')
37
+ it.todo('optional + nullable + brand + min + max + description')
38
+ it.todo('description + max + min + brand + nullable + optional')
39
+ })
40
+
41
+ describe('ERROR_CODE.invalidType (foldC, foldE)', () => {
42
+ it.todo('iterate over fixture.DATA_TYPE')
43
+ it.todo('InvalidSubject error of nested schema should have correct path/schema/subject')
44
+ })
45
+
46
+ describe('ERROR_CODE.invalidRange (foldD)', () => {
47
+ it.todo('min')
48
+ it.todo('max')
49
+ it.todo('min + max')
50
+ })
51
+
52
+ describe('Compound schema specifics (foldA)', () => {
53
+ it.todo('nested primitive schema: optional + nullable + brand')
54
+ it.todo('nested compound schema: optional + nullable')
55
+ it.todo('nested by itself (schema depth: 4)')
56
+ })
57
+ ```
58
+
59
+ # Fold A: Type Inference and Parse Consistency
60
+
61
+ Purpose**: Validates that all three approaches (schema, construct, struct) produce identical TypeScript types and runtime behavior for successful parsing scenarios.
62
+
63
+ ```typescript
64
+ it('required', () => {
65
+ const schema = { type: 'boolean' } as const satisfies x.Schema
66
+ const struct = x.boolean()
67
+
68
+ type ExpectedSubj = boolean
69
+
70
+ const subjects: Array<ExpectedSubj> = [true, false]
71
+
72
+ foldA: {
73
+ const construct = x.makeStruct(schema)
74
+
75
+ /* ensure that schema/construct/struct/~standard subject types are identical */
76
+
77
+ type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
78
+
79
+ x.tCh<ConstructSchemaSubj, ExpectedSubj>()
80
+ x.tCh<ExpectedSubj, ConstructSchemaSubj>()
81
+
82
+ type SchemaSubj = x.Infer<typeof schema>
83
+
84
+ x.tCh<SchemaSubj, ExpectedSubj>()
85
+ x.tCh<ExpectedSubj, SchemaSubj>()
86
+
87
+ type StructSubj = x.Infer<typeof struct.__schema>
88
+
89
+ x.tCh<StructSubj, ExpectedSubj>()
90
+ x.tCh<ExpectedSubj, StructSubj>()
91
+
92
+ type StandardSubj = NonNullable<
93
+ (typeof struct)['~standard']['types']
94
+ >['output']
95
+
96
+ x.tCh<StandardSubj, ExpectedSubj>()
97
+ x.tCh<ExpectedSubj, StandardSubj>()
98
+
99
+ /* parsed either type check */
100
+
101
+ type ExpectedParsed = x.ParseResult<ExpectedSubj>
102
+
103
+ const parsed = x.parse(schema, undefined)
104
+
105
+ type SchemaParsed = typeof parsed
106
+
107
+ x.tCh<SchemaParsed, ExpectedParsed>()
108
+ x.tCh<ExpectedParsed, SchemaParsed>()
109
+
110
+ type ConstructParsed = ReturnType<typeof construct.parse>
111
+
112
+ x.tCh<ConstructParsed, ExpectedParsed>()
113
+ x.tCh<ExpectedParsed, ConstructParsed>()
114
+
115
+ type StructParsed = ReturnType<typeof struct.parse>
116
+
117
+ x.tCh<StructParsed, ExpectedParsed>()
118
+ x.tCh<ExpectedParsed, StructParsed>()
119
+
120
+ type StandardParsed = Extract<
121
+ ReturnType<(typeof struct)['~standard']['validate']>,
122
+ { value: unknown }
123
+ >['value']
124
+
125
+ x.tCh<StandardParsed, ExpectedSubj>()
126
+ x.tCh<ExpectedSubj, StandardParsed>()
127
+
128
+ /* runtime schema check */
129
+
130
+ expect(struct.__schema).toStrictEqual(schema)
131
+ expect(construct.__schema).toStrictEqual(schema)
132
+ expect(construct.__schema === schema).toBe(false)
133
+
134
+ /* parse result check */
135
+
136
+ for (const subj of subjects) {
137
+ const schemaParsed = x.parse(schema, subj)
138
+
139
+ expect(schemaParsed.error).toBe(undefined)
140
+ expect(schemaParsed.data).toStrictEqual(subj)
141
+
142
+ const constructParsed = construct.parse(subj)
143
+
144
+ expect(constructParsed.error).toBe(undefined)
145
+ expect(constructParsed.data).toStrictEqual(subj)
146
+
147
+ const structParsed = struct.parse(subj)
148
+
149
+ expect(structParsed.error).toBe(undefined)
150
+ expect(structParsed.data).toStrictEqual(subj)
151
+
152
+ const standardParsed = struct['~standard'].validate(subj)
153
+
154
+ if (standardParsed instanceof Promise) {
155
+ throw Error('Not expected')
156
+ }
157
+
158
+ if (standardParsed.issues !== undefined) {
159
+ throw Error('not expected')
160
+ }
161
+
162
+ expect(standardParsed.value).toStrictEqual(subj)
163
+ }
164
+ }
165
+ })
166
+ ```
167
+
168
+ # Fold B: Parameter Keys Reduction and Schema Immutability
169
+
170
+ Purpose**: Tests that fluent API methods correctly reduce available keys (removing applied methods) and ensure schema objects are immutable when parameters are applied.
171
+
172
+ ```typescript
173
+ it('optional', () => {
174
+ const schema = {
175
+ type: 'boolean',
176
+ optional: true,
177
+ } as const satisfies x.Schema
178
+
179
+ const prevStruct = x.boolean()
180
+ const struct = prevStruct.optional()
181
+
182
+ type ExpectedKeys =
183
+ | 'brand'
184
+ | 'nullable'
185
+ | 'description'
186
+ | '__schema'
187
+ | 'parse'
188
+
189
+ foldB: {
190
+ const construct = x.makeStruct(schema)
191
+
192
+ /* ensure that struct keys are reduced after application */
193
+
194
+ type StructKeys = keyof typeof struct
195
+
196
+ x.tCh<StructKeys, ExpectedKeys>()
197
+ x.tCh<ExpectedKeys, StructKeys>()
198
+
199
+ type ConstructKeys = keyof typeof construct
200
+
201
+ x.tCh<ConstructKeys, ExpectedKeys>()
202
+ x.tCh<ExpectedKeys, ConstructKeys>()
203
+
204
+ /* ensure that construct/struct schema types are identical */
205
+
206
+ type ExpectedSchema = typeof schema
207
+ type StructSchema = typeof struct.__schema
208
+
209
+ x.tCh<StructSchema, ExpectedSchema>()
210
+ x.tCh<ExpectedSchema, StructSchema>()
211
+
212
+ type ConstructSchema = typeof struct.__schema
213
+
214
+ x.tCh<ConstructSchema, ExpectedSchema>()
215
+ x.tCh<ExpectedSchema, ConstructSchema>()
216
+
217
+ /* runtime schema check */
218
+
219
+ expect(struct.__schema).toStrictEqual(schema)
220
+ expect(construct.__schema).toStrictEqual(schema)
221
+ expect(construct.__schema === schema).toBe(false)
222
+
223
+ /* runtime schema parameter application immutability check */
224
+
225
+ expect(prevStruct.__schema === struct.__schema).toBe(false)
226
+ }
227
+ })
228
+ ```
229
+
230
+ # Fold C: Invalid Type Error Handling
231
+
232
+ Purpose**: Systematically tests type validation errors by feeding incompatible data types and ensuring consistent error responses across all three approaches.
233
+
234
+ ```typescript
235
+ it('iterate over fixture.DATA_TYPE', () => {
236
+ const schema = { type: 'boolean' } satisfies x.Schema
237
+ const struct = x.boolean()
238
+ const source = fixture.DATA_TYPE
239
+
240
+ foldC: {
241
+ const construct = x.makeStruct(schema)
242
+
243
+ for (const [kind, types] of source) {
244
+ if (kind === schema.type) {
245
+ continue
246
+ }
247
+
248
+ for (const subject of types) {
249
+ const expectedError = [
250
+ {
251
+ code: x.ERROR_CODE.invalidType,
252
+ schema: schema,
253
+ subject: subject,
254
+ path: [],
255
+ },
256
+ ]
257
+
258
+ const parsedSchema = x.parse(schema, subject)
259
+ const parsedConstruct = construct.parse(subject)
260
+ const parsedStruct = struct.parse(subject)
261
+
262
+ expect(parsedSchema.error).toStrictEqual(expectedError)
263
+ expect(parsedConstruct.error).toStrictEqual(expectedError)
264
+ expect(parsedStruct.error).toStrictEqual(expectedError)
265
+
266
+ const parsedStandard = struct['~standard'].validate(subject)
267
+
268
+ if (parsedStandard instanceof Promise) {
269
+ throw Error('Not expected')
270
+ }
271
+
272
+ expect(parsedStandard.issues).toStrictEqual([
273
+ { message: x.ERROR_CODE.invalidType, path: [] },
274
+ ])
275
+ }
276
+ }
277
+ }
278
+ })
279
+ ```
280
+
281
+ Fold D: Range Validation Error Handling
282
+
283
+ Purpose**: Tests range/constraint validation errors (like `min`, `max`, `minLength`, `maxLength`) ensuring consistent error handling.
284
+
285
+ ```typescript
286
+ it('min', () => {
287
+ const schema = { type: 'number', min: 0 } satisfies x.Schema
288
+ const struct = x.number().min(schema.min)
289
+ const subjects = [-1, -2, -3]
290
+
291
+ foldD: {
292
+ const construct = x.makeStruct(schema)
293
+
294
+ for (const subject of subjects) {
295
+ const expectedError = [
296
+ {
297
+ code: x.ERROR_CODE.invalidRange,
298
+ schema: schema,
299
+ subject: subject,
300
+ path: [],
301
+ },
302
+ ]
303
+
304
+ const parsedSchema = x.parse(schema, subject)
305
+ const parsedConstruct = construct.parse(subject)
306
+ const parsedStruct = struct.parse(subject)
307
+
308
+ expect(parsedSchema.error).toStrictEqual(expectedError)
309
+ expect(parsedConstruct.error).toStrictEqual(expectedError)
310
+ expect(parsedStruct.error).toStrictEqual(expectedError)
311
+
312
+ const parsedStandard = struct['~standard'].validate(subject)
313
+
314
+ if (parsedStandard instanceof Promise) {
315
+ throw Error('Not expected')
316
+ }
317
+
318
+ expect(parsedStandard.issues).toStrictEqual([
319
+ { message: x.ERROR_CODE.invalidRange, path: [] },
320
+ ])
321
+ }
322
+ }
323
+ })
324
+ ```
325
+
326
+ # Fold E: Nested Schema Error Path Tracking
327
+
328
+ Purpose**: Tests that error reporting correctly tracks the path to invalid data in nested structures, ensuring the `path`, `schema`, and `subject` properties in errors point to the exact location and nature of validation failures.
329
+
330
+ ```typescript
331
+ it('InvalidSubject error of nested schema should have correct path/schema/subject', () => {
332
+ const schema = {
333
+ type: 'array',
334
+ of: { type: 'array', of: { type: 'boolean' } },
335
+ } as const satisfies x.Schema
336
+
337
+ const struct = x.array(x.array(x.boolean()))
338
+ const samples: Array<
339
+ [
340
+ subj: unknown[],
341
+ invalidSubj: unknown,
342
+ invalidSubjSchema: x.Schema,
343
+ errorPath: x.ErrorPath,
344
+ ]
345
+ > = [
346
+ [[[null, false, true]], null, schema.of.of, [0, 0]],
347
+ [[[], [null, false, true]], null, schema.of.of, [1, 0]],
348
+ [[[], [], [null, false, true]], null, schema.of.of, [2, 0]],
349
+ [[[true, 'str', true]], 'str', schema.of.of, [0, 1]],
350
+ [[[], [true, 'str', true]], 'str', schema.of.of, [1, 1]],
351
+ [[[], [], [true, 'str', true]], 'str', schema.of.of, [2, 1]],
352
+ [[[true, false, 69]], 69, schema.of.of, [0, 2]],
353
+ [[[], [true, false, 69]], 69, schema.of.of, [1, 2]],
354
+ [[[], [], [true, false, 69]], 69, schema.of.of, [2, 2]],
355
+ ]
356
+
357
+ foldE: {
358
+ const construct = x.makeStruct(schema)
359
+
360
+ for (const [subject, invalidSubj, invalidSubjSchema, path] of samples) {
361
+ const expectedError = [
362
+ {
363
+ path,
364
+ code: x.ERROR_CODE.invalidType,
365
+ schema: invalidSubjSchema,
366
+ subject: invalidSubj,
367
+ },
368
+ ]
369
+
370
+ const parsedSchema = x.parse(schema, subject)
371
+ const parsedConstruct = construct.parse(subject)
372
+ const parsedStruct = struct.parse(subject)
373
+
374
+ expect(parsedSchema.error).toStrictEqual(expectedError)
375
+ expect(parsedConstruct.error).toStrictEqual(expectedError)
376
+ expect(parsedStruct.error).toStrictEqual(expectedError)
377
+
378
+ const parsedStandard = struct['~standard'].validate(subject)
379
+
380
+ if (parsedStandard instanceof Promise) {
381
+ throw Error('Not expected')
382
+ }
383
+
384
+ expect(parsedStandard.issues).toStrictEqual([
385
+ { path, message: x.ERROR_CODE.invalidType },
386
+ ])
387
+ }
388
+ }
389
+ })
390
+ ```