schematox 1.0.0 → 1.1.0-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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.1](https://github.com/incerta/schematox/compare/v1.0.0...v1.0.1)
4
+
5
+ - [FIX: struct brand assignment second argument type restriction #44](https://github.com/incerta/schematox/pull/44)
6
+
3
7
  ## [1.0.0](https://github.com/incerta/schematox/compare/v0.4.0...v1.0.0)
4
8
 
5
9
  The module went through major refactoring so it could be ready for production usage:
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # schematox
1
+ # Schematox
2
2
 
3
3
  Schematox is a lightweight typesafe schema defined parser. All schemas are JSON compatible.
4
4
 
@@ -6,25 +6,28 @@ The library is focusing on fixed set of schema types: boolean, literal, number,
6
6
 
7
7
  Library supports static schema definition which means your schemas could be completely independent from schematox. One could use such schemas as source for generation other structures like DB models.
8
8
 
9
- The library is small so exploring README.md is enough for understanding its API, checkout [examples](#example-for-all-supported-schema-types) and you good to go:
9
+ The library is small so exploring README.md is enough for understanding its API, checkout [examples](#quick-start) and you good to go:
10
10
 
11
11
  - [Install](#install)
12
- - [Minimal requirements](#minimal-requirements)
12
+ - [Minimal Requirements](#minimal-requirements)
13
13
  - [Features](#features)
14
- - [Static schema example](#static-schema-example)
15
- - [Programmatic schema example](#programmatic-schema-example)
16
- - [Example for all supported schema types](#example-for-all-supported-schema-types)
14
+ - [Quick Start](#quick-start)
15
+ - [Static Schema](#static-schema)
16
+ - [Struct](#struct)
17
+ - [Construct](#construct)
18
+ - [Primitive Schema](#primitive-schema)
17
19
  - [Boolean](#boolean)
18
20
  - [Literal](#literal)
19
21
  - [Number](#number)
20
22
  - [String](#string)
23
+ - [Compound Schema](#compound-schema)
21
24
  - [Array](#array)
22
25
  - [Object](#object)
23
26
  - [Record](#record)
24
27
  - [Tuple](#tuple)
25
28
  - [Union](#union)
26
- - [Schema parameters](#schema-parameters)
27
- - [Error shape](#error-shape)
29
+ - [Schema Parameters](#schema-parameters)
30
+ - [Error Shape](#error-shape)
28
31
 
29
32
  ## Install
30
33
 
@@ -32,24 +35,35 @@ The library is small so exploring README.md is enough for understanding its API,
32
35
  npm install schematox
33
36
  ```
34
37
 
35
- ## Minimal requirements
38
+ ## Minimal Requirements
36
39
 
37
40
  - ECMAScript version: `2018`
38
41
  - TypeScript version: `5.3.2`
39
42
 
40
43
  ## Features
41
44
 
42
- - Statically defined JSON compatible schema
43
- - Programmatically defined schema (struct)
44
- - Check defined schema correctness using non generic type "Schema"
45
+ - Statically defined **JSON** compatible schema
46
+ - Programmatically defined schema (**struct**, **construct**)
47
+ - Check defined schema correctness using non generic type **Schema**
45
48
  - Ether-style error handling (no unexpected throws)
46
49
  - First-class support for branded primitives (primitive nominal types alias)
47
50
  - Construct type requirement for schema itself using exposed type generics
51
+ - Support the [standard schema](https://standardschema.dev) - a common interface for TypeScript validation libraries
48
52
 
53
+ ## Quick Start
49
54
 
50
- ## Static schema example
55
+ One can use three ways of schema definition using `schematox` library:
51
56
 
52
- Statically defined schema:
57
+ - **Static**: a JSON-compatible object that structurally conforms to the `Schema` type
58
+ - **Struct**: is commonly accepted way of schema definition, as seen in [zod](https://github.com/colinhacks/zod) or [superstruct](https://github.com/ianstormtaylor/superstruct)
59
+ - **Construct**: use `makeStruct` function to get `struct` from `static` schema
60
+
61
+ All **programmatically defined** (`struct`, `construct`) schemas are eventually based on `static`, which could be accessed by `__schema` key. All schemas must be immutable constants and should not be mutated by the user. Each application of `struct` parameters related to schema update will create shallow copy of the original schema.
62
+
63
+ ### Static schema
64
+
65
+ A JSON-compatible object that structurally conforms to the `Schema` type.
66
+ The `satisfies Schema` check is optional, structurally valid schema will be accepted by the parser.
53
67
 
54
68
  ```typescript
55
69
  import { parse } from 'schematox'
@@ -89,9 +103,9 @@ parsed.data
89
103
  // ^? User
90
104
  ```
91
105
 
92
- ## Programmatic schema example
106
+ ### Struct
93
107
 
94
- Same schema but defined programmatically:
108
+ Is commonly accepted way of schema, as seen in [zod](https://github.com/colinhacks/zod) or [superstruct](https://github.com/ianstormtaylor/superstruct) library:
95
109
 
96
110
  ```typescript
97
111
  import { object, string } from 'schematox'
@@ -125,10 +139,7 @@ parsed.data
125
139
  // ^? User
126
140
  ```
127
141
 
128
- All programmatically defined schemas are the same as static, one just needs to access it through `__schema` key.
129
- A statically defined schema can be transformed to a struct using the `makeStruct` utility function and can have custom props.
130
-
131
- ## Transform static schema into struct
142
+ ### Construct
132
143
 
133
144
  ```typescript
134
145
  import { makeStruct } from 'schematox'
@@ -138,14 +149,9 @@ const schema = { type: 'string' } as const satisfies Schema
138
149
  const string = makeStruct(schema)
139
150
  ```
140
151
 
141
- ## Example for all supported schema types
152
+ ## Primitive Schema
142
153
 
143
- We distinguish two main categories of schema units:
144
-
145
- - primitive: boolean, literal, number, boolean
146
- - compound: array, object, record, union
147
-
148
- Any schema share optional/nullable/description parameters. Any compound schema could have any other schema type as its member including itself. Any primitive schema can have "brand" parameter.
154
+ Any schema share optional/nullable/description/brand parameters.
149
155
 
150
156
  ### Boolean
151
157
 
@@ -249,6 +255,10 @@ type FromStruct = Infer<typeof struct>
249
255
  //
250
256
  ```
251
257
 
258
+ ## Compound Schema
259
+
260
+ Any compound schema could have any other schema type as its member including itself.
261
+
252
262
  ### Array
253
263
 
254
264
  ```typescript
@@ -372,15 +382,15 @@ type FromSchema = Infer<typeof schema>
372
382
  type FromStruct = Infer<typeof struct>
373
383
  ```
374
384
 
375
- ## Schema parameters
385
+ ## Schema Parameters
376
386
 
377
- - `optional?: boolean` –  unionize with `undefined`: `{ type: 'string', optinoal: true }` result in `string | undefined`
387
+ - `optional?: boolean` – unionize with `undefined`: `{ type: 'string', optinoal: true }` result in `string | undefined`
378
388
  - `nullable?: boolean` – unionize with `null`: `{ type: 'string', nullable: true }` result in `string | null`
379
- - `brand?: [string, string]` – make primitive type nominal "['idFor', 'User'] -> T & { \_\_idFor: 'User' }"
380
- - `minLength/maxLength/min/max` – schema type dependent limiting characteristics
381
- - `description?: string` – description of the particular schema property which can be used to provide more detailed information for the user/developer on validation/parse error
389
+ - `brand?: [string, unknown]` – make primitive type nominal "['idFor', 'User'] -> T & { \_\_idFor: 'User' }"
390
+ - `minLength/maxLength/min/max` – schema type dependent limiting characteristics
391
+ - `description?: string` – description of the particular schema property which can be used to provide more detailed information for the user/developer on validation/parse error
382
392
 
383
- ## Error shape
393
+ ## Error Shape
384
394
 
385
395
  Nested schema example. Subject `0` is invalid, should be a `string`:
386
396
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schematox",
3
- "version": "1.0.0",
3
+ "version": "1.1.0-alpha",
4
4
  "scripts": {
5
5
  "prepublishOnly": "bash release-check.sh",
6
6
  "test": "jest",
package/src/constants.ts CHANGED
@@ -16,3 +16,8 @@ export const PARAMS_BY_SCHEMA_TYPE = {
16
16
  tuple: new Set(['optional', 'nullable', 'description'] as const),
17
17
  union: new Set(['optional', 'nullable', 'description'] as const),
18
18
  } as const
19
+
20
+ export const STANDARD_SCHEMA = {
21
+ version: 1,
22
+ vendor: 'schematox',
23
+ } as const
package/src/struct.ts CHANGED
@@ -1,15 +1,31 @@
1
- import { PARAMS_BY_SCHEMA_TYPE } from './constants'
1
+ import { PARAMS_BY_SCHEMA_TYPE, STANDARD_SCHEMA } from './constants'
2
2
  import { parse } from './parse'
3
3
 
4
- import type { Schema, StringSchema } from './types/schema'
4
+ import type { StandardSchemaV1 } from './types/standard-schema'
5
+ import type { Schema, BrandSchema, StringSchema } from './types/schema'
5
6
  import type { Struct, StructParams, StructShape } from './types/struct'
6
7
 
7
8
  export function makeStruct<T extends Schema>(schema: T): Struct<T>
8
9
  export function makeStruct(schema: Schema) {
9
10
  const params = PARAMS_BY_SCHEMA_TYPE[schema.type] as Set<StructParams>
10
- const result: Record<string, unknown> = {
11
+ const result: Record<string, unknown> & StandardSchemaV1 = {
11
12
  __schema: { ...schema },
12
13
  parse: (subj: unknown) => parse(schema as never, subj),
14
+ ['~standard']: {
15
+ ...STANDARD_SCHEMA,
16
+ validate: (input) => {
17
+ const parsed = parse(schema as never, input)
18
+
19
+ return parsed.success
20
+ ? { value: parsed.data }
21
+ : {
22
+ issues: parsed.error.map((x) => ({
23
+ path: x.path,
24
+ message: x.code,
25
+ })),
26
+ }
27
+ },
28
+ },
13
29
  }
14
30
 
15
31
  if (params.has('optional')) {
@@ -26,8 +42,12 @@ export function makeStruct(schema: Schema) {
26
42
  }
27
43
 
28
44
  if (params.has('brand')) {
29
- result.brand = (key: string, value: string) =>
30
- makeStruct({ ...schema, brand: [key, value] })
45
+ result.brand = (...args: BrandSchema | [BrandSchema]) => {
46
+ return makeStruct({
47
+ ...schema,
48
+ brand: (Array.isArray(args[0]) ? args[0] : args) as BrandSchema,
49
+ })
50
+ }
31
51
  }
32
52
 
33
53
  if (params.has('min')) {
@@ -8,9 +8,7 @@ export type Schema =
8
8
  | { type: 'tuple'; of: Array<Schema> } & SchemaShared
9
9
  | { type: 'union'; of: Array<Schema> } & SchemaShared
10
10
 
11
- export type BrandSchema<T = string, U = unknown> = Readonly<
12
- [category: T, subCategory: U]
13
- >
11
+ export type BrandSchema<T = string, U = unknown> = Readonly<[T, U]>
14
12
 
15
13
  export type SchemaShared = {
16
14
  /**
@@ -0,0 +1,50 @@
1
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
2
+ /** Source: https://github.com/standard-schema/standard-schema/blob/main/packages/spec/src/index.ts */
3
+ readonly '~standard': StandardSchemaV1.Props<Input, Output>
4
+ }
5
+
6
+ export declare namespace StandardSchemaV1 {
7
+ export interface Props<Input = unknown, Output = Input> {
8
+ readonly version: 1
9
+ readonly vendor: string
10
+ readonly validate: (
11
+ value: unknown
12
+ ) => Result<Output> | Promise<Result<Output>>
13
+ readonly types?: Types<Input, Output> | undefined
14
+ }
15
+
16
+ export type Result<Output> = SuccessResult<Output> | FailureResult
17
+
18
+ export interface SuccessResult<Output> {
19
+ readonly value: Output
20
+ readonly issues?: undefined
21
+ }
22
+
23
+ export interface FailureResult {
24
+ readonly issues: ReadonlyArray<Issue>
25
+ }
26
+
27
+ export interface Issue {
28
+ readonly message: string
29
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined
30
+ }
31
+
32
+ export interface PathSegment {
33
+ readonly key: PropertyKey
34
+ }
35
+
36
+ export interface Types<Input = unknown, Output = Input> {
37
+ readonly input: Input
38
+ readonly output: Output
39
+ }
40
+
41
+ export type InferInput<Schema extends StandardSchemaV1> = NonNullable<
42
+ Schema['~standard']['types']
43
+ >['input']
44
+
45
+ export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<
46
+ Schema['~standard']['types']
47
+ >['output']
48
+
49
+ export {}
50
+ }
@@ -1,5 +1,6 @@
1
1
  import { ParseResult } from './utils'
2
2
 
3
+ import type { StandardSchemaV1 } from './standard-schema'
3
4
  import type {
4
5
  Schema,
5
6
  //
@@ -9,6 +10,8 @@ import type {
9
10
  TupleSchema,
10
11
  UnionSchema,
11
12
  //
13
+ BrandSchema,
14
+ //
12
15
  BooleanSchema,
13
16
  LiteralSchema,
14
17
  NumberSchema,
@@ -23,10 +26,19 @@ export type Struct<T extends Schema> = Omit<
23
26
  optional: () => Struct<T & { optional: true }>
24
27
  nullable: () => Struct<T & { nullable: true }>
25
28
 
26
- brand: <V extends string, W extends string>(
27
- key: V,
28
- value: W
29
- ) => Struct<T & { brand: Readonly<[V, W]> }>
29
+ brand: <
30
+ U extends [string, BrandSubType] | [Readonly<[string, BrandSubType]>],
31
+ >(
32
+ ...args: U
33
+ ) => Struct<
34
+ T & {
35
+ brand: U extends [infer V, infer W]
36
+ ? BrandSchema<V, W>
37
+ : U extends [Readonly<[infer V, infer W]>]
38
+ ? BrandSchema<V, W>
39
+ : never
40
+ }
41
+ >
30
42
 
31
43
  minLength: <U extends number>(
32
44
  minLength: U
@@ -48,7 +60,14 @@ export type Struct<T extends Schema> = Omit<
48
60
  > & {
49
61
  __schema: Readonly<T>
50
62
  parse: (s: unknown) => ParseResult<InferSchema<T>>
51
- }
63
+ } & StandardSchemaV1<unknown, InferSchema<T>>
64
+
65
+ type BrandSubType =
66
+ | boolean
67
+ | number
68
+ | string
69
+ | ReadonlyArray<unknown>
70
+ | Record<string, unknown>
52
71
 
53
72
  export type StructShape<T> = { __schema: T }
54
73