schematox 1.0.1 → 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/README.md +42 -34
- package/package.json +1 -1
- package/src/constants.ts +5 -0
- package/src/struct.ts +25 -5
- package/src/types/standard-schema.ts +50 -0
- package/src/types/struct.ts +20 -11
package/README.md
CHANGED
|
@@ -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](#
|
|
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
|
|
12
|
+
- [Minimal Requirements](#minimal-requirements)
|
|
13
13
|
- [Features](#features)
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
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
|
|
27
|
-
- [Error
|
|
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
|
|
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
|
|
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
|
-
|
|
55
|
+
One can use three ways of schema definition using `schematox` library:
|
|
51
56
|
|
|
52
|
-
|
|
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
|
-
|
|
106
|
+
### Struct
|
|
93
107
|
|
|
94
|
-
|
|
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
|
-
|
|
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,16 +149,9 @@ const schema = { type: 'string' } as const satisfies Schema
|
|
|
138
149
|
const string = makeStruct(schema)
|
|
139
150
|
```
|
|
140
151
|
|
|
141
|
-
##
|
|
142
|
-
|
|
143
|
-
We distinguish two main categories of schema units:
|
|
152
|
+
## Primitive Schema
|
|
144
153
|
|
|
145
|
-
|
|
146
|
-
- compound: array, object, record, tuple, union
|
|
147
|
-
|
|
148
|
-
Any schema share optional/nullable/description parameters.
|
|
149
|
-
Any primitive schema can have "brand" parameter.
|
|
150
|
-
Any compound schema could have any other schema type as its member including itself.
|
|
154
|
+
Any schema share optional/nullable/description/brand parameters.
|
|
151
155
|
|
|
152
156
|
### Boolean
|
|
153
157
|
|
|
@@ -251,6 +255,10 @@ type FromStruct = Infer<typeof struct>
|
|
|
251
255
|
//
|
|
252
256
|
```
|
|
253
257
|
|
|
258
|
+
## Compound Schema
|
|
259
|
+
|
|
260
|
+
Any compound schema could have any other schema type as its member including itself.
|
|
261
|
+
|
|
254
262
|
### Array
|
|
255
263
|
|
|
256
264
|
```typescript
|
|
@@ -374,15 +382,15 @@ type FromSchema = Infer<typeof schema>
|
|
|
374
382
|
type FromStruct = Infer<typeof struct>
|
|
375
383
|
```
|
|
376
384
|
|
|
377
|
-
## Schema
|
|
385
|
+
## Schema Parameters
|
|
378
386
|
|
|
379
|
-
- `optional?: boolean` –
|
|
387
|
+
- `optional?: boolean` – unionize with `undefined`: `{ type: 'string', optinoal: true }` result in `string | undefined`
|
|
380
388
|
- `nullable?: boolean` – unionize with `null`: `{ type: 'string', nullable: true }` result in `string | null`
|
|
381
|
-
- `brand?: [string,
|
|
382
|
-
- `minLength/maxLength/min/max` –
|
|
383
|
-
- `description?: string` –
|
|
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
|
|
384
392
|
|
|
385
|
-
## Error
|
|
393
|
+
## Error Shape
|
|
386
394
|
|
|
387
395
|
Nested schema example. Subject `0` is invalid, should be a `string`:
|
|
388
396
|
|
package/package.json
CHANGED
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 {
|
|
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 = (
|
|
30
|
-
makeStruct({
|
|
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')) {
|
|
@@ -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
|
+
}
|
package/src/types/struct.ts
CHANGED
|
@@ -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
|
//
|
|
@@ -26,17 +27,18 @@ export type Struct<T extends Schema> = Omit<
|
|
|
26
27
|
nullable: () => Struct<T & { nullable: true }>
|
|
27
28
|
|
|
28
29
|
brand: <
|
|
29
|
-
U extends string,
|
|
30
|
-
V extends
|
|
31
|
-
| boolean
|
|
32
|
-
| number
|
|
33
|
-
| string
|
|
34
|
-
| ReadonlyArray<unknown>
|
|
35
|
-
| Record<string, unknown>,
|
|
30
|
+
U extends [string, BrandSubType] | [Readonly<[string, BrandSubType]>],
|
|
36
31
|
>(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
+
>
|
|
40
42
|
|
|
41
43
|
minLength: <U extends number>(
|
|
42
44
|
minLength: U
|
|
@@ -58,7 +60,14 @@ export type Struct<T extends Schema> = Omit<
|
|
|
58
60
|
> & {
|
|
59
61
|
__schema: Readonly<T>
|
|
60
62
|
parse: (s: unknown) => ParseResult<InferSchema<T>>
|
|
61
|
-
}
|
|
63
|
+
} & StandardSchemaV1<unknown, InferSchema<T>>
|
|
64
|
+
|
|
65
|
+
type BrandSubType =
|
|
66
|
+
| boolean
|
|
67
|
+
| number
|
|
68
|
+
| string
|
|
69
|
+
| ReadonlyArray<unknown>
|
|
70
|
+
| Record<string, unknown>
|
|
62
71
|
|
|
63
72
|
export type StructShape<T> = { __schema: T }
|
|
64
73
|
|