schematox 0.0.2 → 0.0.4

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 CHANGED
@@ -4,19 +4,17 @@ SchmatoX is a lightweight library for creating JSON compatible schemas. The subj
4
4
 
5
5
  ## Pros
6
6
 
7
- - The statically defined JSON compatible schema is a killer feature:
8
- - It has great potential for automation in the context of DB model creation, CRUD backends, documentation, outer interface requirements etc.
9
- - One can store schema changes as static JSON and potentially use it for creating coherent DB Model migration logic.
10
- - One can check if the defined schema is compatible with our constraints using the "as const satisfies Schema".
11
- - Programmatically defined schemas are also supported, but as a means of creating statically defined schemas.
12
- - There is a clear separation of concerns for validating and parsing logic.
13
- - The Schematox parser is used for dealing with an outer interface where the data type is not guaranteed.
14
- - The Schematox validator is used for dealing with an internal interface where the data type is known.
15
- - Schematox uses an Ether-style error handling system. It never throws an error on an unsuccessful schema subject check. Instead, our parser/validator always returns an `EitherError` object.
16
- - We offer first-class support for branded base schema types.
7
+ - The statically defined JSON compatible schema
8
+ - Check defined schema correctness using non generic type `Schema`
9
+ - Programmatically defined schemas supported as mean of creation statically defined schema
10
+ - Clear separation of concerns for validating and parsing logic:
11
+ - Parser is used for dealing with an outer uknnown interface
12
+ - Validator is used for dealing with an internal known interface
13
+ - Schematox uses an Ether-style error handling
14
+ - We offer first-class support for branded base schema primitive types
17
15
  - We have zero dependencies. The runtime code logic is small and easy to grasp, consisting of just a couple of functions. Most of the library code is tests and types.
18
16
 
19
- Essentially, to define a schema, one doesn't even need to import any functions from our library, only the `Schema` type. This approach does come with a few limitations. The first is `stringUnion` and `numberUnion` schema default values are not constrained by the defined union choices, only the primitive type. We might fix this issue later, but for now, we prioritize this over the case when `default` extends union choice by its definition.
17
+ Essentially, to define a schema, one doesn't need to import any functions from our library, only the `as const satisfies Schema` statement. This approach does come with a few limitations. The first is `stringUnion` and `numberUnion` schema default values are not constrained by the defined union choices, only the primitive type. We might fix this issue later, but for now, we prioritize this over the case when `default` extends union choice by its definition.
20
18
 
21
19
  A second limitation is the depth of the compound schema data structure. Currently, we support 7 layers of depth. It's easy to increase this number, but because of the exponential nature of stored type variants in memory, we want to determine how much RAM each next layer will use before increasing it.
22
20
 
@@ -24,11 +22,13 @@ It's crucial to separate parsing/validation logic from the schema itself. Librar
24
22
 
25
23
  ## Cons
26
24
 
25
+ - The library is not ready for production yet, the version is 0 and public API might be changed
27
26
  - Currently we support only 7 layers of compound structure depth but most likely it will be higher soon
28
27
  - We do not support records, discriminated unions, object unions, array unions, intersections, functions, NaN, Infinity and other not JSON compatible structures
29
28
  - Null value is acceptable by the parser but will be treated as undefined and transformed to undefined
30
29
  - Null value is not acceptable by the validator
31
- - We only plan to support custom parser/normalizer integration logic.
30
+
31
+ Check out [github issues](https://github.com/incerta/schematox/issues) of the project to know what we are planning to support soon.
32
32
 
33
33
  ## Installation
34
34
 
@@ -169,7 +169,6 @@ import {
169
169
  string,
170
170
  number,
171
171
  boolean,
172
- buffer,
173
172
  stringUnion,
174
173
  numberUnion,
175
174
  } from 'schematox'
@@ -243,28 +242,6 @@ const programmaticBoolean = boolean()
243
242
  .brand('x', 'y')
244
243
  .description('y')
245
244
 
246
- // buffer
247
- // The default value is not supported because Buffer is not compatible with JSON
248
-
249
- const staticShortBufferRequired = 'buffer' satisfies Schema
250
- const staticShortBufferOptional = 'buffer?' satisfies Schema
251
-
252
- const staticDetailedBuffer = {
253
- type: 'buffer',
254
- optional: true,
255
- brand: ['x', 'y'],
256
- minLength: 1,
257
- maxLength: 1,
258
- description: 'x',
259
- } as const satisfies Schema
260
-
261
- const programmaticBuffer = buffer()
262
- .optional()
263
- .minLength(1)
264
- .maxLength(1)
265
- .brand('x', 'y')
266
- .description('y')
267
-
268
245
  // stringUnion
269
246
 
270
247
  const staticStringUnion = {
@@ -359,41 +336,32 @@ The `result.error` will be:
359
336
 
360
337
  ## Parse/validate differences
361
338
 
362
- The parser provides a new object/primitive without references to the evaluated subject, except in the case of `Buffer` for better performance. This functionality is responsible for clearing the `array` schema result value from `undefined` optional schema values.
339
+ The parser returns `data` as new object/primitive without references to the parsed subject. Parser manages the `null` value as `undefined` and subsequently replaces it with `undefined`. It also swaps `optional` values with the `default` value from schema. One can infer schema parsed subject type by using `XParsed<typeof schema>` generic.
363
340
 
364
- Moreover, the parser manages the `null` value as `undefined` and subsequently replaces it with `undefined`. It also swaps `optional` values with the `default` value, provided that the default values are explicitly stated in the schema.
341
+ The validator on the other hand returns the evaluated subject itself and not applying any mutation/transformation to it. The validator should be used in exceptional cases when we known subject type but not sure that it actually correct. One can infer schema validated subject type by using `XValidated<typeof schema>` generic.
365
342
 
366
- Particularly for this last function, the parser uses a separate type inference flow. This flow is accessible for the library user via the type generic `XParsed<T extends Schema>`. By leveraging this mechanism, the user can simplify the process and enhance efficiency.
343
+ So the difference between `XParsed` and `XValidated` is just about handling `default` schema value. `XParsed` narrows optional schema subject type with default value in the way that it will not be optional, because optional `undefined` and `null` values will be replaced by the `default`. `XValidated` just ignores `default` schema value.
367
344
 
368
- The validator operates by returning a reference to the original object, rather than applying any form of mutations to it. This is a significant feature because it disregards the schema’s default values.
369
-
370
- Furthermore, the validator incorporates a type inference flow distinct from the parser's. Available for utilization through the type generic `XValidated<T extends Schema>`.
371
-
372
- Examples of described differences:
345
+ Examples:
373
346
 
374
347
  ```typescript
375
348
  const optionalStrX = x('string?')
376
349
 
377
- /* Parser treats `null` as `undefined` */
378
-
379
- expect(optionalStrX.parse(null).data).toBe(undefined)
380
- expect(optionalStrX.parse(null).error).toBe(undefined)
381
-
382
- /* Validator is not */
350
+ /* Parser doesn't check subject type */
383
351
 
384
- // @ts-expect-error 'null' is not assignable to parameter of type 'string | undefined'
385
- expect(optionalStrX.validate(null).error).toStrictEqual([
352
+ expect(optionalStrX.parse(0).error).toStrictEqual([
386
353
  {
387
354
  code: 'INVALID_TYPE',
388
355
  schema: 'string?',
389
- subject: null,
356
+ subject: 0,
390
357
  path: [],
391
358
  },
392
359
  ])
393
360
 
394
- /* Parser doesn't check subject type */
361
+ /* Validator does */
395
362
 
396
- expect(optionalStrX.parse(0).error).toStrictEqual([
363
+ // @ts-expect-error 'number' is not 'string'
364
+ expect(optionalStrX.validate(0).error).toStrictEqual([
397
365
  {
398
366
  code: 'INVALID_TYPE',
399
367
  schema: 'string?',
@@ -402,14 +370,19 @@ expect(optionalStrX.parse(0).error).toStrictEqual([
402
370
  },
403
371
  ])
404
372
 
405
- /* Validator does */
373
+ /* Parser treats `null` as `undefined` */
406
374
 
407
- // @ts-expect-error 'number' is not assignable to parameter of type 'string'
408
- expect(optionalStrX.validate(0).error).toStrictEqual([
375
+ expect(optionalStrX.parse(null).data).toBe(undefined)
376
+ expect(optionalStrX.parse(null).error).toBe(undefined)
377
+
378
+ /* Validator is not */
379
+
380
+ // @ts-expect-error 'null' is not 'string | undefined'
381
+ expect(optionalStrX.validate(null).error).toStrictEqual([
409
382
  {
410
383
  code: 'INVALID_TYPE',
411
384
  schema: 'string?',
412
- subject: 0,
385
+ subject: null,
413
386
  path: [],
414
387
  },
415
388
  ])
@@ -425,17 +398,6 @@ expect(defaultedStrX.parse(undefined).data).toBe('y')
425
398
 
426
399
  expect(defaultedStrX.validate(undefined).data).toBe(undefined)
427
400
 
428
- /* Parser clears subject array from optional undefined */
429
-
430
- const arrX = x({ type: 'array', of: 'string?' })
431
- const subject = [undefined, 'y', undefined, 'z']
432
-
433
- expect(arrX.parse(subject).data).toStrictEqual(['y', 'z'])
434
-
435
- /* Validator keeps them */
436
-
437
- expect(arrX.validate(subject).data).toStrictEqual(subject)
438
-
439
401
  /* Parser returning new object */
440
402
 
441
403
  expect(arrX.parse(subject).data === subject).toBe(false)
@@ -1,6 +1,5 @@
1
- /// <reference types="node" />
2
1
  import type { EitherError } from './utils/fp';
3
2
  import type { BaseSchema, Con_Schema_SubjT_P } from './types/compound-schema-types';
4
3
  import type { BaseSchemaParseError } from './error';
5
- export type BaseSchemaSubjectType = string | number | boolean | Buffer | undefined;
4
+ export type BaseSchemaSubjectType = string | number | boolean | undefined;
6
5
  export declare function parseBaseSchemaSubject<T extends BaseSchema>(schema: T, schemaSubject: unknown): EitherError<BaseSchemaParseError, Con_Schema_SubjT_P<T>>;
@@ -68,22 +68,6 @@ function parseBaseSchemaSubject(schema, subject) {
68
68
  }
69
69
  return (0, fp_1.data)(subject);
70
70
  }
71
- case 'buffer?':
72
- case 'buffer': {
73
- if (Buffer.isBuffer(subject) === false) {
74
- if (subject === null || subject === undefined) {
75
- if (schema === 'buffer?') {
76
- return (0, fp_1.data)(undefined);
77
- }
78
- }
79
- return (0, fp_1.error)({
80
- code: error_1.PARSE_ERROR_CODE.invalidType,
81
- schema: schema,
82
- subject: subject,
83
- });
84
- }
85
- return (0, fp_1.data)(subject);
86
- }
87
71
  }
88
72
  }
89
73
  switch (schema.type) {
@@ -171,39 +155,6 @@ function parseBaseSchemaSubject(schema, subject) {
171
155
  }
172
156
  return (0, fp_1.data)(subject);
173
157
  }
174
- case 'buffer': {
175
- if (Buffer.isBuffer(subject) === false) {
176
- if (subject === undefined || subject === null) {
177
- if (schema.optional) {
178
- return (0, fp_1.data)(undefined);
179
- }
180
- }
181
- return (0, fp_1.error)({
182
- code: error_1.PARSE_ERROR_CODE.invalidType,
183
- schema: schema,
184
- subject: subject,
185
- });
186
- }
187
- if (typeof schema.minLength === 'number') {
188
- if (subject.length < schema.minLength) {
189
- return (0, fp_1.error)({
190
- code: error_1.PARSE_ERROR_CODE.minRange,
191
- schema: schema,
192
- subject: subject,
193
- });
194
- }
195
- }
196
- if (typeof schema.maxLength === 'number') {
197
- if (subject.length > schema.maxLength) {
198
- return (0, fp_1.error)({
199
- code: error_1.PARSE_ERROR_CODE.maxRange,
200
- schema: schema,
201
- subject: subject,
202
- });
203
- }
204
- }
205
- return (0, fp_1.data)(subject);
206
- }
207
158
  case 'stringUnion': {
208
159
  if (typeof subject !== 'string') {
209
160
  if (subject === undefined || subject === null) {
@@ -1,6 +1,5 @@
1
- /// <reference types="node" />
2
1
  import type { EitherError } from './utils/fp';
3
2
  import type { BaseSchema, Con_Schema_SubjT_V } from './types/compound-schema-types';
4
3
  import type { BaseSchemaValidateError } from './error';
5
- export type BaseSchemaSubjectType = string | number | boolean | Buffer | undefined;
4
+ export type BaseSchemaSubjectType = string | number | boolean | undefined;
6
5
  export declare function validateBaseSchemaSubject<T extends BaseSchema>(schema: T, schemaSubject: unknown): EitherError<BaseSchemaValidateError, Con_Schema_SubjT_V<T>>;
@@ -62,20 +62,6 @@ function validateBaseSchemaSubject(schema, subject) {
62
62
  }
63
63
  return (0, fp_1.data)(subject);
64
64
  }
65
- case 'buffer?':
66
- case 'buffer': {
67
- if (Buffer.isBuffer(subject) === false) {
68
- if (subject === undefined && schema === 'buffer?') {
69
- return (0, fp_1.data)(undefined);
70
- }
71
- return (0, fp_1.error)({
72
- code: error_1.VALIDATE_ERROR_CODE.invalidType,
73
- schema: schema,
74
- subject: subject,
75
- });
76
- }
77
- return (0, fp_1.data)(subject);
78
- }
79
65
  }
80
66
  }
81
67
  switch (schema.type) {
@@ -168,37 +154,6 @@ function validateBaseSchemaSubject(schema, subject) {
168
154
  }
169
155
  return (0, fp_1.data)(subject);
170
156
  }
171
- case 'buffer': {
172
- if (Buffer.isBuffer(subject) === false) {
173
- if (subject === undefined && schema.optional) {
174
- return (0, fp_1.data)(undefined);
175
- }
176
- return (0, fp_1.error)({
177
- code: error_1.VALIDATE_ERROR_CODE.invalidType,
178
- schema: schema,
179
- subject: subject,
180
- });
181
- }
182
- if (typeof schema.minLength === 'number') {
183
- if (subject.length < schema.minLength) {
184
- return (0, fp_1.error)({
185
- code: error_1.VALIDATE_ERROR_CODE.minRange,
186
- schema: schema,
187
- subject: subject,
188
- });
189
- }
190
- }
191
- if (typeof schema.maxLength === 'number') {
192
- if (subject.length > schema.maxLength) {
193
- return (0, fp_1.error)({
194
- code: error_1.VALIDATE_ERROR_CODE.maxRange,
195
- schema: schema,
196
- subject: subject,
197
- });
198
- }
199
- }
200
- return (0, fp_1.data)(subject);
201
- }
202
157
  case 'stringUnion': {
203
158
  if (typeof subject !== 'string') {
204
159
  if (subject === undefined && schema.optional) {
@@ -45,7 +45,6 @@ function parse(schema, subject) {
45
45
  schema.type === 'string' ||
46
46
  schema.type === 'number' ||
47
47
  schema.type === 'boolean' ||
48
- schema.type === 'buffer' ||
49
48
  schema.type === 'stringUnion' ||
50
49
  schema.type === 'numberUnion') {
51
50
  var parsed = (0, base_schema_parser_1.parseBaseSchemaSubject)(schema, subject);
@@ -113,9 +112,7 @@ function parse(schema, subject) {
113
112
  parsed.error.forEach(function (err) { return errors.push(err); });
114
113
  continue;
115
114
  }
116
- if (parsed.data !== undefined) {
117
- result.push(parsed.data);
118
- }
115
+ result.push(parsed.data);
119
116
  }
120
117
  if (errors.length) {
121
118
  return (0, fp_1.error)(errors);
@@ -45,7 +45,6 @@ function validate(schema, subject) {
45
45
  schema.type === 'string' ||
46
46
  schema.type === 'number' ||
47
47
  schema.type === 'boolean' ||
48
- schema.type === 'buffer' ||
49
48
  schema.type === 'stringUnion' ||
50
49
  schema.type === 'numberUnion') {
51
50
  var validated = (0, base_schema_validator_1.validateBaseSchemaSubject)(schema, subject);
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * but only with a MINOR version update.
9
9
  **/
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.x = exports.validate = exports.parse = exports.object = exports.array = exports.numberUnion = exports.stringUnion = exports.buffer = exports.boolean = exports.number = exports.string = void 0;
11
+ exports.x = exports.validate = exports.parse = exports.object = exports.array = exports.numberUnion = exports.stringUnion = exports.boolean = exports.number = exports.string = void 0;
12
12
  /* Programmatically base schema definition */
13
13
  var string_1 = require("./programmatic-schema/string");
14
14
  Object.defineProperty(exports, "string", { enumerable: true, get: function () { return string_1.string; } });
@@ -16,8 +16,6 @@ var number_1 = require("./programmatic-schema/number");
16
16
  Object.defineProperty(exports, "number", { enumerable: true, get: function () { return number_1.number; } });
17
17
  var boolean_1 = require("./programmatic-schema/boolean");
18
18
  Object.defineProperty(exports, "boolean", { enumerable: true, get: function () { return boolean_1.boolean; } });
19
- var buffer_1 = require("./programmatic-schema/buffer");
20
- Object.defineProperty(exports, "buffer", { enumerable: true, get: function () { return buffer_1.buffer; } });
21
19
  var string_union_1 = require("./programmatic-schema/string-union");
22
20
  Object.defineProperty(exports, "stringUnion", { enumerable: true, get: function () { return string_union_1.stringUnion; } });
23
21
  var number_union_1 = require("./programmatic-schema/number-union");
package/dist/index.ts CHANGED
@@ -18,7 +18,6 @@ import type {
18
18
  export { string } from './programmatic-schema/string'
19
19
  export { number } from './programmatic-schema/number'
20
20
  export { boolean } from './programmatic-schema/boolean'
21
- export { buffer } from './programmatic-schema/buffer'
22
21
  export { stringUnion } from './programmatic-schema/string-union'
23
22
  export { numberUnion } from './programmatic-schema/number-union'
24
23
 
@@ -36,17 +36,6 @@ export type BD_Boolean = {
36
36
  brand?: BrandSchema
37
37
  }
38
38
 
39
- export type BD_Buffer = {
40
- type: 'buffer'
41
- optional?: boolean
42
-
43
- description?: string
44
- brand?: BrandSchema
45
-
46
- minLength?: number /* >= */
47
- maxLength?: number /* <= */
48
- }
49
-
50
39
  export type BD_StringUnion<T extends string = string> = {
51
40
  type: 'stringUnion'
52
41
  of: Readonly<Array<T>>
@@ -71,7 +60,6 @@ export type BD_Schema =
71
60
  | BD_String
72
61
  | BD_Number
73
62
  | BD_Boolean
74
- | BD_Buffer
75
63
  | BD_StringUnion
76
64
  | BD_NumberUnion
77
65
 
@@ -82,13 +70,11 @@ export type Con_BD_Schema_TypeOnly_SubjT<T extends BD_Schema> =
82
70
  ? number
83
71
  : T extends BD_Boolean
84
72
  ? boolean
85
- : T extends BD_Buffer
86
- ? Buffer
87
- : T extends BD_StringUnion<infer U>
88
- ? U
89
- : T extends BD_NumberUnion<infer V>
90
- ? V
91
- : never
73
+ : T extends BD_StringUnion<infer U>
74
+ ? U
75
+ : T extends BD_NumberUnion<infer V>
76
+ ? V
77
+ : never
92
78
 
93
79
  export type Con_BrandSchema_SubjT<T extends BrandSchema> = T extends Readonly<
94
80
  [infer U, infer V]
@@ -7,20 +7,9 @@ export type BS_Number_Opt = 'number?'
7
7
  export type BS_Boolean_Req = 'boolean'
8
8
  export type BS_Boolean_Opt = 'boolean?'
9
9
 
10
- export type BS_Buffer_Req = 'buffer'
11
- export type BS_Buffer_Opt = 'buffer?'
10
+ export type BS_Schema_Req = BS_String_Req | BS_Number_Req | BS_Boolean_Req
12
11
 
13
- export type BS_Schema_Req =
14
- | BS_String_Req
15
- | BS_Number_Req
16
- | BS_Boolean_Req
17
- | BS_Buffer_Req
18
-
19
- export type BS_Schema_Opt =
20
- | BS_String_Opt
21
- | BS_Number_Opt
22
- | BS_Boolean_Opt
23
- | BS_Buffer_Opt
12
+ export type BS_Schema_Opt = BS_String_Opt | BS_Number_Opt | BS_Boolean_Opt
24
13
 
25
14
  export type BS_Schema = BS_Schema_Req | BS_Schema_Opt
26
15
 
@@ -31,9 +20,7 @@ export type Con_BS_Schema_Req_SubjT<T extends BS_Schema_Req> =
31
20
  ? number
32
21
  : T extends BS_Boolean_Req
33
22
  ? boolean
34
- : T extends BS_Buffer_Req
35
- ? Buffer
36
- : never
23
+ : never
37
24
 
38
25
  export type Con_BS_Schema_Opt_SubjT<T extends BS_Schema_Opt> =
39
26
  T extends BS_String_Opt
@@ -42,9 +29,7 @@ export type Con_BS_Schema_Opt_SubjT<T extends BS_Schema_Opt> =
42
29
  ? number | undefined
43
30
  : T extends BS_Boolean_Opt
44
31
  ? boolean | undefined
45
- : T extends BS_Buffer_Opt
46
- ? Buffer | undefined
47
- : never
32
+ : never
48
33
 
49
34
  export type Con_BS_Schema_SubjT<T extends BS_Schema> = T extends BS_Schema_Req
50
35
  ? Con_BS_Schema_Req_SubjT<T>
@@ -63,12 +63,6 @@ export type ExtWith_CompoundSchemaOptionality<
63
63
  U,
64
64
  > = T extends { optional: true } ? U | undefined : U
65
65
 
66
- // TODO: should work as expected only if we add `& {}`
67
- // which is currently forbidden by our linter
68
- export type Prettify_ObjectSchema_SubjT<T> = {
69
- [k in keyof T]: T[k]
70
- }
71
-
72
66
  /* Construct ArraySchema subject type */
73
67
 
74
68
  export type Con_ArraySchema_SubjT_P<T extends ArraySchema> =
@@ -76,11 +70,11 @@ export type Con_ArraySchema_SubjT_P<T extends ArraySchema> =
76
70
  T,
77
71
  T extends { of: infer U }
78
72
  ? U extends BaseSchema
79
- ? Array<NonNullable<Con_BaseSchema_SubjT_P<U>>>
73
+ ? Array<Con_BaseSchema_SubjT_P<U>>
80
74
  : U extends ArraySchema
81
- ? Array<NonNullable<Con_ArraySchema_SubjT_P<U>>>
75
+ ? Array<Con_ArraySchema_SubjT_P<U>>
82
76
  : U extends ObjectSchema
83
- ? Array<NonNullable<Con_ObjectSchema_SubjT_P<U>>>
77
+ ? Array<Con_ObjectSchema_SubjT_P<U>>
84
78
  : never
85
79
  : never
86
80
  >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schematox",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Define JSON compatible schema statically/programmatically and parse/validate its subject with typesafety",
5
5
  "author": "Konstantin Mazur",
6
6
  "license": "MIT",
@@ -34,7 +34,6 @@
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/jest": "^29.5.1",
37
- "@types/node": "^20.1.4",
38
37
  "@typescript-eslint/eslint-plugin": "^5.59.5",
39
38
  "@typescript-eslint/parser": "^5.59.5",
40
39
  "eslint": "^8.40.0",
@@ -42,7 +41,6 @@
42
41
  "jest": "^29.5.0",
43
42
  "prettier": "^3.1.1",
44
43
  "ts-jest": "^29.1.0",
45
- "ts-node": "^10.9.1",
46
44
  "typescript": "^5.0.4"
47
45
  }
48
46
  }