@valbuild/core 0.12.0 → 0.13.0

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.
Files changed (71) hide show
  1. package/jest.config.js +4 -0
  2. package/package.json +1 -1
  3. package/src/Json.ts +4 -0
  4. package/src/expr/README.md +193 -0
  5. package/src/expr/eval.test.ts +202 -0
  6. package/src/expr/eval.ts +248 -0
  7. package/src/expr/expr.ts +91 -0
  8. package/src/expr/index.ts +3 -0
  9. package/src/expr/parser.test.ts +158 -0
  10. package/src/expr/parser.ts +229 -0
  11. package/src/expr/repl.ts +93 -0
  12. package/src/expr/tokenizer.test.ts +539 -0
  13. package/src/expr/tokenizer.ts +117 -0
  14. package/src/fetchVal.test.ts +164 -0
  15. package/src/fetchVal.ts +211 -0
  16. package/src/fp/array.ts +30 -0
  17. package/src/fp/index.ts +3 -0
  18. package/src/fp/result.ts +214 -0
  19. package/src/fp/util.ts +52 -0
  20. package/src/index.ts +55 -0
  21. package/src/initSchema.ts +45 -0
  22. package/src/initVal.ts +96 -0
  23. package/src/module.test.ts +170 -0
  24. package/src/module.ts +333 -0
  25. package/src/patch/deref.test.ts +300 -0
  26. package/src/patch/deref.ts +128 -0
  27. package/src/patch/index.ts +11 -0
  28. package/src/patch/json.test.ts +583 -0
  29. package/src/patch/json.ts +304 -0
  30. package/src/patch/operation.ts +74 -0
  31. package/src/patch/ops.ts +83 -0
  32. package/src/patch/parse.test.ts +202 -0
  33. package/src/patch/parse.ts +187 -0
  34. package/src/patch/patch.ts +46 -0
  35. package/src/patch/util.ts +67 -0
  36. package/src/schema/array.ts +52 -0
  37. package/src/schema/boolean.ts +38 -0
  38. package/src/schema/i18n.ts +65 -0
  39. package/src/schema/image.ts +70 -0
  40. package/src/schema/index.ts +46 -0
  41. package/src/schema/literal.ts +42 -0
  42. package/src/schema/number.ts +45 -0
  43. package/src/schema/object.ts +67 -0
  44. package/src/schema/oneOf.ts +60 -0
  45. package/src/schema/richtext.ts +417 -0
  46. package/src/schema/string.ts +49 -0
  47. package/src/schema/union.ts +62 -0
  48. package/src/selector/ExprProxy.test.ts +203 -0
  49. package/src/selector/ExprProxy.ts +209 -0
  50. package/src/selector/SelectorProxy.test.ts +172 -0
  51. package/src/selector/SelectorProxy.ts +237 -0
  52. package/src/selector/array.ts +37 -0
  53. package/src/selector/boolean.ts +4 -0
  54. package/src/selector/file.ts +14 -0
  55. package/src/selector/i18n.ts +13 -0
  56. package/src/selector/index.ts +159 -0
  57. package/src/selector/number.ts +4 -0
  58. package/src/selector/object.ts +22 -0
  59. package/src/selector/primitive.ts +17 -0
  60. package/src/selector/remote.ts +9 -0
  61. package/src/selector/selector.test.ts +453 -0
  62. package/src/selector/selectorOf.ts +7 -0
  63. package/src/selector/string.ts +4 -0
  64. package/src/source/file.ts +45 -0
  65. package/src/source/i18n.ts +60 -0
  66. package/src/source/index.ts +50 -0
  67. package/src/source/remote.ts +54 -0
  68. package/src/val/array.ts +10 -0
  69. package/src/val/index.ts +90 -0
  70. package/src/val/object.ts +13 -0
  71. package/src/val/primitive.ts +8 -0
@@ -0,0 +1,187 @@
1
+ import { array, pipe, result } from "../fp";
2
+ import { Operation, OperationJSON } from "./operation";
3
+ import { Patch, PatchJSON } from "./patch";
4
+
5
+ function parseJSONPointerReferenceToken(value: string): string | undefined {
6
+ if (value.endsWith("~")) {
7
+ return undefined;
8
+ }
9
+ try {
10
+ return value.replace(/~./, (escaped) => {
11
+ switch (escaped) {
12
+ case "~0":
13
+ return "~";
14
+ case "~1":
15
+ return "/";
16
+ }
17
+ throw new Error();
18
+ });
19
+ } catch (e) {
20
+ return undefined;
21
+ }
22
+ }
23
+
24
+ export function parseJSONPointer(
25
+ pointer: string
26
+ ): result.Result<string[], string> {
27
+ if (pointer === "/") return result.ok([]);
28
+ if (!pointer.startsWith("/"))
29
+ return result.err("JSON pointer must start with /");
30
+
31
+ const tokens = pointer
32
+ .substring(1)
33
+ .split("/")
34
+ .map(parseJSONPointerReferenceToken);
35
+ if (
36
+ tokens.every(
37
+ (token: string | undefined): token is string => token !== undefined
38
+ )
39
+ ) {
40
+ return result.ok(tokens);
41
+ } else {
42
+ return result.err("Invalid JSON pointer escape sequence");
43
+ }
44
+ }
45
+
46
+ export function formatJSONPointerReferenceToken(key: string): string {
47
+ return key.replace(/~/g, "~0").replace(/\//g, "~1");
48
+ }
49
+
50
+ export function formatJSONPointer(path: string[]): string {
51
+ return `/${path.map(formatJSONPointerReferenceToken).join("/")}`;
52
+ }
53
+
54
+ /**
55
+ * A signifies an issue that makes a PatchJSON or an OperationJSON invalid.
56
+ * Unlike PatchError, a StaticPatchIssue indicates an issue with the patch
57
+ * document itself; it is independent of any document which the patch or
58
+ * might be applied to.
59
+ */
60
+ export type StaticPatchIssue = {
61
+ path: string[];
62
+ message: string;
63
+ };
64
+
65
+ export function prefixIssuePath(
66
+ prefix: string,
67
+ { path, message }: StaticPatchIssue
68
+ ): StaticPatchIssue {
69
+ return { path: [prefix, ...path], message };
70
+ }
71
+
72
+ function createIssueAtPath(path: string[]) {
73
+ return (message: string): StaticPatchIssue => ({
74
+ path,
75
+ message,
76
+ });
77
+ }
78
+
79
+ function isProperPathPrefix(prefix: string[], path: string[]): boolean {
80
+ if (prefix.length >= path.length) {
81
+ // A proper prefix cannot be longer or have the same length as the path
82
+ return false;
83
+ }
84
+ for (let i = 0; i < prefix.length; ++i) {
85
+ if (prefix[i] !== path[i]) {
86
+ return false;
87
+ }
88
+ }
89
+ return true;
90
+ }
91
+
92
+ export function parseOperation(
93
+ operation: OperationJSON
94
+ ): result.Result<Operation, array.NonEmptyArray<StaticPatchIssue>> {
95
+ const path = parseJSONPointer(operation.path);
96
+
97
+ switch (operation.op) {
98
+ case "add":
99
+ case "replace":
100
+ case "test":
101
+ return pipe(
102
+ path,
103
+ result.mapErr(
104
+ (error: string): array.NonEmptyArray<StaticPatchIssue> => [
105
+ createIssueAtPath(["path"])(error),
106
+ ]
107
+ ),
108
+ result.map((path: string[]) => ({
109
+ op: operation.op,
110
+ path,
111
+ value: operation.value,
112
+ }))
113
+ );
114
+ case "remove":
115
+ return pipe(
116
+ path,
117
+ result.filterOrElse(array.isNonEmpty, () => "Cannot remove root"),
118
+ result.mapErr(
119
+ (error: string): array.NonEmptyArray<StaticPatchIssue> => [
120
+ createIssueAtPath(["path"])(error),
121
+ ]
122
+ ),
123
+ result.map((path: array.NonEmptyArray<string>) => ({
124
+ op: operation.op,
125
+ path,
126
+ }))
127
+ );
128
+ case "move":
129
+ return pipe(
130
+ result.allT<
131
+ [from: array.NonEmptyArray<string>, path: string[]],
132
+ StaticPatchIssue
133
+ >([
134
+ pipe(
135
+ parseJSONPointer(operation.from),
136
+ result.filterOrElse(array.isNonEmpty, () => "Cannot move root"),
137
+ result.mapErr(createIssueAtPath(["from"]))
138
+ ),
139
+ pipe(path, result.mapErr(createIssueAtPath(["path"]))),
140
+ ]),
141
+ result.filterOrElse(
142
+ ([from, path]) => !isProperPathPrefix(from, path),
143
+ (): array.NonEmptyArray<StaticPatchIssue> => [
144
+ createIssueAtPath(["from"])("Cannot be a proper prefix of path"),
145
+ ]
146
+ ),
147
+ result.map(([from, path]) => ({
148
+ op: operation.op,
149
+ from,
150
+ path,
151
+ }))
152
+ );
153
+ case "copy":
154
+ return pipe(
155
+ result.allT<[from: string[], path: string[]], StaticPatchIssue>([
156
+ pipe(
157
+ parseJSONPointer(operation.from),
158
+ result.mapErr(createIssueAtPath(["from"]))
159
+ ),
160
+ pipe(path, result.mapErr(createIssueAtPath(["path"]))),
161
+ ]),
162
+ result.map(([from, path]) => ({
163
+ op: operation.op,
164
+ from,
165
+ path,
166
+ }))
167
+ );
168
+ }
169
+ }
170
+
171
+ export function parsePatch(
172
+ patch: PatchJSON
173
+ ): result.Result<Patch, array.NonEmptyArray<StaticPatchIssue>> {
174
+ return pipe(
175
+ patch
176
+ .map(parseOperation)
177
+ .map(
178
+ result.mapErr(
179
+ array.map((error: StaticPatchIssue, index: number) =>
180
+ prefixIssuePath(index.toString(), error)
181
+ )
182
+ )
183
+ ),
184
+ result.all,
185
+ result.mapErr(array.flatten<StaticPatchIssue>)
186
+ );
187
+ }
@@ -0,0 +1,46 @@
1
+ import { result, pipe } from "../fp";
2
+ import { Ops, PatchError } from "./ops";
3
+ import { Operation, OperationJSON } from "./operation";
4
+
5
+ export type Patch = Operation[];
6
+ export type PatchJSON = OperationJSON[];
7
+
8
+ function apply<T, E>(
9
+ document: T,
10
+ ops: Ops<T, E>,
11
+ op: Operation
12
+ ): result.Result<T, E | PatchError> {
13
+ switch (op.op) {
14
+ case "add":
15
+ return ops.add(document, op.path, op.value);
16
+ case "remove":
17
+ return ops.remove(document, op.path);
18
+ case "replace":
19
+ return ops.replace(document, op.path, op.value);
20
+ case "move":
21
+ return ops.move(document, op.from, op.path);
22
+ case "copy":
23
+ return ops.copy(document, op.from, op.path);
24
+ case "test": {
25
+ if (!ops.test(document, op.path, op.value)) {
26
+ return result.err(new PatchError("Test failed"));
27
+ }
28
+ return result.ok(document);
29
+ }
30
+ }
31
+ }
32
+
33
+ export function applyPatch<T, E>(
34
+ document: T,
35
+ ops: Ops<T, E>,
36
+ patch: Operation[]
37
+ ): result.Result<T, E | PatchError> {
38
+ return pipe(
39
+ patch,
40
+ result.flatMapReduce(
41
+ (doc: T, op: Operation): result.Result<T, E | PatchError> =>
42
+ apply(doc, ops, op),
43
+ document
44
+ )
45
+ );
46
+ }
@@ -0,0 +1,67 @@
1
+ import { array, result } from "../fp";
2
+ import { PatchError, ReadonlyJSONValue, ToMutable } from "./ops";
3
+
4
+ export function isNotRoot(path: string[]): path is array.NonEmptyArray<string> {
5
+ return array.isNonEmpty(path);
6
+ }
7
+
8
+ export function deepEqual(a: ReadonlyJSONValue, b: ReadonlyJSONValue) {
9
+ if (a === b) {
10
+ return true;
11
+ }
12
+
13
+ if (
14
+ typeof a === "object" &&
15
+ typeof b === "object" &&
16
+ a !== null &&
17
+ b !== null
18
+ ) {
19
+ if (Array.isArray(a) && Array.isArray(b)) {
20
+ if (a.length !== b.length) return false;
21
+
22
+ for (let i = 0; i < a.length; ++i) {
23
+ if (!deepEqual(a[i], b[i])) return false;
24
+ }
25
+
26
+ return true;
27
+ } else if (!Array.isArray(a) && !Array.isArray(b)) {
28
+ const aEntries = Object.entries(a);
29
+ // If the objects have a different amount of keys, they cannot be equal
30
+ if (aEntries.length !== Object.keys(b).length) return false;
31
+
32
+ for (const [key, aValue] of aEntries) {
33
+ // b must be a JSON object, so the only way for the bValue to be
34
+ // undefined is if the key is unset
35
+ const bValue: ReadonlyJSONValue | undefined = (
36
+ b as { readonly [P in string]: ReadonlyJSONValue }
37
+ )[key];
38
+ if (bValue === undefined) return false;
39
+ if (!deepEqual(aValue, bValue)) return false;
40
+ }
41
+ return true;
42
+ }
43
+ }
44
+
45
+ return false;
46
+ }
47
+
48
+ export function deepClone<T extends ReadonlyJSONValue>(value: T): ToMutable<T> {
49
+ if (Array.isArray(value)) {
50
+ return value.map(deepClone) as ToMutable<T>;
51
+ } else if (typeof value === "object" && value !== null) {
52
+ return Object.fromEntries(
53
+ Object.entries(value).map(([key, value]) => [key, deepClone(value)])
54
+ ) as ToMutable<T>;
55
+ } else {
56
+ return value as ToMutable<T>;
57
+ }
58
+ }
59
+
60
+ export function parseAndValidateArrayIndex(
61
+ value: string
62
+ ): result.Result<number, PatchError> {
63
+ if (!/^(0|[1-9][0-9]*)$/g.test(value)) {
64
+ return result.err(new PatchError(`Invalid array index "${value}"`));
65
+ }
66
+ return result.ok(Number(value));
67
+ }
@@ -0,0 +1,52 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { Schema, SchemaTypeOf, SerializedSchema } from ".";
3
+ import { SelectorSource } from "../selector";
4
+ import { SourcePath } from "../val";
5
+
6
+ export type SerializedArraySchema = {
7
+ type: "array";
8
+ item: SerializedSchema;
9
+ opt: boolean;
10
+ };
11
+
12
+ export class ArraySchema<T extends Schema<SelectorSource>> extends Schema<
13
+ SchemaTypeOf<T>[]
14
+ > {
15
+ constructor(readonly item: T, readonly opt: boolean = false) {
16
+ super();
17
+ }
18
+
19
+ validate(src: SchemaTypeOf<T>[]): false | Record<SourcePath, string[]> {
20
+ throw new Error("Method not implemented.");
21
+ }
22
+
23
+ match(src: SchemaTypeOf<T>[]): boolean {
24
+ if (this.opt && (src === null || src === undefined)) {
25
+ return true;
26
+ }
27
+ if (!src) {
28
+ return false;
29
+ }
30
+
31
+ // TODO: checks all items
32
+ return typeof src === "object" && Array.isArray(src);
33
+ }
34
+
35
+ optional(): Schema<SchemaTypeOf<T>[] | null> {
36
+ return new ArraySchema(this.item, true);
37
+ }
38
+
39
+ serialize(): SerializedArraySchema {
40
+ return {
41
+ type: "array",
42
+ item: this.item.serialize(),
43
+ opt: this.opt,
44
+ };
45
+ }
46
+ }
47
+
48
+ export const array = <S extends Schema<SelectorSource>>(
49
+ schema: S
50
+ ): Schema<SchemaTypeOf<S>[]> => {
51
+ return new ArraySchema(schema);
52
+ };
@@ -0,0 +1,38 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { Schema, SerializedSchema } from ".";
3
+ import { SourcePath } from "../val";
4
+
5
+ export type SerializedBooleanSchema = {
6
+ type: "boolean";
7
+ opt: boolean;
8
+ };
9
+
10
+ export class BooleanSchema<Src extends boolean | null> extends Schema<Src> {
11
+ constructor(readonly opt: boolean = false) {
12
+ super();
13
+ }
14
+ validate(src: Src): false | Record<SourcePath, string[]> {
15
+ throw new Error("Method not implemented.");
16
+ }
17
+
18
+ match(src: Src): boolean {
19
+ if (this.opt && (src === null || src === undefined)) {
20
+ return true;
21
+ }
22
+ return typeof src === "boolean";
23
+ }
24
+
25
+ optional(): Schema<Src | null> {
26
+ return new BooleanSchema<Src | null>(true);
27
+ }
28
+ serialize(): SerializedSchema {
29
+ return {
30
+ type: "boolean",
31
+ opt: this.opt,
32
+ };
33
+ }
34
+ }
35
+
36
+ export const boolean = (): Schema<boolean> => {
37
+ return new BooleanSchema();
38
+ };
@@ -0,0 +1,65 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { Schema, SchemaTypeOf, SerializedSchema } from ".";
3
+ import { I18nCompatibleSource, I18nSource } from "../source/i18n";
4
+ import { SourcePath } from "../val";
5
+
6
+ export type SerializedI18nSchema = {
7
+ type: "i18n";
8
+ locales: readonly string[];
9
+ item: SerializedSchema;
10
+ opt: boolean;
11
+ };
12
+
13
+ export class I18nSchema<Locales extends readonly string[]> extends Schema<
14
+ I18nSource<Locales, SchemaTypeOf<Schema<I18nCompatibleSource>>>
15
+ > {
16
+ constructor(
17
+ readonly locales: Locales,
18
+ readonly item: Schema<SchemaTypeOf<Schema<I18nCompatibleSource>>>,
19
+ readonly opt: boolean = false
20
+ ) {
21
+ super();
22
+ }
23
+
24
+ validate(
25
+ src: I18nSource<Locales, SchemaTypeOf<Schema<I18nCompatibleSource>>>
26
+ ): false | Record<SourcePath, string[]> {
27
+ throw new Error("Method not implemented.");
28
+ }
29
+
30
+ match(
31
+ src: I18nSource<Locales, SchemaTypeOf<Schema<I18nCompatibleSource>>>
32
+ ): boolean {
33
+ throw new Error("Method not implemented.");
34
+ }
35
+
36
+ optional(): Schema<I18nSource<
37
+ Locales,
38
+ SchemaTypeOf<Schema<I18nCompatibleSource>>
39
+ > | null> {
40
+ return new I18nSchema(this.locales, this.item, true);
41
+ }
42
+
43
+ serialize(): SerializedSchema {
44
+ return {
45
+ type: "i18n",
46
+ item: this.item.serialize(),
47
+ locales: this.locales,
48
+ opt: this.opt,
49
+ };
50
+ }
51
+ }
52
+
53
+ export type I18n<Locales extends readonly string[]> = <
54
+ S extends Schema<I18nCompatibleSource>
55
+ >(
56
+ schema: S
57
+ ) => Schema<I18nSource<Locales, SchemaTypeOf<S>>>;
58
+
59
+ export const i18n =
60
+ <Locales extends readonly string[]>(locales: Locales) =>
61
+ <S extends Schema<I18nCompatibleSource>>(
62
+ schema: S
63
+ ): Schema<I18nSource<Locales, SchemaTypeOf<S>>> => {
64
+ return new I18nSchema(locales, schema);
65
+ };
@@ -0,0 +1,70 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { Schema, SerializedSchema } from ".";
3
+ import { FileSource, FILE_REF_PROP } from "../source/file";
4
+ import { SourcePath } from "../val";
5
+
6
+ export type ImageOptions = {
7
+ ext: ["jpg"] | ["webp"];
8
+ directory?: string;
9
+ prefix?: string;
10
+ };
11
+
12
+ export type SerializedImageSchema = {
13
+ type: "image";
14
+ options?: ImageOptions;
15
+ opt: boolean;
16
+ };
17
+
18
+ export type ImageMetadata =
19
+ | {
20
+ width: number;
21
+ height: number;
22
+ sha256: string;
23
+ }
24
+ | undefined;
25
+ export class ImageSchema<
26
+ Src extends FileSource<ImageMetadata> | null
27
+ > extends Schema<Src> {
28
+ constructor(readonly options?: ImageOptions, readonly opt: boolean = false) {
29
+ super();
30
+ }
31
+
32
+ validate(src: Src): false | Record<SourcePath, string[]> {
33
+ throw new Error("Method not implemented.");
34
+ }
35
+
36
+ match(src: Src): boolean {
37
+ // TODO:
38
+ return true;
39
+ }
40
+
41
+ optional(): Schema<Src | null> {
42
+ return new ImageSchema<Src | null>(this.options, true);
43
+ }
44
+
45
+ serialize(): SerializedSchema {
46
+ return {
47
+ type: "image",
48
+ options: this.options,
49
+ opt: this.opt,
50
+ };
51
+ }
52
+ }
53
+
54
+ export const image = (
55
+ options?: ImageOptions
56
+ ): Schema<FileSource<ImageMetadata>> => {
57
+ return new ImageSchema(options);
58
+ };
59
+
60
+ export const convertImageSource = (
61
+ src: FileSource<ImageMetadata>
62
+ ): { url: string; metadata?: ImageMetadata } => {
63
+ // TODO: /public should be configurable
64
+ return {
65
+ url:
66
+ src[FILE_REF_PROP].slice("/public".length) +
67
+ `?sha256=${src.metadata?.sha256}`,
68
+ metadata: src.metadata,
69
+ };
70
+ };
@@ -0,0 +1,46 @@
1
+ import { SelectorSource } from "../selector";
2
+ import { RemoteCompatibleSource, RemoteSource } from "../source/remote";
3
+ import { SourcePath } from "../val";
4
+ import { SerializedArraySchema } from "./array";
5
+ import { SerializedBooleanSchema } from "./boolean";
6
+ import { SerializedI18nSchema } from "./i18n";
7
+ import { SerializedImageSchema } from "./image";
8
+ import { SerializedLiteralSchema } from "./literal";
9
+ import { SerializedNumberSchema } from "./number";
10
+ import { SerializedObjectSchema } from "./object";
11
+ import { SerializedOneOfSchema } from "./oneOf";
12
+ import { SerializedRichTextSchema } from "./richtext";
13
+ import { SerializedStringSchema } from "./string";
14
+ import { SerializedUnionSchema } from "./union";
15
+
16
+ export type SerializedSchema =
17
+ | SerializedStringSchema
18
+ | SerializedLiteralSchema
19
+ | SerializedBooleanSchema
20
+ | SerializedNumberSchema
21
+ | SerializedObjectSchema
22
+ | SerializedOneOfSchema
23
+ | SerializedArraySchema
24
+ | SerializedUnionSchema
25
+ | SerializedRichTextSchema
26
+ | SerializedImageSchema
27
+ | SerializedI18nSchema;
28
+
29
+ export abstract class Schema<Src extends SelectorSource> {
30
+ abstract validate(src: Src): false | Record<SourcePath, string[]>;
31
+ abstract match(src: Src): boolean; // TODO: false | Record<SourcePath, string[]>;
32
+ abstract optional(): Schema<Src | null>;
33
+ abstract serialize(): SerializedSchema;
34
+ remote(): Src extends RemoteCompatibleSource
35
+ ? Schema<RemoteSource<Src>>
36
+ : never {
37
+ // TODO: Schema<never, "Cannot create remote schema from non-remote source.">
38
+ throw new Error("You need Val Ultra to use .remote()");
39
+ }
40
+ }
41
+
42
+ export type SchemaTypeOf<T extends Schema<SelectorSource>> = T extends Schema<
43
+ infer Src
44
+ >
45
+ ? Src
46
+ : never; // TODO: SourceError<"Could not determine type of Schema">
@@ -0,0 +1,42 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { Schema, SerializedSchema } from ".";
3
+ import { SourcePath } from "../val";
4
+
5
+ export type SerializedLiteralSchema = {
6
+ type: "literal";
7
+ value: string;
8
+ opt: boolean;
9
+ };
10
+
11
+ export class LiteralSchema<Src extends string | null> extends Schema<Src> {
12
+ constructor(readonly value: string, readonly opt: boolean = false) {
13
+ super();
14
+ }
15
+
16
+ validate(src: Src): false | Record<SourcePath, string[]> {
17
+ throw new Error("Method not implemented.");
18
+ }
19
+
20
+ match(src: Src): boolean {
21
+ if (this.opt && (src === null || src === undefined)) {
22
+ return true;
23
+ }
24
+ return typeof src === "string";
25
+ }
26
+
27
+ optional(): Schema<Src | null> {
28
+ return new LiteralSchema<Src | null>(this.value, true);
29
+ }
30
+
31
+ serialize(): SerializedSchema {
32
+ return {
33
+ type: "literal",
34
+ value: this.value,
35
+ opt: this.opt,
36
+ };
37
+ }
38
+ }
39
+
40
+ export const literal = <T extends string>(value: T): Schema<T> => {
41
+ return new LiteralSchema(value);
42
+ };
@@ -0,0 +1,45 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { Schema, SerializedSchema } from ".";
3
+ import { SourcePath } from "../val";
4
+
5
+ type NumberOptions = {
6
+ max?: number;
7
+ min?: number;
8
+ };
9
+
10
+ export type SerializedNumberSchema = {
11
+ type: "number";
12
+ options?: NumberOptions;
13
+ opt: boolean;
14
+ };
15
+
16
+ export class NumberSchema<Src extends number | null> extends Schema<Src> {
17
+ constructor(readonly options?: NumberOptions, readonly opt: boolean = false) {
18
+ super();
19
+ }
20
+ validate(src: Src): false | Record<SourcePath, string[]> {
21
+ throw new Error("Method not implemented.");
22
+ }
23
+
24
+ match(src: Src): boolean {
25
+ if (this.opt && (src === null || src === undefined)) {
26
+ return true;
27
+ }
28
+ return typeof src === "number";
29
+ }
30
+
31
+ optional(): Schema<Src | null> {
32
+ return new NumberSchema<Src | null>(this.options, true);
33
+ }
34
+ serialize(): SerializedSchema {
35
+ return {
36
+ type: "number",
37
+ options: this.options,
38
+ opt: this.opt,
39
+ };
40
+ }
41
+ }
42
+
43
+ export const number = (options?: NumberOptions): Schema<number> => {
44
+ return new NumberSchema(options);
45
+ };