@inlang/sdk 2.4.8 → 2.4.10
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/dist/database/jsonPlugin.d.ts +11 -0
- package/dist/database/jsonPlugin.d.ts.map +1 -0
- package/dist/database/jsonPlugin.js +197 -0
- package/dist/database/jsonPlugin.js.map +1 -0
- package/dist/import-export/upsertBundleNestedMatchByProperties.d.ts.map +1 -1
- package/dist/migrations/v2/createMessageV1.d.ts.map +1 -1
- package/dist/project/fs-sync.playground.test.d.ts +2 -0
- package/dist/project/fs-sync.playground.test.d.ts.map +1 -0
- package/dist/project/fs-sync.playground.test.js +535 -0
- package/dist/project/fs-sync.playground.test.js.map +1 -0
- package/dist/project/loadProjectFromDirectory.d.ts.map +1 -1
- package/dist/project/loadProjectFromDirectory.js +7 -3
- package/dist/project/loadProjectFromDirectory.js.map +1 -1
- package/dist/project/saveProjectToDirectory.d.ts.map +1 -1
- package/dist/project/saveProjectToDirectory.js +25 -18
- package/dist/project/saveProjectToDirectory.js.map +1 -1
- package/dist/query-utilities/insertBundleNested.d.ts.map +1 -1
- package/dist/query-utilities/selectBundleNested.d.ts.map +1 -1
- package/dist/query-utilities/updateBundleNested.d.ts.map +1 -1
- package/dist/query-utilities/upsertBundleNested.d.ts.map +1 -1
- package/dist/schema-definitions/bundle.d.ts +22 -0
- package/dist/schema-definitions/bundle.d.ts.map +1 -0
- package/dist/schema-definitions/bundle.js +18 -0
- package/dist/schema-definitions/bundle.js.map +1 -0
- package/dist/schema-definitions/bundle.test.d.ts +2 -0
- package/dist/schema-definitions/bundle.test.d.ts.map +1 -0
- package/dist/schema-definitions/bundle.test.js +23 -0
- package/dist/schema-definitions/bundle.test.js.map +1 -0
- package/dist/schema-definitions/index.d.ts +4 -0
- package/dist/schema-definitions/index.d.ts.map +1 -0
- package/dist/schema-definitions/index.js +4 -0
- package/dist/schema-definitions/index.js.map +1 -0
- package/dist/schema-definitions/message.d.ts +35 -0
- package/dist/schema-definitions/message.d.ts.map +1 -0
- package/dist/schema-definitions/message.js +26 -0
- package/dist/schema-definitions/message.js.map +1 -0
- package/dist/schema-definitions/message.test.d.ts +2 -0
- package/dist/schema-definitions/message.test.d.ts.map +1 -0
- package/dist/schema-definitions/message.test.js +45 -0
- package/dist/schema-definitions/message.test.js.map +1 -0
- package/dist/schema-definitions/variant.d.ts +35 -0
- package/dist/schema-definitions/variant.d.ts.map +1 -0
- package/dist/schema-definitions/variant.js +26 -0
- package/dist/schema-definitions/variant.js.map +1 -0
- package/dist/schema-definitions/variant.test.d.ts +2 -0
- package/dist/schema-definitions/variant.test.d.ts.map +1 -0
- package/dist/schema-definitions/variant.test.js +67 -0
- package/dist/schema-definitions/variant.test.js.map +1 -0
- package/dist/services/env-variables/index.d.ts +2 -2
- package/dist/services/env-variables/index.js +3 -3
- package/dist/services/env-variables/index.js.map +1 -1
- package/dist/services/telemetry/capture.d.ts.map +1 -1
- package/dist/utilities/detectJsonFormatting.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/project/loadProjectFromDirectory.ts +10 -5
- package/src/project/saveProjectToDirectory.ts +33 -24
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { promises as nodeFs } from "node:fs";
|
|
2
|
+
import { Volume } from "memfs";
|
|
3
|
+
import { expect, test } from "vitest";
|
|
4
|
+
import { loadProjectFromDirectory } from "./loadProjectFromDirectory.js";
|
|
5
|
+
import { saveProjectToDirectory } from "./saveProjectToDirectory.js";
|
|
6
|
+
import { performance } from "node:perf_hooks";
|
|
7
|
+
const OUTPUT_DIR = decodeURIComponent(new URL("./__playground__", import.meta.url).pathname);
|
|
8
|
+
const PROJECT_PATH = "/project.inlang";
|
|
9
|
+
const mockSettings = {
|
|
10
|
+
baseLocale: "en",
|
|
11
|
+
locales: ["en", "de"],
|
|
12
|
+
modules: [],
|
|
13
|
+
};
|
|
14
|
+
const mockDirectory = {
|
|
15
|
+
"/project.inlang/cache/plugin/29j49j2": "cache value",
|
|
16
|
+
"/project.inlang/.gitignore": "git value",
|
|
17
|
+
"/project.inlang/prettierrc.json": "prettier value",
|
|
18
|
+
"/project.inlang/README.md": "readme value",
|
|
19
|
+
"/project.inlang/settings.json": JSON.stringify(mockSettings),
|
|
20
|
+
};
|
|
21
|
+
const branchSettings = {
|
|
22
|
+
...mockSettings,
|
|
23
|
+
"test-json-plugin": {
|
|
24
|
+
pathPattern: "./project.inlang/messages/{locale}.json",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Waits for the sync loop to flush asynchronous work.
|
|
29
|
+
*
|
|
30
|
+
* @param iterations - Number of scheduler loops to wait for.
|
|
31
|
+
* @example
|
|
32
|
+
* await waitForSync(3);
|
|
33
|
+
*/
|
|
34
|
+
const waitForSync = async (iterations = 1) => {
|
|
35
|
+
for (let i = 0; i < iterations; i++) {
|
|
36
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Compares array buffers without cloning them.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const same = arrayBuffersEqual(bufA, bufB);
|
|
44
|
+
*/
|
|
45
|
+
const arrayBuffersEqual = (a, b) => {
|
|
46
|
+
if (a.byteLength !== b.byteLength) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const viewA = new Uint8Array(a);
|
|
50
|
+
const viewB = new Uint8Array(b);
|
|
51
|
+
for (let i = 0; i < viewA.byteLength; i++) {
|
|
52
|
+
if (viewA[i] !== viewB[i]) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Minimal JSON plugin that mirrors the behaviour from the primary sync tests.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* await project.importFiles({ pluginKey: simpleJsonPlugin.key, files });
|
|
63
|
+
*/
|
|
64
|
+
const simpleJsonPlugin = {
|
|
65
|
+
key: "test-json-plugin",
|
|
66
|
+
toBeImportedFiles: async ({ settings }) => settings.locales.map((locale) => ({
|
|
67
|
+
locale,
|
|
68
|
+
path: `messages/${locale}.json`,
|
|
69
|
+
})),
|
|
70
|
+
importFiles: async ({ files }) => {
|
|
71
|
+
const bundles = [];
|
|
72
|
+
const messages = [];
|
|
73
|
+
const variants = [];
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
const decoded = new TextDecoder().decode(file.content);
|
|
76
|
+
if (!decoded)
|
|
77
|
+
continue;
|
|
78
|
+
const parsed = JSON.parse(decoded);
|
|
79
|
+
for (const [bundleId, value] of Object.entries(parsed)) {
|
|
80
|
+
bundles.push({ id: bundleId, declarations: [] });
|
|
81
|
+
messages.push({ bundleId, locale: file.locale, selectors: [] });
|
|
82
|
+
variants.push({
|
|
83
|
+
messageBundleId: bundleId,
|
|
84
|
+
messageLocale: file.locale,
|
|
85
|
+
matches: [],
|
|
86
|
+
pattern: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
value,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { bundles, messages, variants };
|
|
96
|
+
},
|
|
97
|
+
exportFiles: async ({ messages, variants }) => {
|
|
98
|
+
const byLocale = new Map();
|
|
99
|
+
for (const message of messages) {
|
|
100
|
+
const correspondingVariant = variants.find((variant) => variant.messageId === message.id);
|
|
101
|
+
if (!correspondingVariant)
|
|
102
|
+
continue;
|
|
103
|
+
const pattern = correspondingVariant.pattern[0];
|
|
104
|
+
if (!byLocale.has(message.locale)) {
|
|
105
|
+
byLocale.set(message.locale, {});
|
|
106
|
+
}
|
|
107
|
+
byLocale.get(message.locale)[message.bundleId] = pattern.value;
|
|
108
|
+
}
|
|
109
|
+
return Array.from(byLocale.entries()).map(([locale, json]) => ({
|
|
110
|
+
locale,
|
|
111
|
+
name: `messages/${locale}.json`,
|
|
112
|
+
content: new TextEncoder().encode(`${JSON.stringify(json, null, 2)}\n`),
|
|
113
|
+
}));
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const createMessagesFs = () => Volume.fromJSON({
|
|
117
|
+
"/project.inlang/settings.json": JSON.stringify(mockSettings),
|
|
118
|
+
"/project.inlang/messages/en.json": JSON.stringify({ greeting: "Hello" }),
|
|
119
|
+
"/project.inlang/messages/de.json": JSON.stringify({ greeting: "Hallo" }),
|
|
120
|
+
});
|
|
121
|
+
const createFormattedMessagesFs = () => Volume.fromJSON({
|
|
122
|
+
"/project.inlang/settings.json": JSON.stringify(mockSettings),
|
|
123
|
+
"/project.inlang/messages/en.json": `${JSON.stringify({ greeting: "Hello" }, null, 2)}\n`,
|
|
124
|
+
"/project.inlang/messages/de.json": `${JSON.stringify({ greeting: "Hallo" }, null, 2)}\n`,
|
|
125
|
+
});
|
|
126
|
+
const createBranchFs = () => Volume.fromJSON({
|
|
127
|
+
"/project.inlang/settings.json": JSON.stringify(branchSettings),
|
|
128
|
+
"/project.inlang/messages/en.json": `${JSON.stringify({ greeting: "Hello" }, null, 2)}\n`,
|
|
129
|
+
"/project.inlang/messages/de.json": `${JSON.stringify({ greeting: "Hallo" }, null, 2)}\n`,
|
|
130
|
+
});
|
|
131
|
+
const createDirectoryFs = () => Volume.fromJSON(mockDirectory);
|
|
132
|
+
const selectFileScanPredicate = (entry) => {
|
|
133
|
+
const normalised = entry.sql.replace(/\s+/g, " ").toLowerCase();
|
|
134
|
+
return (normalised.includes('"lixcol_writer_key"') &&
|
|
135
|
+
normalised.includes('from "file"') &&
|
|
136
|
+
normalised.includes("not like ?"));
|
|
137
|
+
};
|
|
138
|
+
const selectFilePredicate = (path) => (entry) => {
|
|
139
|
+
const normalised = entry.sql.replace(/\s+/g, " ").toLowerCase();
|
|
140
|
+
return (normalised.startsWith("select") &&
|
|
141
|
+
normalised.includes('from "file"') &&
|
|
142
|
+
normalised.includes('"path" = ?') &&
|
|
143
|
+
entry.parameters?.[0] === path);
|
|
144
|
+
};
|
|
145
|
+
const deleteFilePredicate = (path) => (entry) => {
|
|
146
|
+
const normalised = entry.sql.replace(/\s+/g, " ").toLowerCase();
|
|
147
|
+
return (normalised.startsWith('delete from "file"') &&
|
|
148
|
+
normalised.includes('where "path" = ?') &&
|
|
149
|
+
entry.parameters?.[0] === path);
|
|
150
|
+
};
|
|
151
|
+
const selectAllFilesPredicate = (entry) => {
|
|
152
|
+
return entry.sql.trim().toLowerCase().startsWith('select * from "file"');
|
|
153
|
+
};
|
|
154
|
+
const SCENARIOS = [
|
|
155
|
+
{
|
|
156
|
+
label: "touched-locale-rewrite",
|
|
157
|
+
fsFactory: createMessagesFs,
|
|
158
|
+
run: async ({ project }) => {
|
|
159
|
+
await project.lix.db
|
|
160
|
+
.updateTable("file")
|
|
161
|
+
.where("path", "=", "/messages/en.json")
|
|
162
|
+
.set({
|
|
163
|
+
data: new TextEncoder().encode(JSON.stringify({ greeting: "Hi from lix" })),
|
|
164
|
+
})
|
|
165
|
+
.execute();
|
|
166
|
+
await waitForSync(3);
|
|
167
|
+
},
|
|
168
|
+
predicate: selectFileScanPredicate,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
label: "plugin-export-delta",
|
|
172
|
+
fsFactory: createFormattedMessagesFs,
|
|
173
|
+
providePlugins: [simpleJsonPlugin],
|
|
174
|
+
run: async ({ project, fs }) => {
|
|
175
|
+
await waitForSync(3);
|
|
176
|
+
const initialFiles = ["en", "de"].map((locale) => ({
|
|
177
|
+
locale,
|
|
178
|
+
content: new TextEncoder().encode(fs.readFileSync(`${PROJECT_PATH}/messages/${locale}.json`, "utf-8")),
|
|
179
|
+
}));
|
|
180
|
+
await project.importFiles({
|
|
181
|
+
pluginKey: simpleJsonPlugin.key,
|
|
182
|
+
files: initialFiles,
|
|
183
|
+
});
|
|
184
|
+
const messagesAfterImport = await project.db
|
|
185
|
+
.selectFrom("message")
|
|
186
|
+
.selectAll()
|
|
187
|
+
.execute();
|
|
188
|
+
const englishMessage = messagesAfterImport.find((message) => message.locale === "en");
|
|
189
|
+
if (!englishMessage) {
|
|
190
|
+
throw new Error("expected english message");
|
|
191
|
+
}
|
|
192
|
+
await project.db
|
|
193
|
+
.updateTable("variant")
|
|
194
|
+
.set({ pattern: [{ type: "text", value: "Hi" }] })
|
|
195
|
+
.where("messageId", "=", englishMessage.id)
|
|
196
|
+
.execute();
|
|
197
|
+
const exportedFiles = await project.exportFiles({
|
|
198
|
+
pluginKey: simpleJsonPlugin.key,
|
|
199
|
+
});
|
|
200
|
+
for (const file of exportedFiles) {
|
|
201
|
+
const existing = await project.lix.db
|
|
202
|
+
.selectFrom("file")
|
|
203
|
+
.select(["data"])
|
|
204
|
+
.where("path", "=", `/${file.name}`)
|
|
205
|
+
.executeTakeFirst();
|
|
206
|
+
let currentBuffer;
|
|
207
|
+
if (existing) {
|
|
208
|
+
const dataView = new Uint8Array(existing.data);
|
|
209
|
+
currentBuffer = dataView.slice().buffer;
|
|
210
|
+
}
|
|
211
|
+
const targetBuffer = new Uint8Array(file.content).slice()
|
|
212
|
+
.buffer;
|
|
213
|
+
if (existing &&
|
|
214
|
+
currentBuffer !== undefined &&
|
|
215
|
+
arrayBuffersEqual(currentBuffer, targetBuffer)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
await project.lix.db
|
|
219
|
+
.updateTable("file")
|
|
220
|
+
.set({ data: file.content })
|
|
221
|
+
.where("path", "=", `/${file.name}`)
|
|
222
|
+
.execute();
|
|
223
|
+
}
|
|
224
|
+
await waitForSync(3);
|
|
225
|
+
},
|
|
226
|
+
predicate: selectFileScanPredicate,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
label: "branch-switch",
|
|
230
|
+
fsFactory: createBranchFs,
|
|
231
|
+
providePlugins: [simpleJsonPlugin],
|
|
232
|
+
run: async ({ project, fs }) => {
|
|
233
|
+
await waitForSync(5);
|
|
234
|
+
const initialFiles = ["en", "de"].map((locale) => ({
|
|
235
|
+
locale,
|
|
236
|
+
content: new TextEncoder().encode(fs.readFileSync(`${PROJECT_PATH}/messages/${locale}.json`, "utf-8")),
|
|
237
|
+
}));
|
|
238
|
+
await project.importFiles({
|
|
239
|
+
pluginKey: simpleJsonPlugin.key,
|
|
240
|
+
files: initialFiles,
|
|
241
|
+
});
|
|
242
|
+
const { id: englishMessageId } = await project.db
|
|
243
|
+
.selectFrom("message")
|
|
244
|
+
.select("id")
|
|
245
|
+
.where("locale", "=", "en")
|
|
246
|
+
.executeTakeFirstOrThrow();
|
|
247
|
+
await project.db
|
|
248
|
+
.updateTable("variant")
|
|
249
|
+
.set({ pattern: [{ type: "text", value: "Hi" }] })
|
|
250
|
+
.where("messageId", "=", englishMessageId)
|
|
251
|
+
.execute();
|
|
252
|
+
const exportedFiles = await project.exportFiles({
|
|
253
|
+
pluginKey: simpleJsonPlugin.key,
|
|
254
|
+
});
|
|
255
|
+
for (const file of exportedFiles) {
|
|
256
|
+
await project.lix.db
|
|
257
|
+
.updateTable("file")
|
|
258
|
+
.set({ data: file.content })
|
|
259
|
+
.where("path", "=", `/${file.name}`)
|
|
260
|
+
.execute();
|
|
261
|
+
}
|
|
262
|
+
await waitForSync(5);
|
|
263
|
+
const branchContent = `${JSON.stringify({ greeting: "Branch" }, null, 2)}\n`;
|
|
264
|
+
fs.writeFileSync(`${PROJECT_PATH}/messages/en.json`, branchContent);
|
|
265
|
+
await project.importFiles({
|
|
266
|
+
pluginKey: simpleJsonPlugin.key,
|
|
267
|
+
files: [
|
|
268
|
+
{
|
|
269
|
+
locale: "en",
|
|
270
|
+
content: new TextEncoder().encode(branchContent),
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
});
|
|
274
|
+
await saveProjectToDirectory({
|
|
275
|
+
fs: fs.promises,
|
|
276
|
+
project,
|
|
277
|
+
path: PROJECT_PATH,
|
|
278
|
+
});
|
|
279
|
+
await waitForSync(3);
|
|
280
|
+
},
|
|
281
|
+
predicate: selectFileScanPredicate,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
label: "noop-sync",
|
|
285
|
+
fsFactory: createMessagesFs,
|
|
286
|
+
run: async ({ project }) => {
|
|
287
|
+
await waitForSync(6);
|
|
288
|
+
await project.lix.db
|
|
289
|
+
.selectFrom("file")
|
|
290
|
+
.where("path", "not like", "%db.sqlite")
|
|
291
|
+
.select(["path", "data", "lixcol_writer_key"])
|
|
292
|
+
.execute();
|
|
293
|
+
},
|
|
294
|
+
predicate: selectFileScanPredicate,
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
label: "lix-only-update",
|
|
298
|
+
fsFactory: createMessagesFs,
|
|
299
|
+
run: async ({ project }) => {
|
|
300
|
+
await waitForSync(3);
|
|
301
|
+
await project.lix.db
|
|
302
|
+
.updateTable("file")
|
|
303
|
+
.set({
|
|
304
|
+
data: new TextEncoder().encode(JSON.stringify({ greeting: "Hi from lix-only" })),
|
|
305
|
+
})
|
|
306
|
+
.where("path", "=", "/messages/en.json")
|
|
307
|
+
.execute();
|
|
308
|
+
await waitForSync(3);
|
|
309
|
+
},
|
|
310
|
+
predicate: selectFileScanPredicate,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
label: "fs-files-available-in-lix",
|
|
314
|
+
fsFactory: createDirectoryFs,
|
|
315
|
+
run: async ({ project }) => {
|
|
316
|
+
await waitForSync(4);
|
|
317
|
+
await project.lix.db.selectFrom("file").selectAll().execute();
|
|
318
|
+
},
|
|
319
|
+
predicate: selectAllFilesPredicate,
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
label: "fs-create",
|
|
323
|
+
fsFactory: createDirectoryFs,
|
|
324
|
+
run: async ({ project, fs }) => {
|
|
325
|
+
fs.writeFileSync(`${PROJECT_PATH}/file-created-on-fs.txt`, "value written by fs", {
|
|
326
|
+
encoding: "utf-8",
|
|
327
|
+
});
|
|
328
|
+
await waitForSync(4);
|
|
329
|
+
await project.lix.db
|
|
330
|
+
.selectFrom("file")
|
|
331
|
+
.selectAll()
|
|
332
|
+
.where("path", "=", "/file-created-on-fs.txt")
|
|
333
|
+
.executeTakeFirstOrThrow();
|
|
334
|
+
},
|
|
335
|
+
predicate: selectFilePredicate("/file-created-on-fs.txt"),
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
label: "fs-update",
|
|
339
|
+
fsFactory: createDirectoryFs,
|
|
340
|
+
run: async ({ project, fs }) => {
|
|
341
|
+
fs.writeFileSync(`${PROJECT_PATH}/settings.json`, JSON.stringify({
|
|
342
|
+
...mockSettings,
|
|
343
|
+
baseLocale: "brand-new-locale-written-to-fs-file",
|
|
344
|
+
}));
|
|
345
|
+
await waitForSync(4);
|
|
346
|
+
await project.lix.db
|
|
347
|
+
.selectFrom("file")
|
|
348
|
+
.selectAll()
|
|
349
|
+
.where("path", "=", "/settings.json")
|
|
350
|
+
.executeTakeFirstOrThrow();
|
|
351
|
+
},
|
|
352
|
+
predicate: selectFilePredicate("/settings.json"),
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
label: "fs-delete",
|
|
356
|
+
fsFactory: createDirectoryFs,
|
|
357
|
+
run: async ({ project, fs }) => {
|
|
358
|
+
await project.lix.db
|
|
359
|
+
.selectFrom("file")
|
|
360
|
+
.selectAll()
|
|
361
|
+
.where("path", "=", "/README.md")
|
|
362
|
+
.executeTakeFirstOrThrow();
|
|
363
|
+
fs.unlinkSync(`${PROJECT_PATH}/README.md`);
|
|
364
|
+
await waitForSync(4);
|
|
365
|
+
await project.lix.db
|
|
366
|
+
.selectFrom("file")
|
|
367
|
+
.selectAll()
|
|
368
|
+
.where("path", "=", "/README.md")
|
|
369
|
+
.execute();
|
|
370
|
+
},
|
|
371
|
+
predicate: deleteFilePredicate("/README.md"),
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
label: "lix-create",
|
|
375
|
+
fsFactory: createDirectoryFs,
|
|
376
|
+
run: async ({ project, fs }) => {
|
|
377
|
+
await project.lix.db
|
|
378
|
+
.insertInto("file")
|
|
379
|
+
.values({
|
|
380
|
+
path: "/file-created-in.lix.txt",
|
|
381
|
+
data: new TextEncoder().encode("random value lix"),
|
|
382
|
+
})
|
|
383
|
+
.execute();
|
|
384
|
+
await waitForSync(4);
|
|
385
|
+
fs.readFileSync(`${PROJECT_PATH}/file-created-in.lix.txt`).toString();
|
|
386
|
+
},
|
|
387
|
+
predicate: selectFileScanPredicate,
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
label: "lix-update",
|
|
391
|
+
fsFactory: createDirectoryFs,
|
|
392
|
+
run: async ({ project, fs }) => {
|
|
393
|
+
await project.lix.db
|
|
394
|
+
.updateTable("file")
|
|
395
|
+
.where("path", "=", "/settings.json")
|
|
396
|
+
.set({
|
|
397
|
+
data: new TextEncoder().encode(JSON.stringify({ ...mockSettings, baseLocale: "brand-new-locale2" })),
|
|
398
|
+
})
|
|
399
|
+
.execute();
|
|
400
|
+
await waitForSync(4);
|
|
401
|
+
fs.readFileSync(`${PROJECT_PATH}/settings.json`).toString();
|
|
402
|
+
},
|
|
403
|
+
predicate: selectFileScanPredicate,
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
label: "lix-delete",
|
|
407
|
+
fsFactory: createDirectoryFs,
|
|
408
|
+
run: async ({ project, fs }) => {
|
|
409
|
+
await project.lix.db
|
|
410
|
+
.deleteFrom("file")
|
|
411
|
+
.where("path", "=", "/.gitignore")
|
|
412
|
+
.execute();
|
|
413
|
+
await waitForSync(4);
|
|
414
|
+
fs.existsSync(`${PROJECT_PATH}/.gitignore`);
|
|
415
|
+
},
|
|
416
|
+
predicate: selectFileScanPredicate,
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
label: "fs-vs-lix-conflict",
|
|
420
|
+
fsFactory: createDirectoryFs,
|
|
421
|
+
run: async ({ project, fs }) => {
|
|
422
|
+
fs.writeFileSync(`${PROJECT_PATH}/settings.json`, JSON.stringify({ ...mockSettings, baseLocale: "fs-version" }));
|
|
423
|
+
await project.lix.db
|
|
424
|
+
.updateTable("file")
|
|
425
|
+
.where("path", "=", "/settings.json")
|
|
426
|
+
.set({
|
|
427
|
+
data: new TextEncoder().encode(JSON.stringify({ ...mockSettings, baseLocale: "lix-version" })),
|
|
428
|
+
})
|
|
429
|
+
.execute();
|
|
430
|
+
await waitForSync(8);
|
|
431
|
+
fs.readFileSync(`${PROJECT_PATH}/settings.json`).toString();
|
|
432
|
+
await project.lix.db
|
|
433
|
+
.selectFrom("file")
|
|
434
|
+
.selectAll()
|
|
435
|
+
.where("path", "=", "/settings.json")
|
|
436
|
+
.executeTakeFirstOrThrow();
|
|
437
|
+
},
|
|
438
|
+
predicate: selectFileScanPredicate,
|
|
439
|
+
},
|
|
440
|
+
];
|
|
441
|
+
/**
|
|
442
|
+
* Captures the EXPLAIN output for a single scenario and persists it as a text artifact.
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* await captureExplainPlan({ project, label: "fs-create", run, predicate });
|
|
446
|
+
*/
|
|
447
|
+
async function captureExplainPlan(args) {
|
|
448
|
+
const { project, label, run, predicate } = args;
|
|
449
|
+
const engine = project.lix.engine;
|
|
450
|
+
if (!engine) {
|
|
451
|
+
throw new Error("Expected an engine on project.lix");
|
|
452
|
+
}
|
|
453
|
+
const executed = [];
|
|
454
|
+
const original = engine.executeSync.bind(engine);
|
|
455
|
+
engine.executeSync = (queryArgs) => {
|
|
456
|
+
if (typeof queryArgs?.sql === "string") {
|
|
457
|
+
const params = Array.isArray(queryArgs.parameters)
|
|
458
|
+
? [...queryArgs.parameters]
|
|
459
|
+
: undefined;
|
|
460
|
+
const entry = {
|
|
461
|
+
sql: queryArgs.sql,
|
|
462
|
+
parameters: params,
|
|
463
|
+
};
|
|
464
|
+
executed.push(entry);
|
|
465
|
+
const start = performance.now();
|
|
466
|
+
try {
|
|
467
|
+
return original(queryArgs);
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
entry.durationMs = performance.now() - start;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return original(queryArgs);
|
|
474
|
+
};
|
|
475
|
+
try {
|
|
476
|
+
await run();
|
|
477
|
+
}
|
|
478
|
+
finally {
|
|
479
|
+
engine.executeSync = original;
|
|
480
|
+
}
|
|
481
|
+
const captured = executed.find((entry) => predicate(entry));
|
|
482
|
+
if (!captured) {
|
|
483
|
+
console.error(`[fs-sync.playground] no query matched for ${label}. Captured:\n${executed
|
|
484
|
+
.map((entry) => entry.sql)
|
|
485
|
+
.join("\n\n")}`);
|
|
486
|
+
}
|
|
487
|
+
expect(captured, `No matching query captured for ${label}`).toBeTruthy();
|
|
488
|
+
await nodeFs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
489
|
+
const explain = (await project.lix.call("lix_explain_query", {
|
|
490
|
+
sql: captured.sql,
|
|
491
|
+
parameters: captured.parameters ?? [],
|
|
492
|
+
}));
|
|
493
|
+
const payload = [
|
|
494
|
+
`-- SQL (${label})`,
|
|
495
|
+
explain.originalSql,
|
|
496
|
+
"",
|
|
497
|
+
"-- parameters",
|
|
498
|
+
JSON.stringify(captured.parameters ?? [], null, 2),
|
|
499
|
+
"",
|
|
500
|
+
`-- time ${captured.durationMs !== undefined
|
|
501
|
+
? captured.durationMs.toFixed(3)
|
|
502
|
+
: "unknown"} ms`,
|
|
503
|
+
"",
|
|
504
|
+
"-- rewritten SQL",
|
|
505
|
+
explain.rewrittenSql ?? "<unchanged>",
|
|
506
|
+
"",
|
|
507
|
+
"-- query plan",
|
|
508
|
+
JSON.stringify(explain.plan, null, 2),
|
|
509
|
+
"",
|
|
510
|
+
].join("\n");
|
|
511
|
+
await nodeFs.writeFile(`${OUTPUT_DIR}/fs-sync.${label}.explain.txt`, payload, "utf8");
|
|
512
|
+
}
|
|
513
|
+
test("fs sync explain plans", async () => {
|
|
514
|
+
await nodeFs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
515
|
+
for (const scenario of SCENARIOS) {
|
|
516
|
+
const fsInstance = scenario.fsFactory();
|
|
517
|
+
const project = await loadProjectFromDirectory({
|
|
518
|
+
fs: fsInstance,
|
|
519
|
+
path: PROJECT_PATH,
|
|
520
|
+
providePlugins: scenario.providePlugins,
|
|
521
|
+
});
|
|
522
|
+
try {
|
|
523
|
+
await captureExplainPlan({
|
|
524
|
+
project,
|
|
525
|
+
label: scenario.label,
|
|
526
|
+
run: () => scenario.run({ project, fs: fsInstance }),
|
|
527
|
+
predicate: scenario.predicate,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
await project.close();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
//# sourceMappingURL=fs-sync.playground.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-sync.playground.test.js","sourceRoot":"/","sources":["project/fs-sync.playground.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,MAAM,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAIrE,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,UAAU,GAAG,kBAAkB,CACpC,IAAI,GAAG,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CACrD,CAAC;AAEF,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAEvC,MAAM,YAAY,GAAG;IACpB,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;IACrB,OAAO,EAAE,EAAE;CACe,CAAC;AAE5B,MAAM,aAAa,GAAG;IACrB,sCAAsC,EAAE,aAAa;IACrD,4BAA4B,EAAE,WAAW;IACzC,iCAAiC,EAAE,gBAAgB;IACnD,2BAA2B,EAAE,cAAc;IAC3C,+BAA+B,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;CAC7D,CAAC;AAEF,MAAM,cAAc,GAAoB;IACvC,GAAG,YAAY;IACf,kBAAkB,EAAE;QACnB,WAAW,EAAE,yCAAyC;KACtD;CACD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,KAAK,EAAE,UAAU,GAAG,CAAC,EAAE,EAAE;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC;AACF,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG,CAAC,CAAc,EAAE,CAAc,EAAW,EAAE;IACrE,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,gBAAgB,GAAiB;IACtC,GAAG,EAAE,kBAAkB;IACvB,iBAAiB,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CACzC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjC,MAAM;QACN,IAAI,EAAE,YAAY,MAAM,OAAO;KAC/B,CAAC,CAAC;IACJ,WAAW,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAChC,MAAM,OAAO,GAAuC,EAAE,CAAC;QACvD,MAAM,QAAQ,GAA0D,EAAE,CAAC;QAC3E,MAAM,QAAQ,GAKR,EAAE,CAAC;QAET,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvD,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA2B,CAAC;YAC7D,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxD,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;gBACjD,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;gBAChE,QAAQ,CAAC,IAAI,CAAC;oBACb,eAAe,EAAE,QAAQ;oBACzB,aAAa,EAAE,IAAI,CAAC,MAAM;oBAC1B,OAAO,EAAE,EAAE;oBACX,OAAO,EAAE;wBACR;4BACC,IAAI,EAAE,MAAM;4BACZ,KAAK;yBACL;qBACD;iBACD,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACxC,CAAC;IACD,WAAW,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC7C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkC,CAAC;QAC3D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAChC,MAAM,oBAAoB,GAAG,QAAQ,CAAC,IAAI,CACzC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,CAC7C,CAAC;YACF,IAAI,CAAC,oBAAoB;gBAAE,SAAS;YACpC,MAAM,OAAO,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAS,CAAC;YACxD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAClC,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;QACjE,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9D,MAAM;YACN,IAAI,EAAE,YAAY,MAAM,OAAO;YAC/B,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;SACvE,CAAC,CAAC,CAAC;IACL,CAAC;CACD,CAAC;AAwBF,MAAM,gBAAgB,GAAG,GAAG,EAAE,CAC7B,MAAM,CAAC,QAAQ,CAAC;IACf,+BAA+B,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;IAC7D,kCAAkC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACzE,kCAAkC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;CACzE,CAAW,CAAC;AAEd,MAAM,yBAAyB,GAAG,GAAG,EAAE,CACtC,MAAM,CAAC,QAAQ,CAAC;IACf,+BAA+B,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;IAC7D,kCAAkC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI;IACzF,kCAAkC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI;CACzF,CAAW,CAAC;AAEd,MAAM,cAAc,GAAG,GAAG,EAAE,CAC3B,MAAM,CAAC,QAAQ,CAAC;IACf,+BAA+B,EAAE,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;IAC/D,kCAAkC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI;IACzF,kCAAkC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI;CACzF,CAAW,CAAC;AAEd,MAAM,iBAAiB,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAW,CAAC;AAEzE,MAAM,uBAAuB,GAAG,CAAC,KAAiB,EAAE,EAAE;IACrD,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAChE,OAAO,CACN,UAAU,CAAC,QAAQ,CAAC,qBAAqB,CAAC;QAC1C,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC;QAClC,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC,CACjC,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,KAAiB,EAAE,EAAE;IACnE,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAChE,OAAO,CACN,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC;QAC/B,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC;QAClC,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC;QACjC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAC9B,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,KAAiB,EAAE,EAAE;IACnE,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAChE,OAAO,CACN,UAAU,CAAC,UAAU,CAAC,oBAAoB,CAAC;QAC3C,UAAU,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QACvC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAC9B,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,uBAAuB,GAAG,CAAC,KAAiB,EAAE,EAAE;IACrD,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;AAC1E,CAAC,CAAC;AAEF,MAAM,SAAS,GAAwB;IACtC;QACC,KAAK,EAAE,wBAAwB;QAC/B,SAAS,EAAE,gBAAgB;QAC3B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;YAC1B,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,WAAW,CAAC,MAAM,CAAC;iBACnB,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,mBAAmB,CAAC;iBACvC,GAAG,CAAC;gBACJ,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAC7B,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAC3C;aACD,CAAC;iBACD,OAAO,EAAE,CAAC;YACZ,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;IACD;QACC,KAAK,EAAE,qBAAqB;QAC5B,SAAS,EAAE,yBAAyB;QACpC,cAAc,EAAE,CAAC,gBAAgB,CAAC;QAClC,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;YAC9B,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAClD,MAAM;gBACN,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAChC,EAAE,CAAC,YAAY,CAAC,GAAG,YAAY,aAAa,MAAM,OAAO,EAAE,OAAO,CAAC,CACnE;aACD,CAAC,CAAC,CAAC;YACJ,MAAM,OAAO,CAAC,WAAW,CAAC;gBACzB,SAAS,EAAE,gBAAgB,CAAC,GAAG;gBAC/B,KAAK,EAAE,YAAY;aACnB,CAAC,CAAC;YACH,MAAM,mBAAmB,GAAG,MAAM,OAAO,CAAC,EAAE;iBAC1C,UAAU,CAAC,SAAS,CAAC;iBACrB,SAAS,EAAE;iBACX,OAAO,EAAE,CAAC;YACZ,MAAM,cAAc,GAAG,mBAAmB,CAAC,IAAI,CAC9C,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,IAAI,CACpC,CAAC;YACF,IAAI,CAAC,cAAc,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;YAC7C,CAAC;YACD,MAAM,OAAO,CAAC,EAAE;iBACd,WAAW,CAAC,SAAS,CAAC;iBACtB,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;iBACjD,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,cAAc,CAAC,EAAE,CAAC;iBAC1C,OAAO,EAAE,CAAC;YAEZ,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC;gBAC/C,SAAS,EAAE,gBAAgB,CAAC,GAAG;aAC/B,CAAC,CAAC;YACH,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;gBAClC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;qBACnC,UAAU,CAAC,MAAM,CAAC;qBAClB,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;qBAChB,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;qBACnC,gBAAgB,EAAE,CAAC;gBAErB,IAAI,aAAsC,CAAC;gBAC3C,IAAI,QAAQ,EAAE,CAAC;oBACd,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAkB,CAAC,CAAC;oBAC7D,aAAa,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC;gBACzC,CAAC;gBACD,MAAM,YAAY,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE;qBACvD,MAAqB,CAAC;gBAExB,IACC,QAAQ;oBACR,aAAa,KAAK,SAAS;oBAC3B,iBAAiB,CAAC,aAAa,EAAE,YAAY,CAAC,EAC7C,CAAC;oBACF,SAAS;gBACV,CAAC;gBAED,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;qBAClB,WAAW,CAAC,MAAM,CAAC;qBACnB,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;qBAC3B,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;qBACnC,OAAO,EAAE,CAAC;YACb,CAAC;YACD,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;IACD;QACC,KAAK,EAAE,eAAe;QACtB,SAAS,EAAE,cAAc;QACzB,cAAc,EAAE,CAAC,gBAAgB,CAAC;QAClC,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;YAC9B,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAClD,MAAM;gBACN,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAChC,EAAE,CAAC,YAAY,CAAC,GAAG,YAAY,aAAa,MAAM,OAAO,EAAE,OAAO,CAAC,CACnE;aACD,CAAC,CAAC,CAAC;YACJ,MAAM,OAAO,CAAC,WAAW,CAAC;gBACzB,SAAS,EAAE,gBAAgB,CAAC,GAAG;gBAC/B,KAAK,EAAE,YAAY;aACnB,CAAC,CAAC;YAEH,MAAM,EAAE,EAAE,EAAE,gBAAgB,EAAE,GAAG,MAAM,OAAO,CAAC,EAAE;iBAC/C,UAAU,CAAC,SAAS,CAAC;iBACrB,MAAM,CAAC,IAAI,CAAC;iBACZ,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC;iBAC1B,uBAAuB,EAAE,CAAC;YAE5B,MAAM,OAAO,CAAC,EAAE;iBACd,WAAW,CAAC,SAAS,CAAC;iBACtB,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;iBACjD,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,gBAAgB,CAAC;iBACzC,OAAO,EAAE,CAAC;YAEZ,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC;gBAC/C,SAAS,EAAE,gBAAgB,CAAC,GAAG;aAC/B,CAAC,CAAC;YAEH,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;gBAClC,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;qBAClB,WAAW,CAAC,MAAM,CAAC;qBACnB,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;qBAC3B,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;qBACnC,OAAO,EAAE,CAAC;YACb,CAAC;YAED,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YAErB,MAAM,aAAa,GAAG,GAAG,IAAI,CAAC,SAAS,CACtC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EACtB,IAAI,EACJ,CAAC,CACD,IAAI,CAAC;YACN,EAAE,CAAC,aAAa,CAAC,GAAG,YAAY,mBAAmB,EAAE,aAAa,CAAC,CAAC;YACpE,MAAM,OAAO,CAAC,WAAW,CAAC;gBACzB,SAAS,EAAE,gBAAgB,CAAC,GAAG;gBAC/B,KAAK,EAAE;oBACN;wBACC,MAAM,EAAE,IAAI;wBACZ,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC;qBAChD;iBACD;aACD,CAAC,CAAC;YAEH,MAAM,sBAAsB,CAAC;gBAC5B,EAAE,EAAE,EAAE,CAAC,QAAe;gBACtB,OAAO;gBACP,IAAI,EAAE,YAAY;aAClB,CAAC,CAAC;YAEH,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;IACD;QACC,KAAK,EAAE,WAAW;QAClB,SAAS,EAAE,gBAAgB;QAC3B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;YAC1B,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,UAAU,CAAC,MAAa,CAAC;iBACzB,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,CAAC;iBACvC,MAAM,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC;iBAC7C,OAAO,EAAE,CAAC;QACb,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;IACD;QACC,KAAK,EAAE,iBAAiB;QACxB,SAAS,EAAE,gBAAgB;QAC3B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;YAC1B,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,WAAW,CAAC,MAAM,CAAC;iBACnB,GAAG,CAAC;gBACJ,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAC7B,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAC,CAChD;aACD,CAAC;iBACD,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,mBAAmB,CAAC;iBACvC,OAAO,EAAE,CAAC;YACZ,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;IACD;QACC,KAAK,EAAE,2BAA2B;QAClC,SAAS,EAAE,iBAAiB;QAC5B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;YAC1B,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC;QAC/D,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;IACD;QACC,KAAK,EAAE,WAAW;QAClB,SAAS,EAAE,iBAAiB;QAC5B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;YAC9B,EAAE,CAAC,aAAa,CACf,GAAG,YAAY,yBAAyB,EACxC,qBAAqB,EACrB;gBACC,QAAQ,EAAE,OAAO;aACjB,CACD,CAAC;YAEF,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YAErB,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,UAAU,CAAC,MAAM,CAAC;iBAClB,SAAS,EAAE;iBACX,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,yBAAyB,CAAC;iBAC7C,uBAAuB,EAAE,CAAC;QAC7B,CAAC;QACD,SAAS,EAAE,mBAAmB,CAAC,yBAAyB,CAAC;KACzD;IACD;QACC,KAAK,EAAE,WAAW;QAClB,SAAS,EAAE,iBAAiB;QAC5B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;YAC9B,EAAE,CAAC,aAAa,CACf,GAAG,YAAY,gBAAgB,EAC/B,IAAI,CAAC,SAAS,CAAC;gBACd,GAAG,YAAY;gBACf,UAAU,EAAE,qCAAqC;aACjD,CAAC,CACF,CAAC;YACF,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,UAAU,CAAC,MAAM,CAAC;iBAClB,SAAS,EAAE;iBACX,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,gBAAgB,CAAC;iBACpC,uBAAuB,EAAE,CAAC;QAC7B,CAAC;QACD,SAAS,EAAE,mBAAmB,CAAC,gBAAgB,CAAC;KAChD;IACD;QACC,KAAK,EAAE,WAAW;QAClB,SAAS,EAAE,iBAAiB;QAC5B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;YAC9B,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,UAAU,CAAC,MAAM,CAAC;iBAClB,SAAS,EAAE;iBACX,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,YAAY,CAAC;iBAChC,uBAAuB,EAAE,CAAC;YAE5B,EAAE,CAAC,UAAU,CAAC,GAAG,YAAY,YAAY,CAAC,CAAC;YAC3C,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,UAAU,CAAC,MAAM,CAAC;iBAClB,SAAS,EAAE;iBACX,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,YAAY,CAAC;iBAChC,OAAO,EAAE,CAAC;QACb,CAAC;QACD,SAAS,EAAE,mBAAmB,CAAC,YAAY,CAAC;KAC5C;IACD;QACC,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,iBAAiB;QAC5B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;YAC9B,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,UAAU,CAAC,MAAM,CAAC;iBAClB,MAAM,CAAC;gBACP,IAAI,EAAE,0BAA0B;gBAChC,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC;aAClD,CAAC;iBACD,OAAO,EAAE,CAAC;YACZ,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YAErB,EAAE,CAAC,YAAY,CAAC,GAAG,YAAY,0BAA0B,CAAC,CAAC,QAAQ,EAAE,CAAC;QACvE,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;IACD;QACC,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,iBAAiB;QAC5B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;YAC9B,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,WAAW,CAAC,MAAM,CAAC;iBACnB,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,gBAAgB,CAAC;iBACpC,GAAG,CAAC;gBACJ,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAC7B,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,CAAC,CACpE;aACD,CAAC;iBACD,OAAO,EAAE,CAAC;YAEZ,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YAErB,EAAE,CAAC,YAAY,CAAC,GAAG,YAAY,gBAAgB,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC7D,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;IACD;QACC,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,iBAAiB;QAC5B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;YAC9B,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,UAAU,CAAC,MAAM,CAAC;iBAClB,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,aAAa,CAAC;iBACjC,OAAO,EAAE,CAAC;YAEZ,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YAErB,EAAE,CAAC,UAAU,CAAC,GAAG,YAAY,aAAa,CAAC,CAAC;QAC7C,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;IACD;QACC,KAAK,EAAE,oBAAoB;QAC3B,SAAS,EAAE,iBAAiB;QAC5B,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;YAC9B,EAAE,CAAC,aAAa,CACf,GAAG,YAAY,gBAAgB,EAC/B,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAC7D,CAAC;YAEF,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,WAAW,CAAC,MAAM,CAAC;iBACnB,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,gBAAgB,CAAC;iBACpC,GAAG,CAAC;gBACJ,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAC7B,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,CAC9D;aACD,CAAC;iBACD,OAAO,EAAE,CAAC;YAEZ,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;YAErB,EAAE,CAAC,YAAY,CAAC,GAAG,YAAY,gBAAgB,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC5D,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE;iBAClB,UAAU,CAAC,MAAM,CAAC;iBAClB,SAAS,EAAE;iBACX,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,gBAAgB,CAAC;iBACpC,uBAAuB,EAAE,CAAC;QAC7B,CAAC;QACD,SAAS,EAAE,uBAAuB;KAClC;CACD,CAAC;AAEF;;;;;GAKG;AACH,KAAK,UAAU,kBAAkB,CAAC,IAKjC;IACA,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAChD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;IAClC,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACtD,CAAC;IACD,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,CAAC,WAAW,GAAG,CAAC,SAAc,EAAE,EAAE;QACvC,IAAI,OAAO,SAAS,EAAE,GAAG,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC;gBACjD,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC;gBAC3B,CAAC,CAAC,SAAS,CAAC;YACb,MAAM,KAAK,GAAe;gBACzB,GAAG,EAAE,SAAS,CAAC,GAAG;gBAClB,UAAU,EAAE,MAAM;aAClB,CAAC;YACF,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAChC,IAAI,CAAC;gBACJ,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAC;YAC5B,CAAC;oBAAS,CAAC;gBACV,KAAK,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;YAC9C,CAAC;QACF,CAAC;QACD,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,IAAI,CAAC;QACJ,MAAM,GAAG,EAAE,CAAC;IACb,CAAC;YAAS,CAAC;QACV,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC;IAC/B,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CACZ,6CAA6C,KAAK,gBAAgB,QAAQ;aACxE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC;aACzB,IAAI,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;IACH,CAAC;IACD,MAAM,CAAC,QAAQ,EAAE,kCAAkC,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC;IACzE,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpD,MAAM,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,mBAAmB,EAAE;QAC5D,GAAG,EAAE,QAAS,CAAC,GAAG;QAClB,UAAU,EAAE,QAAS,CAAC,UAAU,IAAI,EAAE;KACtC,CAAC,CAID,CAAC;IAEF,MAAM,OAAO,GAAG;QACf,WAAW,KAAK,GAAG;QACnB,OAAO,CAAC,WAAW;QACnB,EAAE;QACF,eAAe;QACf,IAAI,CAAC,SAAS,CAAC,QAAS,CAAC,UAAU,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,EAAE;QACF,WACC,QAAS,CAAC,UAAU,KAAK,SAAS;YACjC,CAAC,CAAC,QAAS,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;YACjC,CAAC,CAAC,SACJ,KAAK;QACL,EAAE;QACF,kBAAkB;QAClB,OAAO,CAAC,YAAY,IAAI,aAAa;QACrC,EAAE;QACF,eAAe;QACf,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACrC,EAAE;KACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,MAAM,CAAC,SAAS,CACrB,GAAG,UAAU,YAAY,KAAK,cAAc,EAC5C,OAAO,EACP,MAAM,CACN,CAAC;AACH,CAAC;AAED,IAAI,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;IACxC,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QAClC,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,wBAAwB,CAAC;YAC9C,EAAE,EAAE,UAAiB;YACrB,IAAI,EAAE,YAAY;YAClB,cAAc,EAAE,QAAQ,CAAC,cAAc;SACvC,CAAC,CAAC;QACH,IAAI,CAAC;YACJ,MAAM,kBAAkB,CAAC;gBACxB,OAAO;gBACP,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,UAAiB,EAAE,CAAC;gBAC3D,SAAS,EAAE,QAAQ,CAAC,SAAS;aAC7B,CAAC,CAAC;QACJ,CAAC;gBAAS,CAAC;YACV,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACF,CAAC;AACF,CAAC,CAAC,CAAC","sourcesContent":["import { promises as nodeFs } from \"node:fs\";\nimport { Volume } from \"memfs\";\nimport { expect, test } from \"vitest\";\nimport { loadProjectFromDirectory } from \"./loadProjectFromDirectory.js\";\nimport { saveProjectToDirectory } from \"./saveProjectToDirectory.js\";\nimport type { InlangPlugin } from \"../plugin/schema.js\";\nimport type { ProjectSettings } from \"../json-schema/settings.js\";\nimport type { Text } from \"../json-schema/pattern.js\";\nimport { performance } from \"node:perf_hooks\";\n\nconst OUTPUT_DIR = decodeURIComponent(\n\tnew URL(\"./__playground__\", import.meta.url).pathname\n);\n\nconst PROJECT_PATH = \"/project.inlang\";\n\nconst mockSettings = {\n\tbaseLocale: \"en\",\n\tlocales: [\"en\", \"de\"],\n\tmodules: [],\n} satisfies ProjectSettings;\n\nconst mockDirectory = {\n\t\"/project.inlang/cache/plugin/29j49j2\": \"cache value\",\n\t\"/project.inlang/.gitignore\": \"git value\",\n\t\"/project.inlang/prettierrc.json\": \"prettier value\",\n\t\"/project.inlang/README.md\": \"readme value\",\n\t\"/project.inlang/settings.json\": JSON.stringify(mockSettings),\n};\n\nconst branchSettings: ProjectSettings = {\n\t...mockSettings,\n\t\"test-json-plugin\": {\n\t\tpathPattern: \"./project.inlang/messages/{locale}.json\",\n\t},\n};\n\n/**\n * Waits for the sync loop to flush asynchronous work.\n *\n * @param iterations - Number of scheduler loops to wait for.\n * @example\n * await waitForSync(3);\n */\nconst waitForSync = async (iterations = 1) => {\n\tfor (let i = 0; i < iterations; i++) {\n\t\tawait new Promise((resolve) => setTimeout(resolve, 10));\n\t}\n};\n\n/**\n * Compares array buffers without cloning them.\n *\n * @example\n * const same = arrayBuffersEqual(bufA, bufB);\n */\nconst arrayBuffersEqual = (a: ArrayBuffer, b: ArrayBuffer): boolean => {\n\tif (a.byteLength !== b.byteLength) {\n\t\treturn false;\n\t}\n\tconst viewA = new Uint8Array(a);\n\tconst viewB = new Uint8Array(b);\n\tfor (let i = 0; i < viewA.byteLength; i++) {\n\t\tif (viewA[i] !== viewB[i]) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n};\n\n/**\n * Minimal JSON plugin that mirrors the behaviour from the primary sync tests.\n *\n * @example\n * await project.importFiles({ pluginKey: simpleJsonPlugin.key, files });\n */\nconst simpleJsonPlugin: InlangPlugin = {\n\tkey: \"test-json-plugin\",\n\ttoBeImportedFiles: async ({ settings }) =>\n\t\tsettings.locales.map((locale) => ({\n\t\t\tlocale,\n\t\t\tpath: `messages/${locale}.json`,\n\t\t})),\n\timportFiles: async ({ files }) => {\n\t\tconst bundles: { id: string; declarations: [] }[] = [];\n\t\tconst messages: { bundleId: string; locale: string; selectors: [] }[] = [];\n\t\tconst variants: {\n\t\t\tmessageBundleId: string;\n\t\t\tmessageLocale: string;\n\t\t\tmatches: [];\n\t\t\tpattern: Text[];\n\t\t}[] = [];\n\n\t\tfor (const file of files) {\n\t\t\tconst decoded = new TextDecoder().decode(file.content);\n\t\t\tif (!decoded) continue;\n\t\t\tconst parsed = JSON.parse(decoded) as Record<string, string>;\n\t\t\tfor (const [bundleId, value] of Object.entries(parsed)) {\n\t\t\t\tbundles.push({ id: bundleId, declarations: [] });\n\t\t\t\tmessages.push({ bundleId, locale: file.locale, selectors: [] });\n\t\t\t\tvariants.push({\n\t\t\t\t\tmessageBundleId: bundleId,\n\t\t\t\t\tmessageLocale: file.locale,\n\t\t\t\t\tmatches: [],\n\t\t\t\t\tpattern: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn { bundles, messages, variants };\n\t},\n\texportFiles: async ({ messages, variants }) => {\n\t\tconst byLocale = new Map<string, Record<string, string>>();\n\t\tfor (const message of messages) {\n\t\t\tconst correspondingVariant = variants.find(\n\t\t\t\t(variant) => variant.messageId === message.id\n\t\t\t);\n\t\t\tif (!correspondingVariant) continue;\n\t\t\tconst pattern = correspondingVariant.pattern[0] as Text;\n\t\t\tif (!byLocale.has(message.locale)) {\n\t\t\t\tbyLocale.set(message.locale, {});\n\t\t\t}\n\t\t\tbyLocale.get(message.locale)![message.bundleId] = pattern.value;\n\t\t}\n\n\t\treturn Array.from(byLocale.entries()).map(([locale, json]) => ({\n\t\t\tlocale,\n\t\t\tname: `messages/${locale}.json`,\n\t\t\tcontent: new TextEncoder().encode(`${JSON.stringify(json, null, 2)}\\n`),\n\t\t}));\n\t},\n};\n\ntype ProjectInstance = Awaited<ReturnType<typeof loadProjectFromDirectory>>;\ntype FsLike = typeof import(\"fs\");\n\ntype QueryEntry = {\n\tsql: string;\n\tparameters: unknown[] | undefined;\n\tdurationMs?: number;\n};\n\ntype ScenarioContext = {\n\tproject: ProjectInstance;\n\tfs: FsLike;\n};\n\ntype Scenario = {\n\tlabel: string;\n\tfsFactory: () => FsLike;\n\tprovidePlugins?: InlangPlugin[];\n\trun: (context: ScenarioContext) => Promise<void>;\n\tpredicate: (entry: QueryEntry) => boolean;\n};\n\nconst createMessagesFs = () =>\n\tVolume.fromJSON({\n\t\t\"/project.inlang/settings.json\": JSON.stringify(mockSettings),\n\t\t\"/project.inlang/messages/en.json\": JSON.stringify({ greeting: \"Hello\" }),\n\t\t\"/project.inlang/messages/de.json\": JSON.stringify({ greeting: \"Hallo\" }),\n\t}) as FsLike;\n\nconst createFormattedMessagesFs = () =>\n\tVolume.fromJSON({\n\t\t\"/project.inlang/settings.json\": JSON.stringify(mockSettings),\n\t\t\"/project.inlang/messages/en.json\": `${JSON.stringify({ greeting: \"Hello\" }, null, 2)}\\n`,\n\t\t\"/project.inlang/messages/de.json\": `${JSON.stringify({ greeting: \"Hallo\" }, null, 2)}\\n`,\n\t}) as FsLike;\n\nconst createBranchFs = () =>\n\tVolume.fromJSON({\n\t\t\"/project.inlang/settings.json\": JSON.stringify(branchSettings),\n\t\t\"/project.inlang/messages/en.json\": `${JSON.stringify({ greeting: \"Hello\" }, null, 2)}\\n`,\n\t\t\"/project.inlang/messages/de.json\": `${JSON.stringify({ greeting: \"Hallo\" }, null, 2)}\\n`,\n\t}) as FsLike;\n\nconst createDirectoryFs = () => Volume.fromJSON(mockDirectory) as FsLike;\n\nconst selectFileScanPredicate = (entry: QueryEntry) => {\n\tconst normalised = entry.sql.replace(/\\s+/g, \" \").toLowerCase();\n\treturn (\n\t\tnormalised.includes('\"lixcol_writer_key\"') &&\n\t\tnormalised.includes('from \"file\"') &&\n\t\tnormalised.includes(\"not like ?\")\n\t);\n};\n\nconst selectFilePredicate = (path: string) => (entry: QueryEntry) => {\n\tconst normalised = entry.sql.replace(/\\s+/g, \" \").toLowerCase();\n\treturn (\n\t\tnormalised.startsWith(\"select\") &&\n\t\tnormalised.includes('from \"file\"') &&\n\t\tnormalised.includes('\"path\" = ?') &&\n\t\tentry.parameters?.[0] === path\n\t);\n};\n\nconst deleteFilePredicate = (path: string) => (entry: QueryEntry) => {\n\tconst normalised = entry.sql.replace(/\\s+/g, \" \").toLowerCase();\n\treturn (\n\t\tnormalised.startsWith('delete from \"file\"') &&\n\t\tnormalised.includes('where \"path\" = ?') &&\n\t\tentry.parameters?.[0] === path\n\t);\n};\n\nconst selectAllFilesPredicate = (entry: QueryEntry) => {\n\treturn entry.sql.trim().toLowerCase().startsWith('select * from \"file\"');\n};\n\nconst SCENARIOS: readonly Scenario[] = [\n\t{\n\t\tlabel: \"touched-locale-rewrite\",\n\t\tfsFactory: createMessagesFs,\n\t\trun: async ({ project }) => {\n\t\t\tawait project.lix.db\n\t\t\t\t.updateTable(\"file\")\n\t\t\t\t.where(\"path\", \"=\", \"/messages/en.json\")\n\t\t\t\t.set({\n\t\t\t\t\tdata: new TextEncoder().encode(\n\t\t\t\t\t\tJSON.stringify({ greeting: \"Hi from lix\" })\n\t\t\t\t\t),\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t\tawait waitForSync(3);\n\t\t},\n\t\tpredicate: selectFileScanPredicate,\n\t},\n\t{\n\t\tlabel: \"plugin-export-delta\",\n\t\tfsFactory: createFormattedMessagesFs,\n\t\tprovidePlugins: [simpleJsonPlugin],\n\t\trun: async ({ project, fs }) => {\n\t\t\tawait waitForSync(3);\n\t\t\tconst initialFiles = [\"en\", \"de\"].map((locale) => ({\n\t\t\t\tlocale,\n\t\t\t\tcontent: new TextEncoder().encode(\n\t\t\t\t\tfs.readFileSync(`${PROJECT_PATH}/messages/${locale}.json`, \"utf-8\")\n\t\t\t\t),\n\t\t\t}));\n\t\t\tawait project.importFiles({\n\t\t\t\tpluginKey: simpleJsonPlugin.key,\n\t\t\t\tfiles: initialFiles,\n\t\t\t});\n\t\t\tconst messagesAfterImport = await project.db\n\t\t\t\t.selectFrom(\"message\")\n\t\t\t\t.selectAll()\n\t\t\t\t.execute();\n\t\t\tconst englishMessage = messagesAfterImport.find(\n\t\t\t\t(message) => message.locale === \"en\"\n\t\t\t);\n\t\t\tif (!englishMessage) {\n\t\t\t\tthrow new Error(\"expected english message\");\n\t\t\t}\n\t\t\tawait project.db\n\t\t\t\t.updateTable(\"variant\")\n\t\t\t\t.set({ pattern: [{ type: \"text\", value: \"Hi\" }] })\n\t\t\t\t.where(\"messageId\", \"=\", englishMessage.id)\n\t\t\t\t.execute();\n\n\t\t\tconst exportedFiles = await project.exportFiles({\n\t\t\t\tpluginKey: simpleJsonPlugin.key,\n\t\t\t});\n\t\t\tfor (const file of exportedFiles) {\n\t\t\t\tconst existing = await project.lix.db\n\t\t\t\t\t.selectFrom(\"file\")\n\t\t\t\t\t.select([\"data\"])\n\t\t\t\t\t.where(\"path\", \"=\", `/${file.name}`)\n\t\t\t\t\t.executeTakeFirst();\n\n\t\t\t\tlet currentBuffer: ArrayBuffer | undefined;\n\t\t\t\tif (existing) {\n\t\t\t\t\tconst dataView = new Uint8Array(existing.data as Uint8Array);\n\t\t\t\t\tcurrentBuffer = dataView.slice().buffer;\n\t\t\t\t}\n\t\t\t\tconst targetBuffer = new Uint8Array(file.content).slice()\n\t\t\t\t\t.buffer as ArrayBuffer;\n\n\t\t\t\tif (\n\t\t\t\t\texisting &&\n\t\t\t\t\tcurrentBuffer !== undefined &&\n\t\t\t\t\tarrayBuffersEqual(currentBuffer, targetBuffer)\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait project.lix.db\n\t\t\t\t\t.updateTable(\"file\")\n\t\t\t\t\t.set({ data: file.content })\n\t\t\t\t\t.where(\"path\", \"=\", `/${file.name}`)\n\t\t\t\t\t.execute();\n\t\t\t}\n\t\t\tawait waitForSync(3);\n\t\t},\n\t\tpredicate: selectFileScanPredicate,\n\t},\n\t{\n\t\tlabel: \"branch-switch\",\n\t\tfsFactory: createBranchFs,\n\t\tprovidePlugins: [simpleJsonPlugin],\n\t\trun: async ({ project, fs }) => {\n\t\t\tawait waitForSync(5);\n\t\t\tconst initialFiles = [\"en\", \"de\"].map((locale) => ({\n\t\t\t\tlocale,\n\t\t\t\tcontent: new TextEncoder().encode(\n\t\t\t\t\tfs.readFileSync(`${PROJECT_PATH}/messages/${locale}.json`, \"utf-8\")\n\t\t\t\t),\n\t\t\t}));\n\t\t\tawait project.importFiles({\n\t\t\t\tpluginKey: simpleJsonPlugin.key,\n\t\t\t\tfiles: initialFiles,\n\t\t\t});\n\n\t\t\tconst { id: englishMessageId } = await project.db\n\t\t\t\t.selectFrom(\"message\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"locale\", \"=\", \"en\")\n\t\t\t\t.executeTakeFirstOrThrow();\n\n\t\t\tawait project.db\n\t\t\t\t.updateTable(\"variant\")\n\t\t\t\t.set({ pattern: [{ type: \"text\", value: \"Hi\" }] })\n\t\t\t\t.where(\"messageId\", \"=\", englishMessageId)\n\t\t\t\t.execute();\n\n\t\t\tconst exportedFiles = await project.exportFiles({\n\t\t\t\tpluginKey: simpleJsonPlugin.key,\n\t\t\t});\n\n\t\t\tfor (const file of exportedFiles) {\n\t\t\t\tawait project.lix.db\n\t\t\t\t\t.updateTable(\"file\")\n\t\t\t\t\t.set({ data: file.content })\n\t\t\t\t\t.where(\"path\", \"=\", `/${file.name}`)\n\t\t\t\t\t.execute();\n\t\t\t}\n\n\t\t\tawait waitForSync(5);\n\n\t\t\tconst branchContent = `${JSON.stringify(\n\t\t\t\t{ greeting: \"Branch\" },\n\t\t\t\tnull,\n\t\t\t\t2\n\t\t\t)}\\n`;\n\t\t\tfs.writeFileSync(`${PROJECT_PATH}/messages/en.json`, branchContent);\n\t\t\tawait project.importFiles({\n\t\t\t\tpluginKey: simpleJsonPlugin.key,\n\t\t\t\tfiles: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlocale: \"en\",\n\t\t\t\t\t\tcontent: new TextEncoder().encode(branchContent),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tawait saveProjectToDirectory({\n\t\t\t\tfs: fs.promises as any,\n\t\t\t\tproject,\n\t\t\t\tpath: PROJECT_PATH,\n\t\t\t});\n\n\t\t\tawait waitForSync(3);\n\t\t},\n\t\tpredicate: selectFileScanPredicate,\n\t},\n\t{\n\t\tlabel: \"noop-sync\",\n\t\tfsFactory: createMessagesFs,\n\t\trun: async ({ project }) => {\n\t\t\tawait waitForSync(6);\n\t\t\tawait project.lix.db\n\t\t\t\t.selectFrom(\"file\" as any)\n\t\t\t\t.where(\"path\", \"not like\", \"%db.sqlite\")\n\t\t\t\t.select([\"path\", \"data\", \"lixcol_writer_key\"])\n\t\t\t\t.execute();\n\t\t},\n\t\tpredicate: selectFileScanPredicate,\n\t},\n\t{\n\t\tlabel: \"lix-only-update\",\n\t\tfsFactory: createMessagesFs,\n\t\trun: async ({ project }) => {\n\t\t\tawait waitForSync(3);\n\t\t\tawait project.lix.db\n\t\t\t\t.updateTable(\"file\")\n\t\t\t\t.set({\n\t\t\t\t\tdata: new TextEncoder().encode(\n\t\t\t\t\t\tJSON.stringify({ greeting: \"Hi from lix-only\" })\n\t\t\t\t\t),\n\t\t\t\t})\n\t\t\t\t.where(\"path\", \"=\", \"/messages/en.json\")\n\t\t\t\t.execute();\n\t\t\tawait waitForSync(3);\n\t\t},\n\t\tpredicate: selectFileScanPredicate,\n\t},\n\t{\n\t\tlabel: \"fs-files-available-in-lix\",\n\t\tfsFactory: createDirectoryFs,\n\t\trun: async ({ project }) => {\n\t\t\tawait waitForSync(4);\n\t\t\tawait project.lix.db.selectFrom(\"file\").selectAll().execute();\n\t\t},\n\t\tpredicate: selectAllFilesPredicate,\n\t},\n\t{\n\t\tlabel: \"fs-create\",\n\t\tfsFactory: createDirectoryFs,\n\t\trun: async ({ project, fs }) => {\n\t\t\tfs.writeFileSync(\n\t\t\t\t`${PROJECT_PATH}/file-created-on-fs.txt`,\n\t\t\t\t\"value written by fs\",\n\t\t\t\t{\n\t\t\t\t\tencoding: \"utf-8\",\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait waitForSync(4);\n\n\t\t\tawait project.lix.db\n\t\t\t\t.selectFrom(\"file\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"path\", \"=\", \"/file-created-on-fs.txt\")\n\t\t\t\t.executeTakeFirstOrThrow();\n\t\t},\n\t\tpredicate: selectFilePredicate(\"/file-created-on-fs.txt\"),\n\t},\n\t{\n\t\tlabel: \"fs-update\",\n\t\tfsFactory: createDirectoryFs,\n\t\trun: async ({ project, fs }) => {\n\t\t\tfs.writeFileSync(\n\t\t\t\t`${PROJECT_PATH}/settings.json`,\n\t\t\t\tJSON.stringify({\n\t\t\t\t\t...mockSettings,\n\t\t\t\t\tbaseLocale: \"brand-new-locale-written-to-fs-file\",\n\t\t\t\t})\n\t\t\t);\n\t\t\tawait waitForSync(4);\n\t\t\tawait project.lix.db\n\t\t\t\t.selectFrom(\"file\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"path\", \"=\", \"/settings.json\")\n\t\t\t\t.executeTakeFirstOrThrow();\n\t\t},\n\t\tpredicate: selectFilePredicate(\"/settings.json\"),\n\t},\n\t{\n\t\tlabel: \"fs-delete\",\n\t\tfsFactory: createDirectoryFs,\n\t\trun: async ({ project, fs }) => {\n\t\t\tawait project.lix.db\n\t\t\t\t.selectFrom(\"file\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"path\", \"=\", \"/README.md\")\n\t\t\t\t.executeTakeFirstOrThrow();\n\n\t\t\tfs.unlinkSync(`${PROJECT_PATH}/README.md`);\n\t\t\tawait waitForSync(4);\n\t\t\tawait project.lix.db\n\t\t\t\t.selectFrom(\"file\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"path\", \"=\", \"/README.md\")\n\t\t\t\t.execute();\n\t\t},\n\t\tpredicate: deleteFilePredicate(\"/README.md\"),\n\t},\n\t{\n\t\tlabel: \"lix-create\",\n\t\tfsFactory: createDirectoryFs,\n\t\trun: async ({ project, fs }) => {\n\t\t\tawait project.lix.db\n\t\t\t\t.insertInto(\"file\")\n\t\t\t\t.values({\n\t\t\t\t\tpath: \"/file-created-in.lix.txt\",\n\t\t\t\t\tdata: new TextEncoder().encode(\"random value lix\"),\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t\tawait waitForSync(4);\n\n\t\t\tfs.readFileSync(`${PROJECT_PATH}/file-created-in.lix.txt`).toString();\n\t\t},\n\t\tpredicate: selectFileScanPredicate,\n\t},\n\t{\n\t\tlabel: \"lix-update\",\n\t\tfsFactory: createDirectoryFs,\n\t\trun: async ({ project, fs }) => {\n\t\t\tawait project.lix.db\n\t\t\t\t.updateTable(\"file\")\n\t\t\t\t.where(\"path\", \"=\", \"/settings.json\")\n\t\t\t\t.set({\n\t\t\t\t\tdata: new TextEncoder().encode(\n\t\t\t\t\t\tJSON.stringify({ ...mockSettings, baseLocale: \"brand-new-locale2\" })\n\t\t\t\t\t),\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tawait waitForSync(4);\n\n\t\t\tfs.readFileSync(`${PROJECT_PATH}/settings.json`).toString();\n\t\t},\n\t\tpredicate: selectFileScanPredicate,\n\t},\n\t{\n\t\tlabel: \"lix-delete\",\n\t\tfsFactory: createDirectoryFs,\n\t\trun: async ({ project, fs }) => {\n\t\t\tawait project.lix.db\n\t\t\t\t.deleteFrom(\"file\")\n\t\t\t\t.where(\"path\", \"=\", \"/.gitignore\")\n\t\t\t\t.execute();\n\n\t\t\tawait waitForSync(4);\n\n\t\t\tfs.existsSync(`${PROJECT_PATH}/.gitignore`);\n\t\t},\n\t\tpredicate: selectFileScanPredicate,\n\t},\n\t{\n\t\tlabel: \"fs-vs-lix-conflict\",\n\t\tfsFactory: createDirectoryFs,\n\t\trun: async ({ project, fs }) => {\n\t\t\tfs.writeFileSync(\n\t\t\t\t`${PROJECT_PATH}/settings.json`,\n\t\t\t\tJSON.stringify({ ...mockSettings, baseLocale: \"fs-version\" })\n\t\t\t);\n\n\t\t\tawait project.lix.db\n\t\t\t\t.updateTable(\"file\")\n\t\t\t\t.where(\"path\", \"=\", \"/settings.json\")\n\t\t\t\t.set({\n\t\t\t\t\tdata: new TextEncoder().encode(\n\t\t\t\t\t\tJSON.stringify({ ...mockSettings, baseLocale: \"lix-version\" })\n\t\t\t\t\t),\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tawait waitForSync(8);\n\n\t\t\tfs.readFileSync(`${PROJECT_PATH}/settings.json`).toString();\n\t\t\tawait project.lix.db\n\t\t\t\t.selectFrom(\"file\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"path\", \"=\", \"/settings.json\")\n\t\t\t\t.executeTakeFirstOrThrow();\n\t\t},\n\t\tpredicate: selectFileScanPredicate,\n\t},\n];\n\n/**\n * Captures the EXPLAIN output for a single scenario and persists it as a text artifact.\n *\n * @example\n * await captureExplainPlan({ project, label: \"fs-create\", run, predicate });\n */\nasync function captureExplainPlan(args: {\n\tproject: ProjectInstance;\n\tlabel: string;\n\trun: () => Promise<void>;\n\tpredicate: (entry: QueryEntry) => boolean;\n}): Promise<void> {\n\tconst { project, label, run, predicate } = args;\n\tconst engine = project.lix.engine;\n\tif (!engine) {\n\t\tthrow new Error(\"Expected an engine on project.lix\");\n\t}\n\tconst executed: QueryEntry[] = [];\n\tconst original = engine.executeSync.bind(engine);\n\tengine.executeSync = (queryArgs: any) => {\n\t\tif (typeof queryArgs?.sql === \"string\") {\n\t\t\tconst params = Array.isArray(queryArgs.parameters)\n\t\t\t\t? [...queryArgs.parameters]\n\t\t\t\t: undefined;\n\t\t\tconst entry: QueryEntry = {\n\t\t\t\tsql: queryArgs.sql,\n\t\t\t\tparameters: params,\n\t\t\t};\n\t\t\texecuted.push(entry);\n\t\t\tconst start = performance.now();\n\t\t\ttry {\n\t\t\t\treturn original(queryArgs);\n\t\t\t} finally {\n\t\t\t\tentry.durationMs = performance.now() - start;\n\t\t\t}\n\t\t}\n\t\treturn original(queryArgs);\n\t};\n\n\ttry {\n\t\tawait run();\n\t} finally {\n\t\tengine.executeSync = original;\n\t}\n\n\tconst captured = executed.find((entry) => predicate(entry));\n\tif (!captured) {\n\t\tconsole.error(\n\t\t\t`[fs-sync.playground] no query matched for ${label}. Captured:\\n${executed\n\t\t\t\t.map((entry) => entry.sql)\n\t\t\t\t.join(\"\\n\\n\")}`\n\t\t);\n\t}\n\texpect(captured, `No matching query captured for ${label}`).toBeTruthy();\n\tawait nodeFs.mkdir(OUTPUT_DIR, { recursive: true });\n\n\tconst explain = (await project.lix.call(\"lix_explain_query\", {\n\t\tsql: captured!.sql,\n\t\tparameters: captured!.parameters ?? [],\n\t})) as {\n\t\toriginalSql: string;\n\t\trewrittenSql?: string;\n\t\tplan: unknown;\n\t};\n\n\tconst payload = [\n\t\t`-- SQL (${label})`,\n\t\texplain.originalSql,\n\t\t\"\",\n\t\t\"-- parameters\",\n\t\tJSON.stringify(captured!.parameters ?? [], null, 2),\n\t\t\"\",\n\t\t`-- time ${\n\t\t\tcaptured!.durationMs !== undefined\n\t\t\t\t? captured!.durationMs.toFixed(3)\n\t\t\t\t: \"unknown\"\n\t\t} ms`,\n\t\t\"\",\n\t\t\"-- rewritten SQL\",\n\t\texplain.rewrittenSql ?? \"<unchanged>\",\n\t\t\"\",\n\t\t\"-- query plan\",\n\t\tJSON.stringify(explain.plan, null, 2),\n\t\t\"\",\n\t].join(\"\\n\");\n\n\tawait nodeFs.writeFile(\n\t\t`${OUTPUT_DIR}/fs-sync.${label}.explain.txt`,\n\t\tpayload,\n\t\t\"utf8\"\n\t);\n}\n\ntest(\"fs sync explain plans\", async () => {\n\tawait nodeFs.mkdir(OUTPUT_DIR, { recursive: true });\n\tfor (const scenario of SCENARIOS) {\n\t\tconst fsInstance = scenario.fsFactory();\n\t\tconst project = await loadProjectFromDirectory({\n\t\t\tfs: fsInstance as any,\n\t\t\tpath: PROJECT_PATH,\n\t\t\tprovidePlugins: scenario.providePlugins,\n\t\t});\n\t\ttry {\n\t\t\tawait captureExplainPlan({\n\t\t\t\tproject,\n\t\t\t\tlabel: scenario.label,\n\t\t\t\trun: () => scenario.run({ project, fs: fsInstance as any }),\n\t\t\t\tpredicate: scenario.predicate,\n\t\t\t});\n\t\t} finally {\n\t\t\tawait project.close();\n\t\t}\n\t}\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadProjectFromDirectory.d.ts","sourceRoot":"/","sources":["project/loadProjectFromDirectory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAqC,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,KAAK,EACX,YAAY,EACZ,0BAA0B,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAIlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC7C,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,EAAE,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAClE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,EACzC,MAAM,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;GAmKD;
|
|
1
|
+
{"version":3,"file":"loadProjectFromDirectory.d.ts","sourceRoot":"/","sources":["project/loadProjectFromDirectory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAqC,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,KAAK,EACX,YAAY,EACZ,0BAA0B,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAIlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC7C,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,EAAE,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAClE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,EACzC,MAAM,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;GAmKD;AAkgBD,qBAAa,yBAA0B,SAAQ,KAAK;gBACvC,MAAM,EAAE,MAAM;CAM1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAChC,EAAE,EAAE,0BAA0B,EAC9B,WAAW,EAAE,MAAM,GACjB,0BAA0B,CAgB5B;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACtC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACd,MAAM,CAoBR;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IACjD,IAAI,EAAE,MAAM,CAAC;gBAED,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAMhD"}
|
|
@@ -542,10 +542,14 @@ async function importLocalPlugins(args) {
|
|
|
542
542
|
if (args.preprocessPluginBeforeImport) {
|
|
543
543
|
moduleAsText = await args.preprocessPluginBeforeImport(moduleAsText);
|
|
544
544
|
}
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
|
|
545
|
+
// In bun we need to do dynamic imports differently
|
|
546
|
+
const pluginAsUrl = process.versions.bun
|
|
547
|
+
? URL.createObjectURL(new Blob([moduleAsText], { type: "text/javascript" }))
|
|
548
|
+
: "data:application/javascript," + encodeURIComponent(moduleAsText);
|
|
549
|
+
const { default: plugin } = await import(/* @vite-ignore */ pluginAsUrl);
|
|
548
550
|
locallyImportedPlugins.push(plugin);
|
|
551
|
+
if (process.versions.bun)
|
|
552
|
+
URL.revokeObjectURL(pluginAsUrl);
|
|
549
553
|
}
|
|
550
554
|
catch (e) {
|
|
551
555
|
errors.push(new PluginImportError({ plugin: module, cause: e }));
|