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 +4 -0
- package/README.md +43 -33
- package/package.json +1 -1
- package/src/constants.ts +5 -0
- package/src/struct.ts +25 -5
- package/src/types/schema.ts +1 -3
- package/src/types/standard-schema.ts +50 -0
- package/src/types/struct.ts +24 -5
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
|
-
#
|
|
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](#
|
|
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,14 +149,9 @@ const schema = { type: 'string' } as const satisfies Schema
|
|
|
138
149
|
const string = makeStruct(schema)
|
|
139
150
|
```
|
|
140
151
|
|
|
141
|
-
##
|
|
152
|
+
## Primitive Schema
|
|
142
153
|
|
|
143
|
-
|
|
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
|
|
385
|
+
## Schema Parameters
|
|
376
386
|
|
|
377
|
-
- `optional?: boolean` –
|
|
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,
|
|
380
|
-
- `minLength/maxLength/min/max` –
|
|
381
|
-
- `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
|
|
382
392
|
|
|
383
|
-
## Error
|
|
393
|
+
## Error Shape
|
|
384
394
|
|
|
385
395
|
Nested schema example. Subject `0` is invalid, should be a `string`:
|
|
386
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')) {
|
package/src/types/schema.ts
CHANGED
|
@@ -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
|
+
}
|
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
|
//
|
|
@@ -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: <
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|