@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
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
+ };