@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
package/src/index.ts
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
export { initVal } from "./initVal";
|
2
|
+
export { fetchVal } from "./fetchVal";
|
3
|
+
export type { InitVal } from "./initVal";
|
4
|
+
export { Schema, type SerializedSchema } from "./schema";
|
5
|
+
export type { ValModule, SerializedModule } from "./module";
|
6
|
+
export type { SourceObject, SourcePrimitive, Source } from "./source";
|
7
|
+
export type { FileSource } from "./source/file";
|
8
|
+
export type { RemoteSource } from "./source/remote";
|
9
|
+
export {
|
10
|
+
type Val,
|
11
|
+
type SerializedVal,
|
12
|
+
type ModuleId,
|
13
|
+
type ModulePath,
|
14
|
+
type SourcePath,
|
15
|
+
} from "./val";
|
16
|
+
export * as expr from "./expr/";
|
17
|
+
export { FILE_REF_PROP } from "./source/file";
|
18
|
+
export { VAL_EXTENSION } from "./source";
|
19
|
+
export { derefPatch } from "./patch/deref";
|
20
|
+
export {
|
21
|
+
type SelectorSource,
|
22
|
+
type SelectorOf,
|
23
|
+
GenericSelector,
|
24
|
+
} from "./selector";
|
25
|
+
import { getVal } from "./fetchVal";
|
26
|
+
import {
|
27
|
+
getRawSource,
|
28
|
+
resolvePath,
|
29
|
+
splitModuleIdAndModulePath,
|
30
|
+
} from "./module";
|
31
|
+
import { getSchema } from "./selector";
|
32
|
+
import { getValPath } from "./val";
|
33
|
+
export type {
|
34
|
+
RichText,
|
35
|
+
TextNode,
|
36
|
+
ParagraphNode,
|
37
|
+
HeadingNode,
|
38
|
+
ImageNode,
|
39
|
+
ListItemNode,
|
40
|
+
ListNode,
|
41
|
+
RootNode,
|
42
|
+
} from "./schema/richtext";
|
43
|
+
import { convertImageSource } from "./schema/image";
|
44
|
+
|
45
|
+
const Internal = {
|
46
|
+
convertImageSource,
|
47
|
+
getSchema,
|
48
|
+
getValPath,
|
49
|
+
getVal,
|
50
|
+
getRawSource,
|
51
|
+
resolvePath,
|
52
|
+
splitModuleIdAndModulePath,
|
53
|
+
};
|
54
|
+
|
55
|
+
export { Internal };
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import { F } from "ts-toolbelt";
|
2
|
+
import { array } from "./schema/array";
|
3
|
+
import { number } from "./schema/number";
|
4
|
+
import { object } from "./schema/object";
|
5
|
+
import { string } from "./schema/string";
|
6
|
+
import { boolean } from "./schema/boolean";
|
7
|
+
import { oneOf } from "./schema/oneOf";
|
8
|
+
import { union } from "./schema/union";
|
9
|
+
import { i18n, I18n } from "./schema/i18n";
|
10
|
+
import { richtext } from "./schema/richtext";
|
11
|
+
import { image } from "./schema/image";
|
12
|
+
import { literal } from "./schema/literal";
|
13
|
+
|
14
|
+
export type InitSchema = {
|
15
|
+
readonly string: typeof string;
|
16
|
+
readonly boolean: typeof boolean;
|
17
|
+
readonly array: typeof array;
|
18
|
+
readonly object: typeof object;
|
19
|
+
readonly number: typeof number;
|
20
|
+
readonly union: typeof union;
|
21
|
+
readonly oneOf: typeof oneOf;
|
22
|
+
readonly richtext: typeof richtext;
|
23
|
+
readonly image: typeof image;
|
24
|
+
readonly literal: typeof literal;
|
25
|
+
};
|
26
|
+
export type InitSchemaLocalized<Locales extends readonly string[]> = {
|
27
|
+
readonly i18n: I18n<Locales>;
|
28
|
+
};
|
29
|
+
export function initSchema<Locales extends readonly string[]>(
|
30
|
+
locales: F.Narrow<Locales>
|
31
|
+
) {
|
32
|
+
return {
|
33
|
+
string,
|
34
|
+
boolean,
|
35
|
+
array,
|
36
|
+
object,
|
37
|
+
number,
|
38
|
+
union,
|
39
|
+
oneOf,
|
40
|
+
richtext,
|
41
|
+
image,
|
42
|
+
literal,
|
43
|
+
i18n: i18n(locales),
|
44
|
+
};
|
45
|
+
}
|
package/src/initVal.ts
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
2
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
|
3
|
+
import { content } from "./module";
|
4
|
+
import { i18n, I18n } from "./source/i18n";
|
5
|
+
import { InitSchema, initSchema, InitSchemaLocalized } from "./initSchema";
|
6
|
+
import { SelectorOf } from "./selector";
|
7
|
+
import { getValPath as getPath, Val } from "./val";
|
8
|
+
import { SelectorSource, GenericSelector } from "./selector";
|
9
|
+
import { JsonOfSource } from "./val";
|
10
|
+
import { fetchVal } from "./fetchVal";
|
11
|
+
import { remote } from "./source/remote";
|
12
|
+
import { file } from "./source/file";
|
13
|
+
|
14
|
+
type ValConstructor = {
|
15
|
+
content: typeof content;
|
16
|
+
getPath: typeof getPath; // TODO: in the react initVal we should also add a key function here which returns the path for use as react keys
|
17
|
+
key: typeof getPath;
|
18
|
+
remote: typeof remote;
|
19
|
+
file: typeof file;
|
20
|
+
};
|
21
|
+
export type InitVal<Locales extends readonly string[] | undefined> = [
|
22
|
+
Locales
|
23
|
+
] extends [readonly string[]]
|
24
|
+
? {
|
25
|
+
val: ValConstructor & {
|
26
|
+
i18n: I18n<Locales>;
|
27
|
+
};
|
28
|
+
fetchVal<T extends SelectorSource>(
|
29
|
+
selector: T,
|
30
|
+
locale: Locales[number]
|
31
|
+
): SelectorOf<T> extends GenericSelector<infer S>
|
32
|
+
? Promise<Val<JsonOfSource<S>>>
|
33
|
+
: never;
|
34
|
+
s: InitSchema & InitSchemaLocalized<Locales>;
|
35
|
+
}
|
36
|
+
: {
|
37
|
+
val: ValConstructor;
|
38
|
+
fetchVal<T extends SelectorSource>(
|
39
|
+
selector: T
|
40
|
+
): SelectorOf<T> extends GenericSelector<infer S>
|
41
|
+
? Promise<Val<JsonOfSource<S>>>
|
42
|
+
: never;
|
43
|
+
s: InitSchema;
|
44
|
+
};
|
45
|
+
|
46
|
+
type NarrowStrings<A> =
|
47
|
+
| (A extends [] ? [] : never)
|
48
|
+
| (A extends string ? A : never)
|
49
|
+
| {
|
50
|
+
[K in keyof A]: NarrowStrings<A[K]>;
|
51
|
+
};
|
52
|
+
|
53
|
+
export const initVal = <
|
54
|
+
Locales extends readonly string[] | undefined
|
55
|
+
>(options?: {
|
56
|
+
readonly locales?: NarrowStrings<{
|
57
|
+
readonly required: Locales;
|
58
|
+
readonly fallback: Locales extends readonly string[]
|
59
|
+
? Locales[number]
|
60
|
+
: never;
|
61
|
+
}>;
|
62
|
+
}): InitVal<Locales> => {
|
63
|
+
const locales = options?.locales;
|
64
|
+
const s = initSchema(locales?.required as readonly string[]);
|
65
|
+
if (locales?.required) {
|
66
|
+
console.error("Locales / i18n currently not implemented");
|
67
|
+
return {
|
68
|
+
val: {
|
69
|
+
content,
|
70
|
+
i18n,
|
71
|
+
remote,
|
72
|
+
getPath,
|
73
|
+
key: getPath,
|
74
|
+
file,
|
75
|
+
},
|
76
|
+
fetchVal: fetchVal,
|
77
|
+
s,
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
79
|
+
} as any;
|
80
|
+
}
|
81
|
+
return {
|
82
|
+
val: {
|
83
|
+
content,
|
84
|
+
remote,
|
85
|
+
getPath,
|
86
|
+
key: getPath,
|
87
|
+
file,
|
88
|
+
},
|
89
|
+
fetchVal: fetchVal,
|
90
|
+
s: {
|
91
|
+
...s,
|
92
|
+
i18n: undefined,
|
93
|
+
},
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
95
|
+
} as any;
|
96
|
+
};
|
@@ -0,0 +1,170 @@
|
|
1
|
+
import {
|
2
|
+
resolvePath as resolveAtPath,
|
3
|
+
getSourceAtPath,
|
4
|
+
parsePath,
|
5
|
+
splitModuleIdAndModulePath,
|
6
|
+
} from "./module";
|
7
|
+
import { SchemaTypeOf } from "./schema";
|
8
|
+
import { array } from "./schema/array";
|
9
|
+
import { i18n as initI18nSchema } from "./schema/i18n";
|
10
|
+
import { i18n as initI18nSource } from "./source/i18n";
|
11
|
+
import { number } from "./schema/number";
|
12
|
+
import { object } from "./schema/object";
|
13
|
+
import { string, StringSchema } from "./schema/string";
|
14
|
+
import { union } from "./schema/union";
|
15
|
+
import { SourceOrExpr } from "./selector";
|
16
|
+
import { newSelectorProxy } from "./selector/SelectorProxy";
|
17
|
+
import { ModulePath, SourcePath } from "./val";
|
18
|
+
import { literal } from "./schema/literal";
|
19
|
+
|
20
|
+
const i18n = initI18nSchema(["en_US", "nb_NO"] as const);
|
21
|
+
const val = {
|
22
|
+
i18n: initI18nSource(["en_US", "nb_NO"] as const),
|
23
|
+
};
|
24
|
+
describe("module", () => {
|
25
|
+
test("parse path", () => {
|
26
|
+
expect(parsePath('"foo"."bar".1."zoo"' as ModulePath)).toStrictEqual([
|
27
|
+
"foo",
|
28
|
+
"bar",
|
29
|
+
"1",
|
30
|
+
"zoo",
|
31
|
+
]);
|
32
|
+
|
33
|
+
expect(parsePath('"foo"."bar".1."z\\"oo"' as ModulePath)).toStrictEqual([
|
34
|
+
"foo",
|
35
|
+
"bar",
|
36
|
+
"1",
|
37
|
+
'z"oo',
|
38
|
+
]);
|
39
|
+
|
40
|
+
expect(parsePath('"foo"."b.ar".1."z\\"oo"' as ModulePath)).toStrictEqual([
|
41
|
+
"foo",
|
42
|
+
"b.ar",
|
43
|
+
"1",
|
44
|
+
'z"oo',
|
45
|
+
]);
|
46
|
+
});
|
47
|
+
|
48
|
+
test("getSourceAtPath: basic selector", () => {
|
49
|
+
const [, modulePath] = splitModuleIdAndModulePath(
|
50
|
+
'/app."foo"."bar".1."zoo"' as SourcePath
|
51
|
+
);
|
52
|
+
expect(modulePath).toStrictEqual('"foo"."bar".1."zoo"');
|
53
|
+
const resolvedModuleAtPath = getSourceAtPath(
|
54
|
+
modulePath,
|
55
|
+
newSelectorProxy({
|
56
|
+
foo: {
|
57
|
+
bar: [{ zoo: "zoo1" }, { zoo: "zoo2" }],
|
58
|
+
},
|
59
|
+
})
|
60
|
+
);
|
61
|
+
expect(resolvedModuleAtPath[SourceOrExpr]).toStrictEqual("zoo2");
|
62
|
+
});
|
63
|
+
|
64
|
+
test("getSourceAtPath: basic source", () => {
|
65
|
+
const resolvedModuleAtPath = getSourceAtPath(
|
66
|
+
'"foo"."bar".1."zoo"' as ModulePath,
|
67
|
+
{
|
68
|
+
foo: {
|
69
|
+
bar: [{ zoo: "zoo1" }, { zoo: "zoo2" }],
|
70
|
+
},
|
71
|
+
}
|
72
|
+
);
|
73
|
+
expect(resolvedModuleAtPath).toStrictEqual("zoo2");
|
74
|
+
});
|
75
|
+
|
76
|
+
test("getSourceAtPath: with dots and escaped quotes", () => {
|
77
|
+
const resolvedModuleAtPath = getSourceAtPath(
|
78
|
+
'"foo"."b.ar".1."z\\"oo"' as ModulePath,
|
79
|
+
newSelectorProxy({
|
80
|
+
foo: {
|
81
|
+
"b.ar": [{ 'z"oo': "zoo1" }, { 'z"oo': "zoo2" }],
|
82
|
+
},
|
83
|
+
})
|
84
|
+
);
|
85
|
+
expect(resolvedModuleAtPath[SourceOrExpr]).toStrictEqual("zoo2");
|
86
|
+
});
|
87
|
+
|
88
|
+
test("getSchemaAtPath: array & object", () => {
|
89
|
+
const basicSchema = array(
|
90
|
+
object({
|
91
|
+
foo: array(object({ bar: string() })),
|
92
|
+
zoo: number(),
|
93
|
+
})
|
94
|
+
);
|
95
|
+
const { schema, source } = resolveAtPath(
|
96
|
+
'0."foo".0."bar"' as ModulePath,
|
97
|
+
[
|
98
|
+
{
|
99
|
+
foo: [
|
100
|
+
{
|
101
|
+
bar: "bar1",
|
102
|
+
},
|
103
|
+
],
|
104
|
+
zoo: 1,
|
105
|
+
},
|
106
|
+
] as SchemaTypeOf<typeof basicSchema>,
|
107
|
+
basicSchema
|
108
|
+
);
|
109
|
+
expect(schema).toBeInstanceOf(StringSchema);
|
110
|
+
expect(source).toStrictEqual("bar1");
|
111
|
+
});
|
112
|
+
|
113
|
+
test("getSchemaAtPath: i18n", () => {
|
114
|
+
const basicSchema = array(
|
115
|
+
object({
|
116
|
+
foo: i18n(array(object({ bar: string() }))),
|
117
|
+
zoo: number(),
|
118
|
+
})
|
119
|
+
);
|
120
|
+
const res = resolveAtPath(
|
121
|
+
'0."foo"."nb_NO".0."bar"' as ModulePath,
|
122
|
+
[
|
123
|
+
{
|
124
|
+
foo: val.i18n({
|
125
|
+
en_US: [
|
126
|
+
{
|
127
|
+
bar: "dive",
|
128
|
+
},
|
129
|
+
],
|
130
|
+
nb_NO: [
|
131
|
+
{
|
132
|
+
bar: "brun",
|
133
|
+
},
|
134
|
+
],
|
135
|
+
}),
|
136
|
+
zoo: 1,
|
137
|
+
},
|
138
|
+
] as SchemaTypeOf<typeof basicSchema>,
|
139
|
+
basicSchema.serialize()
|
140
|
+
);
|
141
|
+
expect(res.schema).toStrictEqual(string().serialize());
|
142
|
+
expect(res.source).toStrictEqual("brun");
|
143
|
+
});
|
144
|
+
|
145
|
+
test("getSchemaAtPath: union", () => {
|
146
|
+
const basicSchema = array(
|
147
|
+
object({
|
148
|
+
foo: union(
|
149
|
+
"type",
|
150
|
+
object({ type: literal("test1"), bar: object({ zoo: string() }) }),
|
151
|
+
object({ type: literal("test2"), bar: object({ zoo: number() }) })
|
152
|
+
),
|
153
|
+
})
|
154
|
+
);
|
155
|
+
const res = resolveAtPath(
|
156
|
+
'0."foo"."bar"."zoo"' as ModulePath,
|
157
|
+
[
|
158
|
+
{
|
159
|
+
foo: {
|
160
|
+
type: "test2",
|
161
|
+
bar: { zoo: 1 },
|
162
|
+
},
|
163
|
+
},
|
164
|
+
] as SchemaTypeOf<typeof basicSchema>,
|
165
|
+
basicSchema.serialize()
|
166
|
+
);
|
167
|
+
expect(res.schema).toStrictEqual(number().serialize());
|
168
|
+
expect(res.source).toStrictEqual(1);
|
169
|
+
});
|
170
|
+
});
|
package/src/module.ts
ADDED
@@ -0,0 +1,333 @@
|
|
1
|
+
import { Schema, SchemaTypeOf, SerializedSchema } from "./schema";
|
2
|
+
import { ObjectSchema, SerializedObjectSchema } from "./schema/object";
|
3
|
+
import {
|
4
|
+
GenericSelector,
|
5
|
+
SelectorOf,
|
6
|
+
SelectorSource,
|
7
|
+
SourceOrExpr,
|
8
|
+
} from "./selector";
|
9
|
+
import { Source, SourceArray } from "./source";
|
10
|
+
import { newSelectorProxy } from "./selector/SelectorProxy";
|
11
|
+
import { ModuleId, ModulePath, SourcePath } from "./val";
|
12
|
+
import { Expr } from "./expr";
|
13
|
+
import { ArraySchema, SerializedArraySchema } from "./schema/array";
|
14
|
+
import { I18nSchema, SerializedI18nSchema } from "./schema/i18n";
|
15
|
+
import { UnionSchema, SerializedUnionSchema } from "./schema/union";
|
16
|
+
import { OneOfSchema, SerializedOneOfSchema } from "./schema/oneOf";
|
17
|
+
import { Json } from "./Json";
|
18
|
+
import {
|
19
|
+
RichText,
|
20
|
+
RichTextSchema,
|
21
|
+
SerializedRichTextSchema,
|
22
|
+
} from "./schema/richtext";
|
23
|
+
import {
|
24
|
+
ImageMetadata,
|
25
|
+
ImageSchema,
|
26
|
+
SerializedImageSchema,
|
27
|
+
} from "./schema/image";
|
28
|
+
import { FileSource } from "./source/file";
|
29
|
+
|
30
|
+
const brand = Symbol("ValModule");
|
31
|
+
export type ValModule<T extends SelectorSource> = SelectorOf<T> &
|
32
|
+
ValModuleBrand;
|
33
|
+
|
34
|
+
export type ValModuleBrand = {
|
35
|
+
[brand]: "ValModule";
|
36
|
+
};
|
37
|
+
|
38
|
+
export type TypeOfValModule<T extends ValModule<SelectorSource>> =
|
39
|
+
T extends GenericSelector<infer S> ? S : never;
|
40
|
+
|
41
|
+
export function content<T extends Schema<SelectorSource>>(
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
43
|
+
id: string,
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
45
|
+
schema: T,
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
47
|
+
source: SchemaTypeOf<T>
|
48
|
+
): ValModule<SchemaTypeOf<T>> {
|
49
|
+
return newSelectorProxy(source, id as SourcePath, schema);
|
50
|
+
}
|
51
|
+
|
52
|
+
export function getRawSource(valModule: ValModule<SelectorSource>): Source {
|
53
|
+
const sourceOrExpr = valModule[SourceOrExpr];
|
54
|
+
if (sourceOrExpr instanceof Expr) {
|
55
|
+
throw Error("Cannot get raw source of an Expr");
|
56
|
+
}
|
57
|
+
const source = sourceOrExpr;
|
58
|
+
return source;
|
59
|
+
}
|
60
|
+
|
61
|
+
export function splitModuleIdAndModulePath(
|
62
|
+
path: SourcePath
|
63
|
+
): [moduleId: ModuleId, path: ModulePath] {
|
64
|
+
return [
|
65
|
+
path.slice(0, path.indexOf(".")) as ModuleId,
|
66
|
+
path.slice(path.indexOf(".") + 1) as ModulePath,
|
67
|
+
];
|
68
|
+
}
|
69
|
+
|
70
|
+
export function getSourceAtPath(
|
71
|
+
modulePath: ModulePath,
|
72
|
+
valModule: ValModule<SelectorSource> | Source
|
73
|
+
) {
|
74
|
+
const parts = parsePath(modulePath);
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
76
|
+
let current: any = valModule;
|
77
|
+
for (const part of parts) {
|
78
|
+
if (typeof current !== "object") {
|
79
|
+
throw Error(`Invalid path: ${part} is not an object`);
|
80
|
+
}
|
81
|
+
current = current[part];
|
82
|
+
}
|
83
|
+
return current;
|
84
|
+
}
|
85
|
+
|
86
|
+
function isObjectSchema(
|
87
|
+
schema: Schema<SelectorSource> | SerializedSchema
|
88
|
+
): schema is
|
89
|
+
| ObjectSchema<{ [key: string]: Schema<SelectorSource> }>
|
90
|
+
| SerializedObjectSchema {
|
91
|
+
return (
|
92
|
+
schema instanceof ObjectSchema ||
|
93
|
+
(typeof schema === "object" && "type" in schema && schema.type === "object")
|
94
|
+
);
|
95
|
+
}
|
96
|
+
|
97
|
+
function isArraySchema(
|
98
|
+
schema: Schema<SelectorSource> | SerializedSchema
|
99
|
+
): schema is ArraySchema<Schema<SelectorSource>> | SerializedArraySchema {
|
100
|
+
return (
|
101
|
+
schema instanceof ArraySchema ||
|
102
|
+
(typeof schema === "object" && "type" in schema && schema.type === "array")
|
103
|
+
);
|
104
|
+
}
|
105
|
+
|
106
|
+
function isI18nSchema(
|
107
|
+
schema: Schema<SelectorSource> | SerializedSchema
|
108
|
+
): schema is I18nSchema<readonly string[]> | SerializedI18nSchema {
|
109
|
+
return (
|
110
|
+
schema instanceof I18nSchema ||
|
111
|
+
(typeof schema === "object" && "type" in schema && schema.type === "i18n")
|
112
|
+
);
|
113
|
+
}
|
114
|
+
|
115
|
+
function isUnionSchema(
|
116
|
+
schema: Schema<SelectorSource> | SerializedSchema
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
118
|
+
): schema is UnionSchema<string, any> | SerializedUnionSchema {
|
119
|
+
return (
|
120
|
+
schema instanceof UnionSchema ||
|
121
|
+
(typeof schema === "object" && "type" in schema && schema.type === "union")
|
122
|
+
);
|
123
|
+
}
|
124
|
+
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
126
|
+
function isRichTextSchema(
|
127
|
+
schema: Schema<SelectorSource> | SerializedSchema
|
128
|
+
): schema is
|
129
|
+
| Schema<RichText> // TODO: RichTextSchema
|
130
|
+
| SerializedRichTextSchema {
|
131
|
+
return (
|
132
|
+
schema instanceof RichTextSchema ||
|
133
|
+
(typeof schema === "object" &&
|
134
|
+
"type" in schema &&
|
135
|
+
schema.type === "richtext")
|
136
|
+
);
|
137
|
+
}
|
138
|
+
|
139
|
+
function isImageSchema(
|
140
|
+
schema: Schema<SelectorSource> | SerializedSchema
|
141
|
+
): schema is
|
142
|
+
| ImageSchema<FileSource<ImageMetadata> | null>
|
143
|
+
| SerializedImageSchema {
|
144
|
+
return (
|
145
|
+
schema instanceof ImageSchema ||
|
146
|
+
(typeof schema === "object" && "type" in schema && schema.type === "image")
|
147
|
+
);
|
148
|
+
}
|
149
|
+
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
151
|
+
function isOneOfSchema(
|
152
|
+
schema: Schema<SelectorSource> | SerializedSchema
|
153
|
+
): schema is OneOfSchema<GenericSelector<SourceArray>> | SerializedOneOfSchema {
|
154
|
+
return (
|
155
|
+
schema instanceof OneOfSchema ||
|
156
|
+
(typeof schema === "object" && "type" in schema && schema.type === "oneOf")
|
157
|
+
);
|
158
|
+
}
|
159
|
+
|
160
|
+
export function resolvePath(
|
161
|
+
path: ModulePath,
|
162
|
+
valModule: ValModule<SelectorSource> | Source,
|
163
|
+
schema: Schema<SelectorSource> | SerializedSchema
|
164
|
+
) {
|
165
|
+
const parts = parsePath(path);
|
166
|
+
const origParts = [...parts];
|
167
|
+
let resolvedSchema = schema;
|
168
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
169
|
+
let resolvedSource: any /* TODO: any */ = valModule;
|
170
|
+
while (parts.length > 0) {
|
171
|
+
const part = parts.shift();
|
172
|
+
if (part === undefined) {
|
173
|
+
throw Error("Unexpected undefined part");
|
174
|
+
}
|
175
|
+
if (isArraySchema(resolvedSchema)) {
|
176
|
+
if (Number.isNaN(Number(part))) {
|
177
|
+
throw Error(
|
178
|
+
`Invalid path: array schema ${resolvedSchema} must have ${part} a number as path`
|
179
|
+
);
|
180
|
+
}
|
181
|
+
if (
|
182
|
+
typeof resolvedSource !== "object" &&
|
183
|
+
!Array.isArray(resolvedSource)
|
184
|
+
) {
|
185
|
+
throw Error(
|
186
|
+
`Schema type error: expected source to be type of array, but got ${typeof resolvedSource}`
|
187
|
+
);
|
188
|
+
}
|
189
|
+
if (!resolvedSource[part]) {
|
190
|
+
throw Error(
|
191
|
+
`Invalid path: array source (length: ${resolvedSource?.length}) did not have index ${part} from path: ${path}`
|
192
|
+
);
|
193
|
+
}
|
194
|
+
resolvedSource = resolvedSource[part];
|
195
|
+
resolvedSchema = resolvedSchema.item;
|
196
|
+
} else if (isObjectSchema(resolvedSchema)) {
|
197
|
+
if (typeof resolvedSource !== "object") {
|
198
|
+
throw Error(
|
199
|
+
`Schema type error: expected source to be type of object, but got ${typeof resolvedSource}`
|
200
|
+
);
|
201
|
+
}
|
202
|
+
|
203
|
+
if (!resolvedSource[part]) {
|
204
|
+
throw Error(
|
205
|
+
`Invalid path: object source did not have key ${part} from path: ${path}`
|
206
|
+
);
|
207
|
+
}
|
208
|
+
resolvedSource = resolvedSource[part];
|
209
|
+
resolvedSchema = resolvedSchema.items[part];
|
210
|
+
} else if (isI18nSchema(resolvedSchema)) {
|
211
|
+
if (!resolvedSchema.locales.includes(part)) {
|
212
|
+
throw Error(
|
213
|
+
`Invalid path: i18n schema ${resolvedSchema} supports locales ${resolvedSchema.locales.join(
|
214
|
+
", "
|
215
|
+
)}, but found: ${part}`
|
216
|
+
);
|
217
|
+
}
|
218
|
+
if (!Object.keys(resolvedSource).includes(part)) {
|
219
|
+
throw Error(
|
220
|
+
`Schema type error: expected source to be type of i18n with locale ${part}, but got ${JSON.stringify(
|
221
|
+
Object.keys(resolvedSource)
|
222
|
+
)}`
|
223
|
+
);
|
224
|
+
}
|
225
|
+
resolvedSource = resolvedSource[part];
|
226
|
+
resolvedSchema = resolvedSchema.item;
|
227
|
+
} else if (isImageSchema(resolvedSchema)) {
|
228
|
+
return {
|
229
|
+
path: origParts
|
230
|
+
.slice(0, origParts.length - parts.length - 1)
|
231
|
+
.map((p) => JSON.stringify(p))
|
232
|
+
.join("."), // TODO: create a function generate path from parts (not sure if this always works)
|
233
|
+
schema: resolvedSchema,
|
234
|
+
source: resolvedSource,
|
235
|
+
};
|
236
|
+
} else if (isUnionSchema(resolvedSchema)) {
|
237
|
+
const key = resolvedSchema.key;
|
238
|
+
const keyValue = resolvedSource[key];
|
239
|
+
if (!keyValue) {
|
240
|
+
throw Error(
|
241
|
+
`Invalid path: union source ${resolvedSchema} did not have required key ${key} in path: ${path}`
|
242
|
+
);
|
243
|
+
}
|
244
|
+
const schemaOfUnionKey = resolvedSchema.items.find(
|
245
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
246
|
+
(child: any) => child?.items?.[key]?.value === keyValue
|
247
|
+
);
|
248
|
+
if (!schemaOfUnionKey) {
|
249
|
+
throw Error(
|
250
|
+
`Invalid path: union schema ${resolvedSchema} did not have a child object with ${key} of value ${keyValue} in path: ${path}`
|
251
|
+
);
|
252
|
+
}
|
253
|
+
resolvedSchema = schemaOfUnionKey.items[part];
|
254
|
+
resolvedSource = resolvedSource[part];
|
255
|
+
} else if (isRichTextSchema(resolvedSchema)) {
|
256
|
+
return {
|
257
|
+
path: origParts
|
258
|
+
.slice(0, origParts.length - parts.length - 1)
|
259
|
+
.map((p) => JSON.stringify(p))
|
260
|
+
.join("."), // TODO: create a function generate path from parts (not sure if this always works)
|
261
|
+
schema: resolvedSchema,
|
262
|
+
source: resolvedSource,
|
263
|
+
};
|
264
|
+
} else {
|
265
|
+
throw Error(
|
266
|
+
`Invalid path: ${part} resolved to an unexpected schema ${JSON.stringify(
|
267
|
+
resolvedSchema
|
268
|
+
)}`
|
269
|
+
);
|
270
|
+
}
|
271
|
+
}
|
272
|
+
if (parts.length > 0) {
|
273
|
+
throw Error(`Invalid path: ${parts.join(".")} is not a valid path`);
|
274
|
+
}
|
275
|
+
return {
|
276
|
+
path,
|
277
|
+
schema: resolvedSchema,
|
278
|
+
source: resolvedSource,
|
279
|
+
};
|
280
|
+
}
|
281
|
+
|
282
|
+
export function parsePath(input: ModulePath) {
|
283
|
+
const result = [];
|
284
|
+
let i = 0;
|
285
|
+
|
286
|
+
while (i < input.length) {
|
287
|
+
let part = "";
|
288
|
+
|
289
|
+
if (input[i] === '"') {
|
290
|
+
// Parse a quoted string
|
291
|
+
i++;
|
292
|
+
while (i < input.length && input[i] !== '"') {
|
293
|
+
if (input[i] === "\\" && input[i + 1] === '"') {
|
294
|
+
// Handle escaped double quotes
|
295
|
+
part += '"';
|
296
|
+
i++;
|
297
|
+
} else {
|
298
|
+
part += input[i];
|
299
|
+
}
|
300
|
+
i++;
|
301
|
+
}
|
302
|
+
if (input[i] !== '"') {
|
303
|
+
throw new Error(
|
304
|
+
`Invalid input (${JSON.stringify(
|
305
|
+
input
|
306
|
+
)}): Missing closing double quote: ${
|
307
|
+
input[i] ?? "at end of string"
|
308
|
+
} (char: ${i}; length: ${input.length})`
|
309
|
+
);
|
310
|
+
}
|
311
|
+
} else {
|
312
|
+
// Parse a regular string
|
313
|
+
while (i < input.length && input[i] !== ".") {
|
314
|
+
part += input[i];
|
315
|
+
i++;
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
if (part !== "") {
|
320
|
+
result.push(part);
|
321
|
+
}
|
322
|
+
|
323
|
+
i++;
|
324
|
+
}
|
325
|
+
|
326
|
+
return result;
|
327
|
+
}
|
328
|
+
|
329
|
+
export type SerializedModule = {
|
330
|
+
source: Json;
|
331
|
+
schema: SerializedSchema;
|
332
|
+
path: SourcePath;
|
333
|
+
};
|