@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,300 @@
|
|
1
|
+
import { file } from "../source/file";
|
2
|
+
import { result } from "../fp";
|
3
|
+
import { remote } from "../source/remote";
|
4
|
+
import { derefPatch, DerefPatchResult } from "./deref";
|
5
|
+
import { JSONOps } from "./json";
|
6
|
+
import { PatchError } from "./ops";
|
7
|
+
|
8
|
+
const ops = new JSONOps();
|
9
|
+
describe("deref", () => {
|
10
|
+
test("replace image", () => {
|
11
|
+
const actual = derefPatch(
|
12
|
+
[
|
13
|
+
{
|
14
|
+
op: "replace",
|
15
|
+
path: ["foo", "baz"],
|
16
|
+
value: 2,
|
17
|
+
},
|
18
|
+
{
|
19
|
+
op: "replace",
|
20
|
+
path: ["foo", "$image1"],
|
21
|
+
value: "aWtrZSB25nJzdD8=",
|
22
|
+
},
|
23
|
+
{
|
24
|
+
op: "replace",
|
25
|
+
path: ["foo", "baz"],
|
26
|
+
value: 3,
|
27
|
+
},
|
28
|
+
{
|
29
|
+
op: "replace",
|
30
|
+
path: ["foo", "$image2"],
|
31
|
+
value: "ZnVua2VyIGRldHRlPw==",
|
32
|
+
},
|
33
|
+
],
|
34
|
+
{
|
35
|
+
foo: {
|
36
|
+
baz: 1,
|
37
|
+
image1: file("/public/val/File\\ Name.jpg", {}),
|
38
|
+
image2: file("/public/val/Some\\ Other\\ image.jpg", {}),
|
39
|
+
},
|
40
|
+
},
|
41
|
+
ops
|
42
|
+
);
|
43
|
+
const expected: DerefPatchResult = {
|
44
|
+
dereferencedPatch: [
|
45
|
+
{
|
46
|
+
op: "replace",
|
47
|
+
path: ["foo", "baz"],
|
48
|
+
value: 2,
|
49
|
+
},
|
50
|
+
{
|
51
|
+
op: "replace",
|
52
|
+
path: ["foo", "baz"],
|
53
|
+
value: 3,
|
54
|
+
},
|
55
|
+
],
|
56
|
+
fileUpdates: {
|
57
|
+
"/public/val/File\\ Name.jpg": "aWtrZSB25nJzdD8=",
|
58
|
+
"/public/val/Some\\ Other\\ image.jpg": "ZnVua2VyIGRldHRlPw==",
|
59
|
+
},
|
60
|
+
remotePatches: {},
|
61
|
+
};
|
62
|
+
expect(actual).toStrictEqual(result.ok(expected));
|
63
|
+
});
|
64
|
+
|
65
|
+
test("replace image sub-reference fails", () => {
|
66
|
+
const actual = derefPatch(
|
67
|
+
[
|
68
|
+
{
|
69
|
+
op: "replace",
|
70
|
+
path: ["foo", "baz"],
|
71
|
+
value: 2,
|
72
|
+
},
|
73
|
+
{
|
74
|
+
op: "replace",
|
75
|
+
path: ["foo", "$image1", "bar"],
|
76
|
+
value: "aWtrZSB25nJzdD8=",
|
77
|
+
},
|
78
|
+
],
|
79
|
+
{
|
80
|
+
foo: {
|
81
|
+
baz: 1,
|
82
|
+
image1: file("/public/val/File\\ Name.jpg", {}),
|
83
|
+
},
|
84
|
+
},
|
85
|
+
ops
|
86
|
+
);
|
87
|
+
expect(actual).toStrictEqual(result.err(expect.any(PatchError)));
|
88
|
+
});
|
89
|
+
|
90
|
+
test("replace image with 2 replaces on same image", () => {
|
91
|
+
const actual = derefPatch(
|
92
|
+
[
|
93
|
+
{
|
94
|
+
op: "replace",
|
95
|
+
path: ["foo", "baz"],
|
96
|
+
value: 2,
|
97
|
+
},
|
98
|
+
{
|
99
|
+
op: "replace",
|
100
|
+
path: ["foo", "$image1"],
|
101
|
+
value: "aWtrZSB25nJzdD8=",
|
102
|
+
},
|
103
|
+
{
|
104
|
+
op: "replace",
|
105
|
+
path: ["foo", "baz"],
|
106
|
+
value: 3,
|
107
|
+
},
|
108
|
+
{
|
109
|
+
op: "replace",
|
110
|
+
path: ["foo", "$image2"],
|
111
|
+
value: "ZnVua2VyIGRldHRlPw==",
|
112
|
+
},
|
113
|
+
],
|
114
|
+
{
|
115
|
+
foo: {
|
116
|
+
baz: 1,
|
117
|
+
image1: file("/public/val/File\\ Name.jpg", {}),
|
118
|
+
image2: file("/public/val/File\\ Name.jpg", {}),
|
119
|
+
},
|
120
|
+
},
|
121
|
+
ops
|
122
|
+
);
|
123
|
+
const expected: DerefPatchResult = {
|
124
|
+
dereferencedPatch: [
|
125
|
+
{
|
126
|
+
op: "replace",
|
127
|
+
path: ["foo", "baz"],
|
128
|
+
value: 2,
|
129
|
+
},
|
130
|
+
{
|
131
|
+
op: "replace",
|
132
|
+
path: ["foo", "baz"],
|
133
|
+
value: 3,
|
134
|
+
},
|
135
|
+
],
|
136
|
+
fileUpdates: {
|
137
|
+
"/public/val/File\\ Name.jpg": "ZnVua2VyIGRldHRlPw==",
|
138
|
+
},
|
139
|
+
remotePatches: {},
|
140
|
+
};
|
141
|
+
expect(actual).toStrictEqual(result.ok(expected));
|
142
|
+
});
|
143
|
+
|
144
|
+
test("replace remote", () => {
|
145
|
+
const actual = derefPatch(
|
146
|
+
[
|
147
|
+
{
|
148
|
+
op: "replace",
|
149
|
+
path: ["foo", "baz"],
|
150
|
+
value: 2,
|
151
|
+
},
|
152
|
+
{
|
153
|
+
op: "replace",
|
154
|
+
path: ["foo", "$re1", "test1"],
|
155
|
+
value: "next test1 update",
|
156
|
+
},
|
157
|
+
{
|
158
|
+
op: "replace",
|
159
|
+
path: ["foo", "baz"],
|
160
|
+
value: 3,
|
161
|
+
},
|
162
|
+
{
|
163
|
+
op: "replace",
|
164
|
+
path: ["foo", "$re2", "test2", "sub-segment"],
|
165
|
+
value: "next test2 update",
|
166
|
+
},
|
167
|
+
],
|
168
|
+
{
|
169
|
+
foo: {
|
170
|
+
baz: 1,
|
171
|
+
re1: remote("41f86df3"),
|
172
|
+
re2: remote("96536d44"),
|
173
|
+
},
|
174
|
+
},
|
175
|
+
ops
|
176
|
+
);
|
177
|
+
const expected: DerefPatchResult = {
|
178
|
+
dereferencedPatch: [
|
179
|
+
{
|
180
|
+
op: "replace",
|
181
|
+
path: ["foo", "baz"],
|
182
|
+
value: 2,
|
183
|
+
},
|
184
|
+
{
|
185
|
+
op: "replace",
|
186
|
+
path: ["foo", "baz"],
|
187
|
+
value: 3,
|
188
|
+
},
|
189
|
+
],
|
190
|
+
fileUpdates: {},
|
191
|
+
remotePatches: {
|
192
|
+
"41f86df3": [
|
193
|
+
{
|
194
|
+
op: "replace",
|
195
|
+
path: ["test1"],
|
196
|
+
value: "next test1 update",
|
197
|
+
},
|
198
|
+
],
|
199
|
+
"96536d44": [
|
200
|
+
{
|
201
|
+
op: "replace",
|
202
|
+
path: ["test2", "sub-segment"],
|
203
|
+
value: "next test2 update",
|
204
|
+
},
|
205
|
+
],
|
206
|
+
},
|
207
|
+
};
|
208
|
+
expect(actual).toStrictEqual(result.ok(expected));
|
209
|
+
});
|
210
|
+
test("replace remote with 2 replaces on same ref", () => {
|
211
|
+
const actual = derefPatch(
|
212
|
+
[
|
213
|
+
{
|
214
|
+
op: "replace",
|
215
|
+
path: ["foo", "baz"],
|
216
|
+
value: 2,
|
217
|
+
},
|
218
|
+
{
|
219
|
+
op: "replace",
|
220
|
+
path: ["foo", "$re1", "test1"],
|
221
|
+
value: "next test1 update",
|
222
|
+
},
|
223
|
+
{
|
224
|
+
op: "replace",
|
225
|
+
path: ["foo", "baz"],
|
226
|
+
value: 3,
|
227
|
+
},
|
228
|
+
{
|
229
|
+
op: "replace",
|
230
|
+
path: ["foo", "$re1", "test1"],
|
231
|
+
value: "next test2 update",
|
232
|
+
},
|
233
|
+
],
|
234
|
+
{
|
235
|
+
foo: {
|
236
|
+
baz: 1,
|
237
|
+
re1: remote("41f86df3"),
|
238
|
+
re2: remote("96536d44"),
|
239
|
+
},
|
240
|
+
},
|
241
|
+
ops
|
242
|
+
);
|
243
|
+
const expected: DerefPatchResult = {
|
244
|
+
dereferencedPatch: [
|
245
|
+
{
|
246
|
+
op: "replace",
|
247
|
+
path: ["foo", "baz"],
|
248
|
+
value: 2,
|
249
|
+
},
|
250
|
+
{
|
251
|
+
op: "replace",
|
252
|
+
path: ["foo", "baz"],
|
253
|
+
value: 3,
|
254
|
+
},
|
255
|
+
],
|
256
|
+
fileUpdates: {},
|
257
|
+
remotePatches: {
|
258
|
+
"41f86df3": [
|
259
|
+
{
|
260
|
+
op: "replace",
|
261
|
+
path: ["test1"],
|
262
|
+
value: "next test1 update",
|
263
|
+
},
|
264
|
+
{
|
265
|
+
op: "replace",
|
266
|
+
path: ["test1"],
|
267
|
+
value: "next test2 update",
|
268
|
+
},
|
269
|
+
],
|
270
|
+
},
|
271
|
+
};
|
272
|
+
expect(actual).toStrictEqual(result.ok(expected));
|
273
|
+
});
|
274
|
+
|
275
|
+
test("replace chained references fails", () => {
|
276
|
+
const actual = derefPatch(
|
277
|
+
[
|
278
|
+
{
|
279
|
+
op: "replace",
|
280
|
+
path: ["foo", "baz"],
|
281
|
+
value: 2,
|
282
|
+
},
|
283
|
+
{
|
284
|
+
op: "replace",
|
285
|
+
path: ["foo", "$re1", "$re2"], // we do not support this, but it might be something we need in the future depending on how remote values
|
286
|
+
value: "next test1 update",
|
287
|
+
},
|
288
|
+
],
|
289
|
+
{
|
290
|
+
foo: {
|
291
|
+
baz: 1,
|
292
|
+
re1: remote("41f86df3"),
|
293
|
+
re2: remote("96536d44"),
|
294
|
+
},
|
295
|
+
},
|
296
|
+
ops
|
297
|
+
);
|
298
|
+
expect(actual).toStrictEqual(result.err(expect.any(PatchError)));
|
299
|
+
});
|
300
|
+
});
|
@@ -0,0 +1,128 @@
|
|
1
|
+
import { FILE_REF_PROP, isFile } from "../source/file";
|
2
|
+
import { result } from "../fp";
|
3
|
+
import { isRemote, REMOTE_REF_PROP } from "../source/remote";
|
4
|
+
import { Ops, PatchError } from "./ops";
|
5
|
+
import { Patch } from "./patch";
|
6
|
+
|
7
|
+
function derefPath(
|
8
|
+
path: string[]
|
9
|
+
): result.Result<[string[], string[] | null], PatchError> {
|
10
|
+
const dereffedPath: string[] = [];
|
11
|
+
let referencedPath: string[] | null = null;
|
12
|
+
for (const segment of path) {
|
13
|
+
if (segment.startsWith("$")) {
|
14
|
+
const dereffedSegment = segment.slice(1);
|
15
|
+
dereffedPath.push(dereffedSegment);
|
16
|
+
referencedPath = [];
|
17
|
+
for (const segment of path.slice(dereffedPath.length)) {
|
18
|
+
if (segment.startsWith("$")) {
|
19
|
+
return result.err(
|
20
|
+
new PatchError(
|
21
|
+
`Cannot reference within reference: ${segment}. Path: ${path.join(
|
22
|
+
"/"
|
23
|
+
)}`
|
24
|
+
)
|
25
|
+
);
|
26
|
+
}
|
27
|
+
referencedPath.push(segment);
|
28
|
+
}
|
29
|
+
break;
|
30
|
+
} else {
|
31
|
+
dereffedPath.push(segment);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
return result.ok([dereffedPath, referencedPath]);
|
35
|
+
}
|
36
|
+
|
37
|
+
export type DerefPatchResult = {
|
38
|
+
dereferencedPatch: Patch;
|
39
|
+
fileUpdates: { [path: string]: string };
|
40
|
+
remotePatches: { [ref: string]: Patch };
|
41
|
+
};
|
42
|
+
|
43
|
+
export function derefPatch<D, E>(
|
44
|
+
patch: Patch,
|
45
|
+
document: D,
|
46
|
+
ops: Ops<D, E>
|
47
|
+
): result.Result<DerefPatchResult, E | PatchError> {
|
48
|
+
const remotePatches: {
|
49
|
+
[ref: string]: Patch;
|
50
|
+
} = {};
|
51
|
+
const fileUpdates: {
|
52
|
+
[file: string]: string;
|
53
|
+
} = {};
|
54
|
+
const dereferencedPatch: Patch = [];
|
55
|
+
for (const op of patch) {
|
56
|
+
if (op.op === "replace") {
|
57
|
+
const maybeDerefRes = derefPath(op.path);
|
58
|
+
if (result.isErr(maybeDerefRes)) {
|
59
|
+
return maybeDerefRes;
|
60
|
+
}
|
61
|
+
const [dereffedPath, referencedPath] = maybeDerefRes.value;
|
62
|
+
if (referencedPath) {
|
63
|
+
const maybeValue = ops.get(document, dereffedPath);
|
64
|
+
if (result.isOk(maybeValue)) {
|
65
|
+
const value = maybeValue.value;
|
66
|
+
if (isFile(value)) {
|
67
|
+
if (referencedPath.length > 0) {
|
68
|
+
return result.err(
|
69
|
+
new PatchError(
|
70
|
+
`Cannot sub-reference file reference at path: ${dereffedPath.join(
|
71
|
+
"/"
|
72
|
+
)}`
|
73
|
+
)
|
74
|
+
);
|
75
|
+
}
|
76
|
+
if (typeof op.value !== "string") {
|
77
|
+
return result.err(
|
78
|
+
new PatchError(
|
79
|
+
`Expected base64 encoded string value for file reference, got ${JSON.stringify(
|
80
|
+
op.value
|
81
|
+
)}`
|
82
|
+
)
|
83
|
+
);
|
84
|
+
}
|
85
|
+
fileUpdates[value[FILE_REF_PROP]] = op.value;
|
86
|
+
} else if (isRemote(value)) {
|
87
|
+
if (!remotePatches[value[REMOTE_REF_PROP]]) {
|
88
|
+
remotePatches[value[REMOTE_REF_PROP]] = [];
|
89
|
+
}
|
90
|
+
remotePatches[value[REMOTE_REF_PROP]].push({
|
91
|
+
op: "replace",
|
92
|
+
path: referencedPath,
|
93
|
+
value: op.value,
|
94
|
+
});
|
95
|
+
} else {
|
96
|
+
return result.err(
|
97
|
+
new PatchError(
|
98
|
+
`Unknown reference: ${JSON.stringify(
|
99
|
+
op
|
100
|
+
)} at path: ${dereffedPath.join("/")}`
|
101
|
+
)
|
102
|
+
);
|
103
|
+
}
|
104
|
+
} else {
|
105
|
+
return maybeValue;
|
106
|
+
}
|
107
|
+
} else {
|
108
|
+
dereferencedPatch.push(op);
|
109
|
+
}
|
110
|
+
} else {
|
111
|
+
const maybeDerefRes = derefPath(op.path);
|
112
|
+
if (result.isErr(maybeDerefRes)) {
|
113
|
+
return maybeDerefRes;
|
114
|
+
}
|
115
|
+
const [, referencedPath] = maybeDerefRes.value;
|
116
|
+
if (referencedPath) {
|
117
|
+
throw new Error(`Unimplemented operation: ${JSON.stringify(op)}`);
|
118
|
+
}
|
119
|
+
dereferencedPatch.push(op);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
return result.ok({
|
124
|
+
remotePatches,
|
125
|
+
fileUpdates,
|
126
|
+
dereferencedPatch,
|
127
|
+
});
|
128
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
export { JSONOps } from "./json";
|
2
|
+
export { type OperationJSON, type Operation } from "./operation";
|
3
|
+
export { parsePatch, parseJSONPointer, formatJSONPointer } from "./parse";
|
4
|
+
export { type JSONValue, type Ops, PatchError } from "./ops";
|
5
|
+
export { type PatchJSON, type Patch, applyPatch } from "./patch";
|
6
|
+
export {
|
7
|
+
isNotRoot,
|
8
|
+
deepEqual,
|
9
|
+
deepClone,
|
10
|
+
parseAndValidateArrayIndex,
|
11
|
+
} from "./util";
|