@vibes.diy/vibe-runtime 12.1.0 → 12.1.2
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/app-ready-signal.js +3 -0
- package/app-ready-signal.js.map +1 -1
- package/backend-executor.d.ts +31 -1
- package/backend-executor.js +1 -0
- package/backend-executor.js.map +1 -1
- package/backend-worker-loader-executor.d.ts +5 -0
- package/backend-worker-loader-executor.js +100 -11
- package/backend-worker-loader-executor.js.map +1 -1
- package/boot-marks.d.ts +15 -0
- package/boot-marks.js +26 -0
- package/boot-marks.js.map +1 -0
- package/firefly-database.d.ts +1 -0
- package/firefly-database.js +1 -1
- package/firefly-database.js.map +1 -1
- package/index.d.ts +2 -1
- package/index.js +2 -1
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/parse-backend-config.d.ts +5 -0
- package/parse-backend-config.js +549 -28
- package/parse-backend-config.js.map +1 -1
- package/register-dependencies.js +20 -3
- package/register-dependencies.js.map +1 -1
- package/vibe-log.d.ts +35 -0
- package/vibe-log.js +228 -0
- package/vibe-log.js.map +1 -0
package/parse-backend-config.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export const MIN_INTERVAL_MS = 5_000;
|
|
2
2
|
export const MAX_INTERVAL_MS = 3_600_000;
|
|
3
3
|
const HANDLER_NAMES = ["fetch", "scheduled", "onChange"];
|
|
4
|
-
function exportsName(
|
|
4
|
+
function exportsName(code, name) {
|
|
5
5
|
const decl = new RegExp(`export\\s+(?:async\\s+)?(?:function\\s+${name}\\b|(?:const|let|var)\\s+${name}\\b)`);
|
|
6
|
-
if (decl.test(
|
|
6
|
+
if (decl.test(code))
|
|
7
7
|
return true;
|
|
8
8
|
const listPattern = /export\s*\{([^}]*)\}/g;
|
|
9
9
|
let m;
|
|
10
|
-
while ((m = listPattern.exec(
|
|
10
|
+
while ((m = listPattern.exec(code)) !== null) {
|
|
11
11
|
const inner = m[1];
|
|
12
12
|
const bareOrAliased = new RegExp(`(?:\\bas\\s+${name}\\b|(?:^|,)\\s*${name}\\s*(?:,|$))`);
|
|
13
13
|
if (bareOrAliased.test(inner))
|
|
@@ -25,58 +25,573 @@ function parseDurationMs(raw) {
|
|
|
25
25
|
const mult = m[2] === "s" ? 1_000 : m[2] === "m" ? 60_000 : 3_600_000;
|
|
26
26
|
return n * mult;
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
const sliceAligned = (a, from, to) => ({
|
|
29
|
+
code: a.code.slice(from, to),
|
|
30
|
+
text: a.text.slice(from, to),
|
|
31
|
+
});
|
|
32
|
+
function skipQuoted(src, start, quote) {
|
|
33
|
+
let i = start + 1;
|
|
34
|
+
while (i < src.length) {
|
|
35
|
+
const ch = src[i];
|
|
36
|
+
if (ch === "\\") {
|
|
37
|
+
i += 2;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === quote)
|
|
41
|
+
return i + 1;
|
|
42
|
+
if (ch === "\n")
|
|
43
|
+
return i;
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
return src.length;
|
|
47
|
+
}
|
|
48
|
+
function skipTemplate(src, start) {
|
|
49
|
+
let i = start + 1;
|
|
50
|
+
while (i < src.length) {
|
|
51
|
+
const ch = src[i];
|
|
52
|
+
if (ch === "\\") {
|
|
53
|
+
i += 2;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (ch === "`")
|
|
57
|
+
return i + 1;
|
|
58
|
+
if (ch === "$" && src[i + 1] === "{") {
|
|
59
|
+
let depth = 1;
|
|
60
|
+
i += 2;
|
|
61
|
+
while (i < src.length && depth > 0) {
|
|
62
|
+
const c = src[i];
|
|
63
|
+
if (c === "\\") {
|
|
64
|
+
i += 2;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (c === '"' || c === "'") {
|
|
68
|
+
i = skipQuoted(src, i, c);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (c === "`") {
|
|
72
|
+
i = skipTemplate(src, i);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (c === "{")
|
|
76
|
+
depth++;
|
|
77
|
+
else if (c === "}")
|
|
78
|
+
depth--;
|
|
35
79
|
i++;
|
|
36
|
-
continue;
|
|
37
80
|
}
|
|
38
|
-
if (ch === quote)
|
|
39
|
-
quote = null;
|
|
40
81
|
continue;
|
|
41
82
|
}
|
|
42
|
-
|
|
43
|
-
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
return src.length;
|
|
86
|
+
}
|
|
87
|
+
function skipRegex(src, start) {
|
|
88
|
+
let i = start + 1;
|
|
89
|
+
let inClass = false;
|
|
90
|
+
while (i < src.length) {
|
|
91
|
+
const ch = src[i];
|
|
92
|
+
if (ch === "\\") {
|
|
93
|
+
i += 2;
|
|
94
|
+
continue;
|
|
44
95
|
}
|
|
45
|
-
|
|
46
|
-
|
|
96
|
+
if (ch === "\n")
|
|
97
|
+
return undefined;
|
|
98
|
+
if (inClass) {
|
|
99
|
+
if (ch === "]")
|
|
100
|
+
inClass = false;
|
|
47
101
|
}
|
|
102
|
+
else if (ch === "[") {
|
|
103
|
+
inClass = true;
|
|
104
|
+
}
|
|
105
|
+
else if (ch === "/") {
|
|
106
|
+
i++;
|
|
107
|
+
while (i < src.length && /[a-z]/i.test(src[i]))
|
|
108
|
+
i++;
|
|
109
|
+
return i;
|
|
110
|
+
}
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const REGEX_PRECEDING_KEYWORDS = new Set([
|
|
116
|
+
"return",
|
|
117
|
+
"typeof",
|
|
118
|
+
"instanceof",
|
|
119
|
+
"in",
|
|
120
|
+
"of",
|
|
121
|
+
"new",
|
|
122
|
+
"delete",
|
|
123
|
+
"void",
|
|
124
|
+
"do",
|
|
125
|
+
"else",
|
|
126
|
+
"case",
|
|
127
|
+
"yield",
|
|
128
|
+
"await",
|
|
129
|
+
"throw",
|
|
130
|
+
]);
|
|
131
|
+
function maskNonCode(source) {
|
|
132
|
+
const chars = source.split("");
|
|
133
|
+
const blank = (from, to) => {
|
|
134
|
+
for (let k = from; k < to && k < chars.length; k++) {
|
|
135
|
+
if (chars[k] !== "\n")
|
|
136
|
+
chars[k] = " ";
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
let prevIsValue = false;
|
|
140
|
+
let prevIsDot = false;
|
|
141
|
+
let i = 0;
|
|
142
|
+
while (i < source.length) {
|
|
143
|
+
const ch = source[i];
|
|
144
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
145
|
+
let j = i + 2;
|
|
146
|
+
while (j < source.length && source[j] !== "\n")
|
|
147
|
+
j++;
|
|
148
|
+
blank(i, j);
|
|
149
|
+
i = j;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
153
|
+
let j = i + 2;
|
|
154
|
+
while (j < source.length && !(source[j] === "*" && source[j + 1] === "/"))
|
|
155
|
+
j++;
|
|
156
|
+
j = Math.min(j + 2, source.length);
|
|
157
|
+
blank(i, j);
|
|
158
|
+
i = j;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (ch === '"' || ch === "'") {
|
|
162
|
+
i = skipQuoted(source, i, ch);
|
|
163
|
+
prevIsValue = true;
|
|
164
|
+
prevIsDot = false;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (ch === "`") {
|
|
168
|
+
const end = skipTemplate(source, i);
|
|
169
|
+
blank(i, end);
|
|
170
|
+
i = end;
|
|
171
|
+
prevIsValue = true;
|
|
172
|
+
prevIsDot = false;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (ch === "/") {
|
|
176
|
+
if (!prevIsValue) {
|
|
177
|
+
const end = skipRegex(source, i);
|
|
178
|
+
if (end !== undefined) {
|
|
179
|
+
blank(i, end);
|
|
180
|
+
i = end;
|
|
181
|
+
prevIsValue = true;
|
|
182
|
+
prevIsDot = false;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
i++;
|
|
187
|
+
prevIsValue = false;
|
|
188
|
+
prevIsDot = false;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (/[A-Za-z0-9_$]/.test(ch)) {
|
|
192
|
+
let j = i;
|
|
193
|
+
while (j < source.length && /[A-Za-z0-9_$]/.test(source[j]))
|
|
194
|
+
j++;
|
|
195
|
+
prevIsValue = prevIsDot || !REGEX_PRECEDING_KEYWORDS.has(source.slice(i, j));
|
|
196
|
+
prevIsDot = false;
|
|
197
|
+
i = j;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (ch === ")" || ch === "]") {
|
|
201
|
+
prevIsValue = true;
|
|
202
|
+
prevIsDot = false;
|
|
203
|
+
i++;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if ((ch === "+" && source[i + 1] === "+") || (ch === "-" && source[i + 1] === "-")) {
|
|
207
|
+
prevIsValue = true;
|
|
208
|
+
prevIsDot = false;
|
|
209
|
+
i += 2;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (/\s/.test(ch)) {
|
|
213
|
+
i++;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
prevIsValue = false;
|
|
217
|
+
prevIsDot = ch === ".";
|
|
218
|
+
i++;
|
|
219
|
+
}
|
|
220
|
+
return chars.join("");
|
|
221
|
+
}
|
|
222
|
+
function blankStrings(masked) {
|
|
223
|
+
const chars = masked.split("");
|
|
224
|
+
let i = 0;
|
|
225
|
+
while (i < masked.length) {
|
|
226
|
+
const ch = masked[i];
|
|
227
|
+
if (ch === '"' || ch === "'") {
|
|
228
|
+
const end = skipQuoted(masked, i, ch);
|
|
229
|
+
const interiorEnd = masked[end - 1] === ch ? end - 1 : end;
|
|
230
|
+
for (let k = i + 1; k < interiorEnd && k < chars.length; k++) {
|
|
231
|
+
if (chars[k] !== "\n")
|
|
232
|
+
chars[k] = " ";
|
|
233
|
+
}
|
|
234
|
+
i = end;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
i++;
|
|
238
|
+
}
|
|
239
|
+
return chars.join("");
|
|
240
|
+
}
|
|
241
|
+
function viewsOf(source) {
|
|
242
|
+
const text = maskNonCode(source);
|
|
243
|
+
return { text, code: blankStrings(text) };
|
|
244
|
+
}
|
|
245
|
+
function skipSpace(code, from) {
|
|
246
|
+
let i = from;
|
|
247
|
+
while (i < code.length && /\s/.test(code[i]))
|
|
248
|
+
i++;
|
|
249
|
+
return i;
|
|
250
|
+
}
|
|
251
|
+
function quotedSpan(code, start) {
|
|
252
|
+
const quote = code[start];
|
|
253
|
+
let i = start + 1;
|
|
254
|
+
while (i < code.length && code[i] !== quote) {
|
|
255
|
+
if (code[i] === "\n")
|
|
256
|
+
return undefined;
|
|
257
|
+
i++;
|
|
258
|
+
}
|
|
259
|
+
return i < code.length ? { end: i + 1 } : undefined;
|
|
260
|
+
}
|
|
261
|
+
function mentionsKey(view, key) {
|
|
262
|
+
if (new RegExp(`\\b${key}\\s*:`).test(view.code))
|
|
263
|
+
return true;
|
|
264
|
+
const code = view.code;
|
|
265
|
+
for (let i = 0; i < code.length; i++) {
|
|
266
|
+
if (code[i] !== '"' && code[i] !== "'")
|
|
267
|
+
continue;
|
|
268
|
+
const span = quotedSpan(code, i);
|
|
269
|
+
if (span === undefined)
|
|
270
|
+
continue;
|
|
271
|
+
if (view.text.slice(i + 1, span.end - 1) === key && code[skipSpace(code, span.end)] === ":")
|
|
272
|
+
return true;
|
|
273
|
+
i = span.end - 1;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
function balancedObjectEnd(code, openIdx) {
|
|
278
|
+
let depth = 0;
|
|
279
|
+
for (let i = openIdx; i < code.length; i++) {
|
|
280
|
+
const ch = code[i];
|
|
281
|
+
if (ch === "{")
|
|
282
|
+
depth++;
|
|
48
283
|
else if (ch === "}") {
|
|
49
284
|
depth--;
|
|
50
285
|
if (depth === 0)
|
|
51
|
-
return
|
|
286
|
+
return i + 1;
|
|
52
287
|
}
|
|
53
288
|
}
|
|
54
289
|
return undefined;
|
|
55
290
|
}
|
|
56
|
-
function extractConfigObject(
|
|
57
|
-
const decl = /(?:export\s+)?(?:const|let|var)\s+config\s*=\s*\{/.exec(
|
|
291
|
+
function extractConfigObject(src) {
|
|
292
|
+
const decl = /(?:export\s+)?(?:const|let|var)\s+config\s*=\s*\{/.exec(src.code);
|
|
58
293
|
if (decl === null)
|
|
59
294
|
return undefined;
|
|
60
295
|
const openIdx = decl.index + decl[0].length - 1;
|
|
61
|
-
|
|
296
|
+
const end = balancedObjectEnd(src.code, openIdx);
|
|
297
|
+
return end === undefined ? undefined : sliceAligned(src, openIdx, end);
|
|
62
298
|
}
|
|
63
|
-
function
|
|
64
|
-
const
|
|
299
|
+
function exportedConfigLocalNames(code) {
|
|
300
|
+
const names = new Set();
|
|
301
|
+
if (/export\s+(?:const|let|var)\s+config\s*=/.test(code))
|
|
302
|
+
names.add("config");
|
|
303
|
+
const listPattern = /export\s*\{([^}]*)\}/g;
|
|
304
|
+
let m;
|
|
305
|
+
while ((m = listPattern.exec(code)) !== null) {
|
|
306
|
+
for (const entry of m[1].split(",")) {
|
|
307
|
+
const aliased = /^\s*([A-Za-z_$][\w$]*)\s+as\s+config\s*$/.exec(entry);
|
|
308
|
+
if (aliased !== null) {
|
|
309
|
+
names.add(aliased[1]);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (/^\s*config\s*$/.test(entry))
|
|
313
|
+
names.add("config");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return [...names];
|
|
317
|
+
}
|
|
318
|
+
function topLevelObjectBindings(code) {
|
|
319
|
+
const at = /(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*\{/y;
|
|
320
|
+
const found = [];
|
|
321
|
+
let depth = 0;
|
|
322
|
+
for (let i = 0; i < code.length; i++) {
|
|
323
|
+
const ch = code[i];
|
|
324
|
+
if (ch === "{" || ch === "(" || ch === "[") {
|
|
325
|
+
depth++;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (ch === "}" || ch === ")" || ch === "]") {
|
|
329
|
+
depth--;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (depth !== 0)
|
|
333
|
+
continue;
|
|
334
|
+
if (i > 0 && /[\w$]/.test(code[i - 1]))
|
|
335
|
+
continue;
|
|
336
|
+
at.lastIndex = i;
|
|
337
|
+
const m = at.exec(code);
|
|
338
|
+
if (m !== null) {
|
|
339
|
+
const openIdx = i + m[0].length - 1;
|
|
340
|
+
found.push({ name: m[1], openIdx });
|
|
341
|
+
depth++;
|
|
342
|
+
i = openIdx;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return found;
|
|
346
|
+
}
|
|
347
|
+
function extractExportedConfigObject(src) {
|
|
348
|
+
const localNames = exportedConfigLocalNames(src.code);
|
|
349
|
+
if (localNames.length === 0)
|
|
350
|
+
return undefined;
|
|
351
|
+
const bindings = topLevelObjectBindings(src.code).filter((b) => localNames.includes(b.name));
|
|
352
|
+
if (bindings.length !== 1)
|
|
353
|
+
return undefined;
|
|
354
|
+
const end = balancedObjectEnd(src.code, bindings[0].openIdx);
|
|
355
|
+
return end === undefined ? undefined : sliceAligned(src, bindings[0].openIdx, end);
|
|
356
|
+
}
|
|
357
|
+
function extractIntervalLiteral(src) {
|
|
358
|
+
const configObj = extractConfigObject(src);
|
|
65
359
|
if (configObj === undefined)
|
|
66
360
|
return undefined;
|
|
67
|
-
const m = /scheduled\s*:\s*\{[^}]*?\binterval\s*:\s*["']([^"']+)["']/s.exec(configObj);
|
|
361
|
+
const m = /scheduled\s*:\s*\{[^}]*?\binterval\s*:\s*["']([^"']+)["']/s.exec(configObj.text);
|
|
68
362
|
return m?.[1];
|
|
69
363
|
}
|
|
364
|
+
const OPEN_BRACKETS = "{[(";
|
|
365
|
+
const CLOSE_BRACKETS = "}])";
|
|
366
|
+
function readTopLevelKey(obj, start) {
|
|
367
|
+
const code = obj.code;
|
|
368
|
+
const ch = code[start];
|
|
369
|
+
const ambiguous = { key: { ambiguous: true }, next: start };
|
|
370
|
+
if (ch === "[")
|
|
371
|
+
return ambiguous;
|
|
372
|
+
if (ch === ".")
|
|
373
|
+
return ambiguous;
|
|
374
|
+
if (ch === "*")
|
|
375
|
+
return ambiguous;
|
|
376
|
+
let name;
|
|
377
|
+
let nameEnd;
|
|
378
|
+
if (ch === '"' || ch === "'") {
|
|
379
|
+
const span = quotedSpan(code, start);
|
|
380
|
+
if (span === undefined)
|
|
381
|
+
return ambiguous;
|
|
382
|
+
const body = obj.text.slice(start + 1, span.end - 1);
|
|
383
|
+
if (body.includes("\\"))
|
|
384
|
+
return { key: { ambiguous: true }, next: span.end };
|
|
385
|
+
name = body;
|
|
386
|
+
nameEnd = span.end;
|
|
387
|
+
}
|
|
388
|
+
else if (/[A-Za-z_$]/.test(ch)) {
|
|
389
|
+
let j = start;
|
|
390
|
+
while (j < code.length && /[\w$]/.test(code[j]))
|
|
391
|
+
j++;
|
|
392
|
+
name = code.slice(start, j);
|
|
393
|
+
nameEnd = j;
|
|
394
|
+
if (name === "get" || name === "set" || name === "async") {
|
|
395
|
+
const after = skipSpace(code, nameEnd);
|
|
396
|
+
if (after < code.length && /["'[*A-Za-z_$]/.test(code[after]))
|
|
397
|
+
return ambiguous;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
else if (/[0-9]/.test(ch)) {
|
|
401
|
+
let j = start;
|
|
402
|
+
while (j < code.length && /[\w$.]/.test(code[j]))
|
|
403
|
+
j++;
|
|
404
|
+
name = code.slice(start, j);
|
|
405
|
+
nameEnd = j;
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
return ambiguous;
|
|
409
|
+
}
|
|
410
|
+
const afterName = skipSpace(code, nameEnd);
|
|
411
|
+
if (code[afterName] === ":") {
|
|
412
|
+
const valueStart = skipSpace(code, afterName + 1);
|
|
413
|
+
return { key: { name, valueStart, ambiguous: false }, next: valueStart };
|
|
414
|
+
}
|
|
415
|
+
return { key: { name, ambiguous: false }, next: nameEnd };
|
|
416
|
+
}
|
|
417
|
+
function topLevelKeys(obj) {
|
|
418
|
+
const code = obj.code;
|
|
419
|
+
const keys = [];
|
|
420
|
+
let depth = 0;
|
|
421
|
+
let expectKey = false;
|
|
422
|
+
let i = 0;
|
|
423
|
+
while (i < code.length) {
|
|
424
|
+
const ch = code[i];
|
|
425
|
+
if (depth === 1 && expectKey && !/\s/.test(ch) && ch !== "}") {
|
|
426
|
+
const read = readTopLevelKey(obj, i);
|
|
427
|
+
keys.push(read.key);
|
|
428
|
+
expectKey = false;
|
|
429
|
+
i = read.next;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (OPEN_BRACKETS.includes(ch)) {
|
|
433
|
+
depth++;
|
|
434
|
+
if (depth === 1 && ch === "{")
|
|
435
|
+
expectKey = true;
|
|
436
|
+
i++;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (CLOSE_BRACKETS.includes(ch)) {
|
|
440
|
+
depth--;
|
|
441
|
+
i++;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (depth === 1 && ch === ",")
|
|
445
|
+
expectKey = true;
|
|
446
|
+
i++;
|
|
447
|
+
}
|
|
448
|
+
return keys;
|
|
449
|
+
}
|
|
450
|
+
const named = (keys, key) => keys.filter((k) => k.name === key);
|
|
451
|
+
function objectAt(obj, valueStart) {
|
|
452
|
+
if (obj.code[valueStart] !== "{")
|
|
453
|
+
return undefined;
|
|
454
|
+
const end = balancedObjectEnd(obj.code, valueStart);
|
|
455
|
+
return end === undefined ? undefined : sliceAligned(obj, valueStart, end);
|
|
456
|
+
}
|
|
457
|
+
function valueTextAt(obj, valueStart) {
|
|
458
|
+
const OPEN = OPEN_BRACKETS;
|
|
459
|
+
const CLOSE = CLOSE_BRACKETS;
|
|
460
|
+
let depth = 0;
|
|
461
|
+
let j = valueStart;
|
|
462
|
+
for (; j < obj.code.length; j++) {
|
|
463
|
+
const c = obj.code[j];
|
|
464
|
+
if (OPEN.includes(c)) {
|
|
465
|
+
depth++;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (depth === 0 && (c === "," || c === "}"))
|
|
469
|
+
break;
|
|
470
|
+
if (CLOSE.includes(c))
|
|
471
|
+
depth--;
|
|
472
|
+
}
|
|
473
|
+
return obj.text.slice(valueStart, j).trim();
|
|
474
|
+
}
|
|
475
|
+
function splitArrayEntries(body) {
|
|
476
|
+
const parts = [];
|
|
477
|
+
let start = 0;
|
|
478
|
+
for (let i = 0; i < body.code.length; i++) {
|
|
479
|
+
if (body.code[i] === ",") {
|
|
480
|
+
parts.push(body.text.slice(start, i));
|
|
481
|
+
start = i + 1;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
parts.push(body.text.slice(start));
|
|
485
|
+
return parts.map((p) => p.trim()).filter((p) => p !== "");
|
|
486
|
+
}
|
|
487
|
+
function stringLiteralValue(raw) {
|
|
488
|
+
const s = raw.trim();
|
|
489
|
+
if (s.startsWith('"'))
|
|
490
|
+
return /^"((?:[^"\\]|\\.)*)"$/.exec(s)?.[1];
|
|
491
|
+
if (s.startsWith("'"))
|
|
492
|
+
return /^'((?:[^'\\]|\\.)*)'$/.exec(s)?.[1];
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
const duplicateKeyError = (where, key, count) => `config.fetch.unfilteredReads is ambiguous: ${where} declares a duplicate \`${key}\` key (${count} times) — JavaScript keeps only the LAST one, so the declaration in the source is not the declaration that runs. Remove the extra \`${key}\` key(s) so exactly one remains, then push again`;
|
|
496
|
+
const ambiguousKeyError = (where) => `config.fetch.unfilteredReads is ambiguous: ${where} has a property key this push-time parser cannot read statically — a computed \`[key]\`, a spread, an accessor/generator, or a quoted name with escapes. Any of those could shadow \`fetch\`/\`unfilteredReads\`/\`dbs\`/\`why\`, so write those keys literally (bare or plainly quoted) alongside no unreadable siblings, then push again`;
|
|
497
|
+
function readableTopLevelKeys(obj, where) {
|
|
498
|
+
const keys = topLevelKeys(obj);
|
|
499
|
+
if (keys.some((k) => k.ambiguous))
|
|
500
|
+
return { errors: [ambiguousKeyError(where)] };
|
|
501
|
+
return { keys, errors: [] };
|
|
502
|
+
}
|
|
503
|
+
function extractFetchUnfilteredReads(src) {
|
|
504
|
+
const configObj = extractExportedConfigObject(src);
|
|
505
|
+
if (configObj === undefined) {
|
|
506
|
+
if (!mentionsKey(src, "unfilteredReads"))
|
|
507
|
+
return { errors: [] };
|
|
508
|
+
return {
|
|
509
|
+
errors: [
|
|
510
|
+
'config.fetch.unfilteredReads must be declared in the exported top-level config object — declare it as `export const config = { fetch: { unfilteredReads: { dbs: ["db-name"], why: "…" } } }`; a declaration on any other config binding is ignored',
|
|
511
|
+
],
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
if (!mentionsKey(configObj, "unfilteredReads"))
|
|
515
|
+
return { errors: [] };
|
|
516
|
+
const shape = 'config.fetch.unfilteredReads must be { dbs: ["db-name"], why: "…" } with static string literals';
|
|
517
|
+
const configKeys = readableTopLevelKeys(configObj, "the exported config object");
|
|
518
|
+
if (configKeys.keys === undefined)
|
|
519
|
+
return { errors: configKeys.errors };
|
|
520
|
+
const fetchKeys = named(configKeys.keys, "fetch");
|
|
521
|
+
if (fetchKeys.length > 1)
|
|
522
|
+
return { errors: [duplicateKeyError("the exported config object", "fetch", fetchKeys.length)] };
|
|
523
|
+
const fetchStart = fetchKeys[0]?.valueStart;
|
|
524
|
+
const fetchObj = fetchStart === undefined ? undefined : objectAt(configObj, fetchStart);
|
|
525
|
+
if (fetchObj === undefined)
|
|
526
|
+
return { errors: [shape] };
|
|
527
|
+
const fetchLaneKeys = readableTopLevelKeys(fetchObj, "config.fetch");
|
|
528
|
+
if (fetchLaneKeys.keys === undefined)
|
|
529
|
+
return { errors: fetchLaneKeys.errors };
|
|
530
|
+
const declKeys = named(fetchLaneKeys.keys, "unfilteredReads");
|
|
531
|
+
if (declKeys.length > 1)
|
|
532
|
+
return { errors: [duplicateKeyError("config.fetch", "unfilteredReads", declKeys.length)] };
|
|
533
|
+
const declStart = declKeys[0]?.valueStart;
|
|
534
|
+
const decl = declStart === undefined ? undefined : objectAt(fetchObj, declStart);
|
|
535
|
+
if (decl === undefined)
|
|
536
|
+
return { errors: [shape] };
|
|
537
|
+
const errors = [];
|
|
538
|
+
const declInner = readableTopLevelKeys(decl, "config.fetch.unfilteredReads");
|
|
539
|
+
if (declInner.keys === undefined)
|
|
540
|
+
return { errors: declInner.errors };
|
|
541
|
+
const dbsKeys = named(declInner.keys, "dbs");
|
|
542
|
+
const whyKeys = named(declInner.keys, "why");
|
|
543
|
+
if (dbsKeys.length > 1 || whyKeys.length > 1) {
|
|
544
|
+
const dupes = [];
|
|
545
|
+
if (dbsKeys.length > 1)
|
|
546
|
+
dupes.push(duplicateKeyError("config.fetch.unfilteredReads", "dbs", dbsKeys.length));
|
|
547
|
+
if (whyKeys.length > 1)
|
|
548
|
+
dupes.push(duplicateKeyError("config.fetch.unfilteredReads", "why", whyKeys.length));
|
|
549
|
+
return { errors: dupes };
|
|
550
|
+
}
|
|
551
|
+
const dbsStart = dbsKeys[0]?.valueStart;
|
|
552
|
+
const whyStart = whyKeys[0]?.valueStart;
|
|
553
|
+
const dbsValue = dbsStart === undefined ? undefined : valueTextAt(decl, dbsStart);
|
|
554
|
+
let dbs;
|
|
555
|
+
if (dbsValue === undefined || !dbsValue.startsWith("[") || !dbsValue.endsWith("]")) {
|
|
556
|
+
errors.push(`${shape} — dbs must be an array literal of string literals`);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
const bodyStart = dbsStart + 1;
|
|
560
|
+
const parts = splitArrayEntries(sliceAligned(decl, bodyStart, bodyStart + dbsValue.length - 2));
|
|
561
|
+
const parsed = parts.map(stringLiteralValue);
|
|
562
|
+
if (parts.length === 0) {
|
|
563
|
+
errors.push(`${shape} — dbs must name at least one database`);
|
|
564
|
+
}
|
|
565
|
+
else if (parsed.some((v) => v === undefined)) {
|
|
566
|
+
errors.push(`${shape} — dbs entries must be string literals; computed/indirect values are unsupported`);
|
|
567
|
+
}
|
|
568
|
+
else if (parsed.some((v) => v?.trim() === "")) {
|
|
569
|
+
errors.push(`${shape} — dbs entries must be non-empty database names`);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
dbs = parsed;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const whyValue = whyStart === undefined ? undefined : valueTextAt(decl, whyStart);
|
|
576
|
+
const why = whyValue === undefined ? undefined : stringLiteralValue(whyValue);
|
|
577
|
+
if (why === undefined || why.trim() === "") {
|
|
578
|
+
errors.push(`${shape} — why must be a non-empty string literal stating why this handler authorizes these reads itself`);
|
|
579
|
+
}
|
|
580
|
+
if (errors.length > 0 || dbs === undefined || why === undefined)
|
|
581
|
+
return { errors };
|
|
582
|
+
return { value: { dbs, why }, errors };
|
|
583
|
+
}
|
|
70
584
|
export function parseBackendConfig(source) {
|
|
71
585
|
const errors = [];
|
|
72
586
|
if (source === undefined || source.trim() === "") {
|
|
73
587
|
return { handlers: [], hasConfig: false, errors };
|
|
74
588
|
}
|
|
75
|
-
const
|
|
76
|
-
const
|
|
589
|
+
const src = viewsOf(source);
|
|
590
|
+
const handlers = HANDLER_NAMES.filter((name) => exportsName(src.code, name));
|
|
591
|
+
const hasConfig = exportsName(src.code, "config");
|
|
77
592
|
const hasScheduled = handlers.includes("scheduled");
|
|
78
593
|
let schedule;
|
|
79
|
-
const rawInterval = extractIntervalLiteral(
|
|
594
|
+
const rawInterval = extractIntervalLiteral(src);
|
|
80
595
|
if (hasScheduled) {
|
|
81
596
|
if (rawInterval === undefined) {
|
|
82
597
|
errors.push('scheduled handler requires config.scheduled.interval as a static string-literal duration, e.g. { scheduled: { interval: "5m" } }; computed/indirect values are unsupported');
|
|
@@ -97,6 +612,12 @@ export function parseBackendConfig(source) {
|
|
|
97
612
|
}
|
|
98
613
|
}
|
|
99
614
|
}
|
|
100
|
-
|
|
615
|
+
let fetchUnfilteredReads;
|
|
616
|
+
if (handlers.includes("fetch")) {
|
|
617
|
+
const parsedReads = extractFetchUnfilteredReads(src);
|
|
618
|
+
errors.push(...parsedReads.errors);
|
|
619
|
+
fetchUnfilteredReads = parsedReads.value;
|
|
620
|
+
}
|
|
621
|
+
return { handlers, hasConfig, schedule, fetchUnfilteredReads, errors };
|
|
101
622
|
}
|
|
102
623
|
//# sourceMappingURL=parse-backend-config.js.map
|