@fncts/schema 0.0.5 → 0.0.6
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/AST.d.ts +528 -0
- package/ASTAnnotation.d.ts +82 -0
- package/ASTAnnotationMap.d.ts +14 -0
- package/Gen.d.ts +15 -0
- package/InvalidInterpretationError.d.ts +5 -0
- package/ParseError.d.ts +107 -0
- package/ParseResult.d.ts +24 -0
- package/Parser/api.d.ts +42 -0
- package/Parser/definition.d.ts +22 -0
- package/Parser/interpreter.d.ts +6 -0
- package/Parser.d.ts +2 -0
- package/Schema/api/conc.d.ts +18 -0
- package/Schema/api/hashMap.d.ts +21 -0
- package/Schema/api/immutableArray.d.ts +23 -0
- package/Schema/api/list.d.ts +23 -0
- package/Schema/api/maybe.d.ts +21 -0
- package/Schema/api.d.ts +243 -0
- package/Schema/definition.d.ts +29 -0
- package/Schema/derivations.d.ts +70 -0
- package/Schema.d.ts +2 -144
- package/_cjs/AST.cjs +1171 -0
- package/_cjs/AST.cjs.map +1 -0
- package/_cjs/ASTAnnotation.cjs +111 -0
- package/_cjs/ASTAnnotation.cjs.map +1 -0
- package/_cjs/ASTAnnotationMap.cjs +35 -0
- package/_cjs/ASTAnnotationMap.cjs.map +1 -0
- package/_cjs/Gen.cjs +185 -0
- package/_cjs/Gen.cjs.map +1 -0
- package/_cjs/InvalidInterpretationError.cjs +18 -0
- package/_cjs/InvalidInterpretationError.cjs.map +1 -0
- package/_cjs/ParseError.cjs +222 -0
- package/_cjs/ParseError.cjs.map +1 -0
- package/_cjs/{Decoder.cjs → ParseResult.cjs} +24 -22
- package/_cjs/ParseResult.cjs.map +1 -0
- package/_cjs/Parser/api.cjs +80 -0
- package/_cjs/Parser/api.cjs.map +1 -0
- package/_cjs/{Guard.cjs → Parser/definition.cjs} +17 -22
- package/_cjs/Parser/definition.cjs.map +1 -0
- package/_cjs/Parser/interpreter.cjs +409 -0
- package/_cjs/Parser/interpreter.cjs.map +1 -0
- package/_cjs/Parser.cjs +28 -0
- package/_cjs/Parser.cjs.map +1 -0
- package/_cjs/Schema/api/conc.cjs +84 -0
- package/_cjs/Schema/api/conc.cjs.map +1 -0
- package/_cjs/Schema/api/hashMap.cjs +161 -0
- package/_cjs/Schema/api/hashMap.cjs.map +1 -0
- package/_cjs/Schema/api/immutableArray.cjs +90 -0
- package/_cjs/Schema/api/immutableArray.cjs.map +1 -0
- package/_cjs/Schema/api/list.cjs +98 -0
- package/_cjs/Schema/api/list.cjs.map +1 -0
- package/_cjs/Schema/api/maybe.cjs +75 -0
- package/_cjs/Schema/api/maybe.cjs.map +1 -0
- package/_cjs/Schema/api.cjs +424 -0
- package/_cjs/Schema/api.cjs.map +1 -0
- package/_cjs/Schema/definition.cjs +26 -0
- package/_cjs/Schema/definition.cjs.map +1 -0
- package/_cjs/Schema/derivations.cjs +108 -0
- package/_cjs/Schema/derivations.cjs.map +1 -0
- package/_cjs/Schema.cjs +20 -237
- package/_cjs/Schema.cjs.map +1 -1
- package/_cjs/utils.cjs +52 -0
- package/_cjs/utils.cjs.map +1 -0
- package/_mjs/AST.mjs +1060 -0
- package/_mjs/AST.mjs.map +1 -0
- package/_mjs/ASTAnnotation.mjs +80 -0
- package/_mjs/ASTAnnotation.mjs.map +1 -0
- package/_mjs/ASTAnnotationMap.mjs +27 -0
- package/_mjs/ASTAnnotationMap.mjs.map +1 -0
- package/_mjs/Gen.mjs +176 -0
- package/_mjs/Gen.mjs.map +1 -0
- package/_mjs/InvalidInterpretationError.mjs +10 -0
- package/_mjs/InvalidInterpretationError.mjs.map +1 -0
- package/_mjs/ParseError.mjs +200 -0
- package/_mjs/ParseError.mjs.map +1 -0
- package/_mjs/ParseResult.mjs +21 -0
- package/_mjs/ParseResult.mjs.map +1 -0
- package/_mjs/Parser/api.mjs +67 -0
- package/_mjs/Parser/api.mjs.map +1 -0
- package/_mjs/Parser/definition.mjs +15 -0
- package/_mjs/Parser/definition.mjs.map +1 -0
- package/_mjs/Parser/interpreter.mjs +401 -0
- package/_mjs/Parser/interpreter.mjs.map +1 -0
- package/_mjs/Parser.mjs +4 -0
- package/_mjs/Parser.mjs.map +1 -0
- package/_mjs/Schema/api/conc.mjs +72 -0
- package/_mjs/Schema/api/conc.mjs.map +1 -0
- package/_mjs/Schema/api/hashMap.mjs +150 -0
- package/_mjs/Schema/api/hashMap.mjs.map +1 -0
- package/_mjs/Schema/api/immutableArray.mjs +79 -0
- package/_mjs/Schema/api/immutableArray.mjs.map +1 -0
- package/_mjs/Schema/api/list.mjs +87 -0
- package/_mjs/Schema/api/list.mjs.map +1 -0
- package/_mjs/Schema/api/maybe.mjs +64 -0
- package/_mjs/Schema/api/maybe.mjs.map +1 -0
- package/_mjs/Schema/api.mjs +367 -0
- package/_mjs/Schema/api.mjs.map +1 -0
- package/_mjs/Schema/definition.mjs +16 -0
- package/_mjs/Schema/definition.mjs.map +1 -0
- package/_mjs/Schema/derivations.mjs +94 -0
- package/_mjs/Schema/derivations.mjs.map +1 -0
- package/_mjs/Schema.mjs +3 -212
- package/_mjs/Schema.mjs.map +1 -1
- package/_mjs/utils.mjs +41 -0
- package/_mjs/utils.mjs.map +1 -0
- package/_src/AST.ts +1353 -0
- package/_src/ASTAnnotation.ts +98 -0
- package/_src/ASTAnnotationMap.ts +38 -0
- package/_src/Gen.ts +171 -0
- package/_src/InvalidInterpretationError.ts +6 -0
- package/_src/ParseError.ts +237 -0
- package/_src/ParseResult.ts +26 -0
- package/_src/Parser/api.ts +71 -0
- package/_src/Parser/definition.ts +24 -0
- package/_src/Parser/interpreter.ts +442 -0
- package/_src/Parser.ts +4 -0
- package/_src/Schema/api/conc.ts +78 -0
- package/_src/Schema/api/hashMap.ts +184 -0
- package/_src/Schema/api/immutableArray.ts +88 -0
- package/_src/Schema/api/list.ts +96 -0
- package/_src/Schema/api/maybe.ts +68 -0
- package/_src/Schema/api.ts +530 -0
- package/_src/Schema/definition.ts +32 -0
- package/_src/Schema/derivations.ts +185 -0
- package/_src/Schema.ts +4 -254
- package/_src/global.ts +53 -0
- package/_src/utils.ts +48 -0
- package/global.d.ts +52 -0
- package/package.json +2 -2
- package/utils.d.ts +8 -0
- package/Decoder.d.ts +0 -3
- package/Encoder.d.ts +0 -4
- package/Guard.d.ts +0 -3
- package/Schemable.d.ts +0 -39
- package/_cjs/Decoder.cjs.map +0 -1
- package/_cjs/Encoder.cjs +0 -45
- package/_cjs/Encoder.cjs.map +0 -1
- package/_cjs/Guard.cjs.map +0 -1
- package/_cjs/Schemable.cjs +0 -6
- package/_cjs/Schemable.cjs.map +0 -1
- package/_mjs/Decoder.mjs +0 -20
- package/_mjs/Decoder.mjs.map +0 -1
- package/_mjs/Encoder.mjs +0 -36
- package/_mjs/Encoder.mjs.map +0 -1
- package/_mjs/Guard.mjs +0 -20
- package/_mjs/Guard.mjs.map +0 -1
- package/_mjs/Schemable.mjs +0 -2
- package/_mjs/Schemable.mjs.map +0 -1
- package/_src/Decoder.ts +0 -20
- package/_src/Encoder.ts +0 -38
- package/_src/Guard.ts +0 -20
- package/_src/Schemable.ts +0 -46
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import type { Literal, ParseOptions, TemplateLiteral, TypeLiteral } from "../AST.js";
|
|
2
|
+
import type { Brand, Validation } from "@fncts/base/data/Branded";
|
|
3
|
+
import type { Check } from "@fncts/typelevel";
|
|
4
|
+
import type { OptionalKeys, RequiredKeys } from "@fncts/typelevel/Object";
|
|
5
|
+
|
|
6
|
+
import { show } from "@fncts/base/data/Showable";
|
|
7
|
+
|
|
8
|
+
import { getParameter } from "../AST.js";
|
|
9
|
+
import { ASTTag, concrete, TemplateLiteralSpan } from "../AST.js";
|
|
10
|
+
import { ownKeys } from "../utils.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @tsplus static fncts.schema.SchemaOps fromAST
|
|
14
|
+
*/
|
|
15
|
+
export function make<A>(ast: AST): Schema<A> {
|
|
16
|
+
return new Schema(ast);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @tsplus pipeable fncts.schema.Schema annotate
|
|
21
|
+
*/
|
|
22
|
+
export function annotate<V>(annotation: ASTAnnotation<V>, value: V) {
|
|
23
|
+
return <A>(self: Schema<A>): Schema<A> => {
|
|
24
|
+
return Schema.fromAST(self.ast.clone({ annotations: self.ast.annotations.annotate(annotation, value) }));
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @tsplus static fncts.schema.SchemaOps declaration
|
|
30
|
+
*/
|
|
31
|
+
export function declaration(
|
|
32
|
+
typeParameters: Vector<Schema<any>>,
|
|
33
|
+
type: Schema<any>,
|
|
34
|
+
decode: (...typeParameters: ReadonlyArray<Schema<any>>) => Parser<any>,
|
|
35
|
+
annotations?: ASTAnnotationMap,
|
|
36
|
+
): Schema<any> {
|
|
37
|
+
return Schema.fromAST(
|
|
38
|
+
AST.createDeclaration(
|
|
39
|
+
typeParameters.map((tp) => tp.ast),
|
|
40
|
+
type.ast,
|
|
41
|
+
(...typeParameters) => decode(...typeParameters.map(Schema.fromAST)),
|
|
42
|
+
annotations,
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @tsplus pipeable fncts.schema.Schema filter
|
|
49
|
+
*/
|
|
50
|
+
export function filter<A, B extends A>(refinement: Refinement<A, B>): (self: Schema<A>) => Schema<B>;
|
|
51
|
+
export function filter<A>(predicate: Predicate<A>): (self: Schema<A>) => Schema<A>;
|
|
52
|
+
export function filter<A>(predicate: Predicate<A>) {
|
|
53
|
+
return (self: Schema<A>): Schema<A> => {
|
|
54
|
+
const ast: AST = AST.createRefinement(
|
|
55
|
+
self.ast,
|
|
56
|
+
(a: A) => (predicate(a) ? ParseResult.succeed(a) : ParseResult.fail(ParseError.TypeError(ast, a))),
|
|
57
|
+
false,
|
|
58
|
+
);
|
|
59
|
+
return Schema.fromAST(ast);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @tsplus pipeable fncts.schema.Schema brand
|
|
65
|
+
*/
|
|
66
|
+
export function brand<A, K extends string>(validation: Validation<A, K>) {
|
|
67
|
+
return (self: Schema<A>): Schema<A & Brand.Valid<A, K>> => {
|
|
68
|
+
const ast = AST.createRefinement(
|
|
69
|
+
self.ast,
|
|
70
|
+
(a: A) => (validation.validate(a) ? ParseResult.succeed(a) : ParseResult.fail(ParseError.TypeError(ast, a))),
|
|
71
|
+
false,
|
|
72
|
+
self.ast.annotations.annotate(ASTAnnotation.Brand, Vector(validation)),
|
|
73
|
+
);
|
|
74
|
+
return Schema.fromAST(ast);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function makeLiteral<Literal extends LiteralValue>(value: Literal): Schema<Literal> {
|
|
79
|
+
return Schema.fromAST(AST.createLiteral(value));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @tsplus static fncts.schema.SchemaOps literal
|
|
84
|
+
*/
|
|
85
|
+
export function literal<Literals extends ReadonlyArray<LiteralValue>>(...literals: Literals): Schema<Literals[number]> {
|
|
86
|
+
return Schema.union(...literals.map(makeLiteral));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @tsplus static fncts.schema.SchemaOps never
|
|
91
|
+
* @tsplus implicit
|
|
92
|
+
*/
|
|
93
|
+
export const never: Schema<never> = Schema.fromAST(AST.neverKeyword);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @tsplus static fncts.schema.SchemaOps unknown
|
|
97
|
+
* @tsplus implicit
|
|
98
|
+
*/
|
|
99
|
+
export const unknown: Schema<unknown> = Schema.fromAST(AST.unknownKeyword);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @tsplus static fncts.schema.SchemaOps any
|
|
103
|
+
*/
|
|
104
|
+
export const any: Schema<any> = Schema.fromAST(AST.anyKeyword);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @tsplus static fncts.schema.SchemaOps undefined
|
|
108
|
+
* @tsplus implicit
|
|
109
|
+
*/
|
|
110
|
+
export const _undefined: Schema<undefined> = Schema.fromAST(AST.undefinedKeyword);
|
|
111
|
+
|
|
112
|
+
export { _undefined as undefined };
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @tsplus static fncts.schema.SchemaOps null
|
|
116
|
+
* @tsplus implicit
|
|
117
|
+
*/
|
|
118
|
+
export const _null: Schema<null> = Schema.fromAST(AST.createLiteral(null));
|
|
119
|
+
|
|
120
|
+
export { _null as null };
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @tsplus static fncts.schema.SchemaOps void
|
|
124
|
+
* @tsplus implicit
|
|
125
|
+
*/
|
|
126
|
+
export const _void: Schema<void> = Schema.fromAST(AST.voidKeyword);
|
|
127
|
+
|
|
128
|
+
export { _void as void };
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @tsplus static fncts.schema.SchemaOps string
|
|
132
|
+
* @tsplus implicit
|
|
133
|
+
*/
|
|
134
|
+
export const string: Schema<string> = Schema.fromAST(AST.stringKeyword);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @tsplus static fncts.schema.SchemaOps number
|
|
138
|
+
* @tsplus implicit
|
|
139
|
+
*/
|
|
140
|
+
export const number: Schema<number> = Schema.fromAST(AST.numberKeyword);
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @tsplus static fncts.schema.SchemaOps boolean
|
|
144
|
+
* @tsplus implicit
|
|
145
|
+
*/
|
|
146
|
+
export const boolean: Schema<boolean> = Schema.fromAST(AST.booleanKeyword);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @tsplus static fncts.schema.SchemaOps bigint
|
|
150
|
+
* @tsplus implicit
|
|
151
|
+
*/
|
|
152
|
+
export const bigint: Schema<bigint> = Schema.fromAST(AST.bigIntKeyword);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @tsplus static fncts.schema.SchemaOps symbol
|
|
156
|
+
* @tsplus implicit
|
|
157
|
+
*/
|
|
158
|
+
export const symbol: Schema<symbol> = Schema.fromAST(AST.symbolKeyword);
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @tsplus static fncts.schema.SchemaOps object
|
|
162
|
+
* @tsplus implicit
|
|
163
|
+
*/
|
|
164
|
+
export const object: Schema<object> = Schema.fromAST(AST.objectKeyword);
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @tsplus static fncts.schema.SchemaOps union
|
|
168
|
+
* @tsplus derive fncts.schema.Schema<|> 30
|
|
169
|
+
*/
|
|
170
|
+
export function union<A extends ReadonlyArray<unknown>>(
|
|
171
|
+
...members: {
|
|
172
|
+
[K in keyof A]: Schema<A[K]>;
|
|
173
|
+
}
|
|
174
|
+
): Schema<A[number]> {
|
|
175
|
+
return Schema.fromAST(AST.createUnion(Vector.from(members.map((m) => m.ast))));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @tsplus getter fncts.schema.Schema nullable
|
|
180
|
+
*/
|
|
181
|
+
export function nullable<A>(self: Schema<A>): Schema<A | null> {
|
|
182
|
+
return Schema.union(Schema.null, self);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @tsplus static fncts.schema.SchemaOps uniqueSymbol
|
|
187
|
+
*/
|
|
188
|
+
export function uniqueSymbol<S extends symbol>(symbol: S, annotations?: ASTAnnotationMap): Schema<S> {
|
|
189
|
+
return Schema.fromAST(AST.createUniqueSymbol(symbol, annotations));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @tsplus getter fncts.schema.Schema optional
|
|
194
|
+
*/
|
|
195
|
+
export function optional<A>(self: Schema<A>): OptionalSchema<A> {
|
|
196
|
+
return Schema.fromAST(
|
|
197
|
+
self.ast.clone({ annotations: self.ast.annotations.annotate(ASTAnnotation.Optional, true) }),
|
|
198
|
+
) as OptionalSchema<A>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @tsplus fluent fncts.schema.Schema isOptional
|
|
203
|
+
*/
|
|
204
|
+
export function isOptional<A>(self: Schema<A>): self is OptionalSchema<A> {
|
|
205
|
+
return self.ast.annotations.get(ASTAnnotation.Optional).getOrElse(false);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export type OptionalSchemaKeys<T> = { [K in keyof T]: T[K] extends OptionalSchema<any> ? K : never }[keyof T];
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @tsplus getter fncts.schema.Schema parseOptional
|
|
212
|
+
*/
|
|
213
|
+
export function parseOptional<A>(self: Schema<A>): Schema<Maybe<A>> {
|
|
214
|
+
return Schema.fromAST(
|
|
215
|
+
self.ast.clone({ annotations: self.ast.annotations.annotate(ASTAnnotation.ParseOptional, true) }),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @tsplus fluent fncts.schema.Schema isParseOptional
|
|
221
|
+
*/
|
|
222
|
+
export function isParseOptional<A>(self: Schema<A>): boolean {
|
|
223
|
+
return self.ast.annotations.get(ASTAnnotation.ParseOptional).getOrElse(false);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export type Spread<A> = {
|
|
227
|
+
[K in keyof A]: A[K];
|
|
228
|
+
} extends infer B
|
|
229
|
+
? B
|
|
230
|
+
: never;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @tsplus static fncts.schema.SchemaOps struct
|
|
234
|
+
*/
|
|
235
|
+
export function struct<Fields extends Record<PropertyKey, Schema<any>>>(
|
|
236
|
+
fields: Fields,
|
|
237
|
+
): Schema<
|
|
238
|
+
Spread<
|
|
239
|
+
{ readonly [K in Exclude<keyof Fields, OptionalSchemaKeys<Fields>>]: Schema.Infer<Fields[K]> } & {
|
|
240
|
+
readonly [K in OptionalSchemaKeys<Fields>]?: Schema.Infer<Fields[K]>;
|
|
241
|
+
}
|
|
242
|
+
>
|
|
243
|
+
> {
|
|
244
|
+
const parseOptionalKeys: Vector<PropertyKey> = ownKeys(fields).filter((key) => isParseOptional(fields[key]!));
|
|
245
|
+
const struct = Schema.fromAST(
|
|
246
|
+
AST.createTypeLiteral(
|
|
247
|
+
ownKeys(fields).map((key) => AST.createPropertySignature(key, fields[key]!.ast, isOptional(fields[key]!), true)),
|
|
248
|
+
Vector.empty(),
|
|
249
|
+
),
|
|
250
|
+
);
|
|
251
|
+
if (parseOptionalKeys.isEmpty()) {
|
|
252
|
+
return struct as Schema<any>;
|
|
253
|
+
}
|
|
254
|
+
const propertySignatures = (struct.ast as TypeLiteral).propertySignatures;
|
|
255
|
+
const from = Schema.fromAST<any>(
|
|
256
|
+
AST.createTypeLiteral(
|
|
257
|
+
propertySignatures.map((p) =>
|
|
258
|
+
parseOptionalKeys.includes(p.name)
|
|
259
|
+
? AST.createPropertySignature(
|
|
260
|
+
p.name,
|
|
261
|
+
AST.createUnion(Vector(AST.undefinedKeyword, AST.createLiteral(null), p.type)),
|
|
262
|
+
true,
|
|
263
|
+
p.isReadonly,
|
|
264
|
+
)
|
|
265
|
+
: p,
|
|
266
|
+
),
|
|
267
|
+
Vector.empty(),
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
const to = Schema.fromAST<any>(
|
|
271
|
+
AST.createTypeLiteral(
|
|
272
|
+
propertySignatures.map((p) => {
|
|
273
|
+
if (parseOptionalKeys.includes(p.name)) {
|
|
274
|
+
if (fields[p.name]!.ast.isLazy()) {
|
|
275
|
+
return AST.createPropertySignature(
|
|
276
|
+
p.name,
|
|
277
|
+
AST.createLazy(() => Schema.maybe(fields[p.name]!).ast),
|
|
278
|
+
true,
|
|
279
|
+
p.isReadonly,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return AST.createPropertySignature(p.name, Schema.maybe(fields[p.name]!).ast, true, p.isReadonly);
|
|
283
|
+
}
|
|
284
|
+
return p;
|
|
285
|
+
}),
|
|
286
|
+
Vector.empty(),
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
return from.transform(
|
|
290
|
+
to,
|
|
291
|
+
(input) => {
|
|
292
|
+
const output = { ...input };
|
|
293
|
+
for (const key of parseOptionalKeys) {
|
|
294
|
+
output[key] = Maybe.fromNullable(input[key]);
|
|
295
|
+
}
|
|
296
|
+
return output;
|
|
297
|
+
},
|
|
298
|
+
(input) => {
|
|
299
|
+
const output = { ...input };
|
|
300
|
+
for (const key of parseOptionalKeys) {
|
|
301
|
+
const value: Maybe<any> = input[key];
|
|
302
|
+
if (value.isNothing()) {
|
|
303
|
+
delete output[key];
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
output[key] = value.value;
|
|
307
|
+
}
|
|
308
|
+
return output;
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @tsplus static fncts.schema.SchemaOps tuple
|
|
315
|
+
*/
|
|
316
|
+
export function tuple<Elements extends ReadonlyArray<Schema<any>>>(
|
|
317
|
+
...elements: Elements
|
|
318
|
+
): Schema<{ readonly [K in keyof Elements]: Schema.Infer<Elements[K]> }> {
|
|
319
|
+
return Schema.fromAST(
|
|
320
|
+
AST.createTuple(Vector.from(elements.map((schema) => AST.createElement(schema.ast, false))), Nothing(), true),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* @tsplus static fncts.schema.SchemaOps lazy
|
|
326
|
+
*/
|
|
327
|
+
export function lazy<A>(f: () => Schema<A>, annotations?: ASTAnnotationMap): Schema<A> {
|
|
328
|
+
return Schema.fromAST(AST.createLazy(() => f().ast, annotations));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* @tsplus static fncts.schema.SchemaOps array
|
|
333
|
+
* @tsplus getter fncts.schema.Schema array
|
|
334
|
+
*/
|
|
335
|
+
export function array<A>(item: Schema<A>): Schema<ReadonlyArray<A>> {
|
|
336
|
+
return Schema.fromAST(AST.createTuple(Vector.empty(), Just(Vector(item.ast)), true));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @tsplus static fncts.schema.SchemaOps mutableArray
|
|
341
|
+
* @tsplus getter fncts.schema.Schema mutableArray
|
|
342
|
+
*/
|
|
343
|
+
export function mutableArray<A>(item: Schema<A>): Schema<Array<A>> {
|
|
344
|
+
return Schema.fromAST(AST.createTuple(Vector.empty(), Just(Vector(item.ast)), false));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @tsplus static fncts.schema.SchemaOps record
|
|
349
|
+
*/
|
|
350
|
+
export function record<K extends string | symbol, V>(
|
|
351
|
+
key: Schema<K>,
|
|
352
|
+
value: Schema<V>,
|
|
353
|
+
): Schema<{ readonly [k in K]: V }> {
|
|
354
|
+
return Schema.fromAST(AST.createRecord(key.ast, value.ast, true));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* @tsplus static fncts.schema.SchemaOps enum
|
|
359
|
+
*/
|
|
360
|
+
export function enum_<A extends { [x: string]: string | number }>(enums: A): Schema<A[keyof A]> {
|
|
361
|
+
return Schema.fromAST(
|
|
362
|
+
AST.createEnum(
|
|
363
|
+
Vector.from(
|
|
364
|
+
Object.keys(enums)
|
|
365
|
+
.filter((key) => typeof enums[enums[key]!] !== "number")
|
|
366
|
+
.map((key) => [key, enums[key]!]),
|
|
367
|
+
),
|
|
368
|
+
),
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export { enum_ as enum };
|
|
373
|
+
|
|
374
|
+
type Join<T> = T extends [infer Head, ...infer Tail]
|
|
375
|
+
? `${Head & (string | number | bigint | boolean | null | undefined)}${Tail extends [] ? "" : Join<Tail>}`
|
|
376
|
+
: never;
|
|
377
|
+
|
|
378
|
+
function getTemplateLiterals(ast: AST): Vector<TemplateLiteral | Literal> {
|
|
379
|
+
concrete(ast);
|
|
380
|
+
switch (ast._tag) {
|
|
381
|
+
case ASTTag.Literal:
|
|
382
|
+
return Vector(ast);
|
|
383
|
+
case ASTTag.NumberKeyword:
|
|
384
|
+
case ASTTag.StringKeyword:
|
|
385
|
+
return Vector(AST.createTemplateLiteral("", Vector(new TemplateLiteralSpan(ast, ""))));
|
|
386
|
+
case ASTTag.Union:
|
|
387
|
+
return ast.types.flatMap(getTemplateLiterals);
|
|
388
|
+
default:
|
|
389
|
+
throw new Error(`Unsupported template literal span ${show(ast)}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function combineTemplateLiterals(
|
|
394
|
+
a: TemplateLiteral | Literal,
|
|
395
|
+
b: TemplateLiteral | Literal,
|
|
396
|
+
): TemplateLiteral | Literal {
|
|
397
|
+
if (a.isLiteral()) {
|
|
398
|
+
return b.isLiteral()
|
|
399
|
+
? AST.createLiteral(String(a.literal) + String(b.literal))
|
|
400
|
+
: AST.createTemplateLiteral(String(a.literal) + b.head, b.spans);
|
|
401
|
+
}
|
|
402
|
+
if (b.isLiteral()) {
|
|
403
|
+
if (!a.spans.isNonEmpty()) {
|
|
404
|
+
throw new Error("Invalid template literal");
|
|
405
|
+
}
|
|
406
|
+
const last = a.spans.unsafeLast!;
|
|
407
|
+
return AST.createTemplateLiteral(
|
|
408
|
+
a.head,
|
|
409
|
+
a.spans.slice(0, -1).append(new TemplateLiteralSpan(last.type, last.literal + String(b.literal))),
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
if (!a.spans.isNonEmpty()) {
|
|
413
|
+
throw new Error("Invalid template literal");
|
|
414
|
+
}
|
|
415
|
+
const last = a.spans.unsafeLast!;
|
|
416
|
+
return AST.createTemplateLiteral(
|
|
417
|
+
a.head,
|
|
418
|
+
a.spans
|
|
419
|
+
.slice(0, -1)
|
|
420
|
+
.append(new TemplateLiteralSpan(last.type, last.literal + String(b.head)))
|
|
421
|
+
.concat(b.spans),
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* @tsplus static fncts.schema.SchemaOps templateLiteral
|
|
427
|
+
*/
|
|
428
|
+
export function templateLiteral<T extends [Schema<any>, ...Array<Schema<any>>]>(
|
|
429
|
+
...[head, ...tail]: T
|
|
430
|
+
): Schema<Join<{ [K in keyof T]: Schema.Infer<T[K]> }>> {
|
|
431
|
+
let types: Vector<TemplateLiteral | Literal> = getTemplateLiterals(head.ast);
|
|
432
|
+
for (const span of tail) {
|
|
433
|
+
types = types.flatMap((a) => getTemplateLiterals(span.ast).map((b) => combineTemplateLiterals(a, b)));
|
|
434
|
+
}
|
|
435
|
+
return Schema.fromAST(AST.createUnion(types));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* @tsplus static fncts.schema.SchemaOps keyof
|
|
440
|
+
* @tsplus getter fncts.schema.Schema keyof
|
|
441
|
+
*/
|
|
442
|
+
export function keyof<A>(self: Schema<A>): Schema<keyof A> {
|
|
443
|
+
return Schema.fromAST(self.ast.keyof);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function isOverlappingPropertySignatures(x: TypeLiteral, y: TypeLiteral): boolean {
|
|
447
|
+
return x.propertySignatures.some((px) => y.propertySignatures.some((py) => px.name === py.name));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function isOverlappingIndexSignatures(x: TypeLiteral, y: TypeLiteral): boolean {
|
|
451
|
+
return x.indexSignatures.some((ix) =>
|
|
452
|
+
y.indexSignatures.some((iy) => {
|
|
453
|
+
const bx = getParameter(ix.parameter);
|
|
454
|
+
const by = getParameter(iy.parameter);
|
|
455
|
+
return (bx.isStringKeyword() && by.isStringKeyword()) || (bx.isSymbolKeyword() && by.isSymbolKeyword());
|
|
456
|
+
}),
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* @tsplus pipeable fncts.schema.Schema extend
|
|
462
|
+
*/
|
|
463
|
+
export function extend<B>(that: Schema<B>) {
|
|
464
|
+
return <A>(self: Schema<A>): Schema<Spread<A & B>> => {
|
|
465
|
+
const selfTypes = self.ast.isUnion() ? self.ast.types : Vector(self.ast);
|
|
466
|
+
const thatTypes = that.ast.isUnion() ? that.ast.types : Vector(that.ast);
|
|
467
|
+
if (selfTypes.every(AST.isTypeLiteral) && thatTypes.every(AST.isTypeLiteral)) {
|
|
468
|
+
return Schema.fromAST(
|
|
469
|
+
AST.createUnion(
|
|
470
|
+
selfTypes.flatMap((x) =>
|
|
471
|
+
thatTypes.map((y) => {
|
|
472
|
+
if (isOverlappingPropertySignatures(x, y)) {
|
|
473
|
+
throw new Error("`extend` cannot handle overlapping property signatures");
|
|
474
|
+
}
|
|
475
|
+
if (isOverlappingIndexSignatures(x, y)) {
|
|
476
|
+
throw new Error("`extend` cannot handle overlapping index signatures");
|
|
477
|
+
}
|
|
478
|
+
return AST.createTypeLiteral(
|
|
479
|
+
x.propertySignatures.concat(y.propertySignatures),
|
|
480
|
+
x.indexSignatures.concat(y.indexSignatures),
|
|
481
|
+
);
|
|
482
|
+
}),
|
|
483
|
+
),
|
|
484
|
+
),
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
throw new Error("`extend can only handle type literals or unions of type literals`");
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* @tsplus pipeable fncts.schema.Schema instanceOf
|
|
493
|
+
*/
|
|
494
|
+
export function instanceOf<A extends abstract new (...args: any) => any>(constructor: A) {
|
|
495
|
+
return (self: Schema<object>): Schema<InstanceType<A>> => {
|
|
496
|
+
return self
|
|
497
|
+
.filter((value): value is InstanceType<A> => value instanceof constructor)
|
|
498
|
+
.annotate(ASTAnnotation.Description, `an instance of ${constructor.name}`);
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* @tsplus pipeable fncts.schema.Schema transformOrFail
|
|
504
|
+
*/
|
|
505
|
+
export function transformOrFail<A, B>(
|
|
506
|
+
to: Schema<B>,
|
|
507
|
+
decode: (input: A, options?: ParseOptions) => ParseResult<B>,
|
|
508
|
+
encode: (input: B, options?: ParseOptions) => ParseResult<A>,
|
|
509
|
+
) {
|
|
510
|
+
return (from: Schema<A>): Schema<B> => {
|
|
511
|
+
return Schema.fromAST(AST.createTransform(from.ast, to.ast, decode, encode, false));
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* @tsplus pipeable fncts.schema.Schema transform
|
|
517
|
+
*/
|
|
518
|
+
export function transform<A, B>(
|
|
519
|
+
to: Schema<B>,
|
|
520
|
+
decode: (input: A, options?: ParseOptions) => B,
|
|
521
|
+
encode: (input: B, options?: ParseOptions) => A,
|
|
522
|
+
) {
|
|
523
|
+
return (from: Schema<A>): Schema<B> => {
|
|
524
|
+
return from.transformOrFail(
|
|
525
|
+
to,
|
|
526
|
+
(input, options) => ParseResult.succeed(decode(input, options)),
|
|
527
|
+
(input, options) => ParseResult.succeed(encode(input, options)),
|
|
528
|
+
);
|
|
529
|
+
};
|
|
530
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const SchemaVariance = Symbol.for("fncts.schema.Schema.Variance");
|
|
2
|
+
export type SchemaVariance = typeof SchemaVariance;
|
|
3
|
+
|
|
4
|
+
export const SchemaTypeId = Symbol.for("fncts.schema.Schema");
|
|
5
|
+
export type SchemaTypeId = typeof SchemaTypeId;
|
|
6
|
+
|
|
7
|
+
export const OptionalSchemaSymbol = Symbol.for("fncts.schema.Schema.OptionalSchema");
|
|
8
|
+
export type OptionalSchemaSymbol = typeof OptionalSchemaSymbol;
|
|
9
|
+
|
|
10
|
+
export interface SchemaF extends HKT {
|
|
11
|
+
readonly type: Schema<this["A"]>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @tsplus type fncts.schema.Schema
|
|
16
|
+
* @tsplus companion fncts.schema.SchemaOps
|
|
17
|
+
*/
|
|
18
|
+
export class Schema<in out A> {
|
|
19
|
+
readonly [SchemaTypeId]: SchemaTypeId = SchemaTypeId;
|
|
20
|
+
declare [SchemaVariance]: {
|
|
21
|
+
_A: (_: A) => A;
|
|
22
|
+
};
|
|
23
|
+
constructor(readonly ast: AST) {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OptionalSchema<in out A> extends Schema<A> {
|
|
27
|
+
[OptionalSchemaSymbol]: OptionalSchemaSymbol;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export declare namespace Schema {
|
|
31
|
+
export type Infer<S extends Schema<any>> = Parameters<S[SchemaVariance]["_A"]>[0];
|
|
32
|
+
}
|