@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.
- package/jest.config.js +4 -0
- package/package.json +1 -1
- package/src/Json.ts +4 -0
- package/src/expr/README.md +193 -0
- package/src/expr/eval.test.ts +202 -0
- package/src/expr/eval.ts +248 -0
- package/src/expr/expr.ts +91 -0
- package/src/expr/index.ts +3 -0
- package/src/expr/parser.test.ts +158 -0
- package/src/expr/parser.ts +229 -0
- package/src/expr/repl.ts +93 -0
- package/src/expr/tokenizer.test.ts +539 -0
- package/src/expr/tokenizer.ts +117 -0
- package/src/fetchVal.test.ts +164 -0
- package/src/fetchVal.ts +211 -0
- package/src/fp/array.ts +30 -0
- package/src/fp/index.ts +3 -0
- package/src/fp/result.ts +214 -0
- package/src/fp/util.ts +52 -0
- package/src/index.ts +55 -0
- package/src/initSchema.ts +45 -0
- package/src/initVal.ts +96 -0
- package/src/module.test.ts +170 -0
- package/src/module.ts +333 -0
- package/src/patch/deref.test.ts +300 -0
- package/src/patch/deref.ts +128 -0
- package/src/patch/index.ts +11 -0
- package/src/patch/json.test.ts +583 -0
- package/src/patch/json.ts +304 -0
- package/src/patch/operation.ts +74 -0
- package/src/patch/ops.ts +83 -0
- package/src/patch/parse.test.ts +202 -0
- package/src/patch/parse.ts +187 -0
- package/src/patch/patch.ts +46 -0
- package/src/patch/util.ts +67 -0
- package/src/schema/array.ts +52 -0
- package/src/schema/boolean.ts +38 -0
- package/src/schema/i18n.ts +65 -0
- package/src/schema/image.ts +70 -0
- package/src/schema/index.ts +46 -0
- package/src/schema/literal.ts +42 -0
- package/src/schema/number.ts +45 -0
- package/src/schema/object.ts +67 -0
- package/src/schema/oneOf.ts +60 -0
- package/src/schema/richtext.ts +417 -0
- package/src/schema/string.ts +49 -0
- package/src/schema/union.ts +62 -0
- package/src/selector/ExprProxy.test.ts +203 -0
- package/src/selector/ExprProxy.ts +209 -0
- package/src/selector/SelectorProxy.test.ts +172 -0
- package/src/selector/SelectorProxy.ts +237 -0
- package/src/selector/array.ts +37 -0
- package/src/selector/boolean.ts +4 -0
- package/src/selector/file.ts +14 -0
- package/src/selector/i18n.ts +13 -0
- package/src/selector/index.ts +159 -0
- package/src/selector/number.ts +4 -0
- package/src/selector/object.ts +22 -0
- package/src/selector/primitive.ts +17 -0
- package/src/selector/remote.ts +9 -0
- package/src/selector/selector.test.ts +453 -0
- package/src/selector/selectorOf.ts +7 -0
- package/src/selector/string.ts +4 -0
- package/src/source/file.ts +45 -0
- package/src/source/i18n.ts +60 -0
- package/src/source/index.ts +50 -0
- package/src/source/remote.ts +54 -0
- package/src/val/array.ts +10 -0
- package/src/val/index.ts +90 -0
- package/src/val/object.ts +13 -0
- 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
|
+
};
|