@vibes.diy/vibe-runtime 12.1.1 → 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 +13 -0
- package/backend-executor.js +1 -0
- package/backend-executor.js.map +1 -1
- package/backend-worker-loader-executor.d.ts +4 -0
- package/backend-worker-loader-executor.js +75 -10
- 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/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/parse-backend-config.js +277 -160
- 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,6 +25,10 @@ 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
|
+
const sliceAligned = (a, from, to) => ({
|
|
29
|
+
code: a.code.slice(from, to),
|
|
30
|
+
text: a.text.slice(from, to),
|
|
31
|
+
});
|
|
28
32
|
function skipQuoted(src, start, quote) {
|
|
29
33
|
let i = start + 1;
|
|
30
34
|
while (i < src.length) {
|
|
@@ -124,23 +128,6 @@ const REGEX_PRECEDING_KEYWORDS = new Set([
|
|
|
124
128
|
"await",
|
|
125
129
|
"throw",
|
|
126
130
|
]);
|
|
127
|
-
function regexAllowedAt(chars, idx) {
|
|
128
|
-
let i = idx - 1;
|
|
129
|
-
while (i >= 0 && /\s/.test(chars[i]))
|
|
130
|
-
i--;
|
|
131
|
-
if (i < 0)
|
|
132
|
-
return true;
|
|
133
|
-
const ch = chars[i];
|
|
134
|
-
if (ch === ")" || ch === "]")
|
|
135
|
-
return false;
|
|
136
|
-
if (/[A-Za-z0-9_$]/.test(ch)) {
|
|
137
|
-
let j = i;
|
|
138
|
-
while (j >= 0 && /[A-Za-z0-9_$]/.test(chars[j]))
|
|
139
|
-
j--;
|
|
140
|
-
return REGEX_PRECEDING_KEYWORDS.has(chars.slice(j + 1, i + 1).join(""));
|
|
141
|
-
}
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
131
|
function maskNonCode(source) {
|
|
145
132
|
const chars = source.split("");
|
|
146
133
|
const blank = (from, to) => {
|
|
@@ -149,6 +136,8 @@ function maskNonCode(source) {
|
|
|
149
136
|
chars[k] = " ";
|
|
150
137
|
}
|
|
151
138
|
};
|
|
139
|
+
let prevIsValue = false;
|
|
140
|
+
let prevIsDot = false;
|
|
152
141
|
let i = 0;
|
|
153
142
|
while (i < source.length) {
|
|
154
143
|
const ch = source[i];
|
|
@@ -171,82 +160,149 @@ function maskNonCode(source) {
|
|
|
171
160
|
}
|
|
172
161
|
if (ch === '"' || ch === "'") {
|
|
173
162
|
i = skipQuoted(source, i, ch);
|
|
163
|
+
prevIsValue = true;
|
|
164
|
+
prevIsDot = false;
|
|
174
165
|
continue;
|
|
175
166
|
}
|
|
176
167
|
if (ch === "`") {
|
|
177
168
|
const end = skipTemplate(source, i);
|
|
178
169
|
blank(i, end);
|
|
179
170
|
i = end;
|
|
171
|
+
prevIsValue = true;
|
|
172
|
+
prevIsDot = false;
|
|
180
173
|
continue;
|
|
181
174
|
}
|
|
182
|
-
if (ch === "/"
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
+
}
|
|
188
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;
|
|
189
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 === ".";
|
|
190
218
|
i++;
|
|
191
219
|
}
|
|
192
220
|
return chars.join("");
|
|
193
221
|
}
|
|
194
|
-
function
|
|
195
|
-
const
|
|
196
|
-
|
|
222
|
+
function blankStrings(masked) {
|
|
223
|
+
const chars = masked.split("");
|
|
224
|
+
let i = 0;
|
|
225
|
+
while (i < masked.length) {
|
|
197
226
|
const ch = masked[i];
|
|
198
227
|
if (ch === '"' || ch === "'") {
|
|
199
|
-
|
|
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;
|
|
200
235
|
continue;
|
|
201
236
|
}
|
|
202
|
-
|
|
203
|
-
|
|
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)] === ":")
|
|
204
272
|
return true;
|
|
273
|
+
i = span.end - 1;
|
|
205
274
|
}
|
|
206
275
|
return false;
|
|
207
276
|
}
|
|
208
|
-
function
|
|
277
|
+
function balancedObjectEnd(code, openIdx) {
|
|
209
278
|
let depth = 0;
|
|
210
|
-
let
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (quote !== null) {
|
|
214
|
-
if (ch === "\\") {
|
|
215
|
-
i++;
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (ch === quote)
|
|
219
|
-
quote = null;
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
if (ch === '"' || ch === "'" || ch === "`") {
|
|
223
|
-
quote = ch;
|
|
224
|
-
}
|
|
225
|
-
else if (ch === "{") {
|
|
279
|
+
for (let i = openIdx; i < code.length; i++) {
|
|
280
|
+
const ch = code[i];
|
|
281
|
+
if (ch === "{")
|
|
226
282
|
depth++;
|
|
227
|
-
}
|
|
228
283
|
else if (ch === "}") {
|
|
229
284
|
depth--;
|
|
230
285
|
if (depth === 0)
|
|
231
|
-
return
|
|
286
|
+
return i + 1;
|
|
232
287
|
}
|
|
233
288
|
}
|
|
234
289
|
return undefined;
|
|
235
290
|
}
|
|
236
|
-
function extractConfigObject(
|
|
237
|
-
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);
|
|
238
293
|
if (decl === null)
|
|
239
294
|
return undefined;
|
|
240
295
|
const openIdx = decl.index + decl[0].length - 1;
|
|
241
|
-
|
|
296
|
+
const end = balancedObjectEnd(src.code, openIdx);
|
|
297
|
+
return end === undefined ? undefined : sliceAligned(src, openIdx, end);
|
|
242
298
|
}
|
|
243
|
-
function exportedConfigLocalNames(
|
|
299
|
+
function exportedConfigLocalNames(code) {
|
|
244
300
|
const names = new Set();
|
|
245
|
-
if (/export\s+(?:const|let|var)\s+config\s*=/.test(
|
|
301
|
+
if (/export\s+(?:const|let|var)\s+config\s*=/.test(code))
|
|
246
302
|
names.add("config");
|
|
247
303
|
const listPattern = /export\s*\{([^}]*)\}/g;
|
|
248
304
|
let m;
|
|
249
|
-
while ((m = listPattern.exec(
|
|
305
|
+
while ((m = listPattern.exec(code)) !== null) {
|
|
250
306
|
for (const entry of m[1].split(",")) {
|
|
251
307
|
const aliased = /^\s*([A-Za-z_$][\w$]*)\s+as\s+config\s*$/.exec(entry);
|
|
252
308
|
if (aliased !== null) {
|
|
@@ -259,16 +315,12 @@ function exportedConfigLocalNames(masked) {
|
|
|
259
315
|
}
|
|
260
316
|
return [...names];
|
|
261
317
|
}
|
|
262
|
-
function topLevelObjectBindings(
|
|
318
|
+
function topLevelObjectBindings(code) {
|
|
263
319
|
const at = /(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*\{/y;
|
|
264
320
|
const found = [];
|
|
265
321
|
let depth = 0;
|
|
266
|
-
for (let i = 0; i <
|
|
267
|
-
const ch =
|
|
268
|
-
if (ch === '"' || ch === "'") {
|
|
269
|
-
i = skipQuoted(masked, i, ch) - 1;
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
322
|
+
for (let i = 0; i < code.length; i++) {
|
|
323
|
+
const ch = code[i];
|
|
272
324
|
if (ch === "{" || ch === "(" || ch === "[") {
|
|
273
325
|
depth++;
|
|
274
326
|
continue;
|
|
@@ -279,10 +331,10 @@ function topLevelObjectBindings(masked) {
|
|
|
279
331
|
}
|
|
280
332
|
if (depth !== 0)
|
|
281
333
|
continue;
|
|
282
|
-
if (i > 0 && /[\w$]/.test(
|
|
334
|
+
if (i > 0 && /[\w$]/.test(code[i - 1]))
|
|
283
335
|
continue;
|
|
284
336
|
at.lastIndex = i;
|
|
285
|
-
const m = at.exec(
|
|
337
|
+
const m = at.exec(code);
|
|
286
338
|
if (m !== null) {
|
|
287
339
|
const openIdx = i + m[0].length - 1;
|
|
288
340
|
found.push({ name: m[1], openIdx });
|
|
@@ -292,119 +344,144 @@ function topLevelObjectBindings(masked) {
|
|
|
292
344
|
}
|
|
293
345
|
return found;
|
|
294
346
|
}
|
|
295
|
-
function extractExportedConfigObject(
|
|
296
|
-
const localNames = exportedConfigLocalNames(
|
|
347
|
+
function extractExportedConfigObject(src) {
|
|
348
|
+
const localNames = exportedConfigLocalNames(src.code);
|
|
297
349
|
if (localNames.length === 0)
|
|
298
350
|
return undefined;
|
|
299
|
-
const bindings = topLevelObjectBindings(
|
|
351
|
+
const bindings = topLevelObjectBindings(src.code).filter((b) => localNames.includes(b.name));
|
|
300
352
|
if (bindings.length !== 1)
|
|
301
353
|
return undefined;
|
|
302
|
-
|
|
354
|
+
const end = balancedObjectEnd(src.code, bindings[0].openIdx);
|
|
355
|
+
return end === undefined ? undefined : sliceAligned(src, bindings[0].openIdx, end);
|
|
303
356
|
}
|
|
304
|
-
function extractIntervalLiteral(
|
|
305
|
-
const configObj = extractConfigObject(
|
|
357
|
+
function extractIntervalLiteral(src) {
|
|
358
|
+
const configObj = extractConfigObject(src);
|
|
306
359
|
if (configObj === undefined)
|
|
307
360
|
return undefined;
|
|
308
|
-
const m = /scheduled\s*:\s*\{[^}]*?\binterval\s*:\s*["']([^"']+)["']/s.exec(configObj);
|
|
361
|
+
const m = /scheduled\s*:\s*\{[^}]*?\binterval\s*:\s*["']([^"']+)["']/s.exec(configObj.text);
|
|
309
362
|
return m?.[1];
|
|
310
363
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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;
|
|
325
398
|
}
|
|
326
|
-
|
|
327
|
-
|
|
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;
|
|
328
430
|
continue;
|
|
329
431
|
}
|
|
330
|
-
if (ch
|
|
432
|
+
if (OPEN_BRACKETS.includes(ch)) {
|
|
331
433
|
depth++;
|
|
434
|
+
if (depth === 1 && ch === "{")
|
|
435
|
+
expectKey = true;
|
|
436
|
+
i++;
|
|
332
437
|
continue;
|
|
333
438
|
}
|
|
334
|
-
if (ch
|
|
439
|
+
if (CLOSE_BRACKETS.includes(ch)) {
|
|
335
440
|
depth--;
|
|
441
|
+
i++;
|
|
336
442
|
continue;
|
|
337
443
|
}
|
|
338
|
-
if (depth
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const m = at.exec(objSource);
|
|
342
|
-
if (m !== null)
|
|
343
|
-
return sliceBalancedObject(objSource, i + m[0].length - 1);
|
|
444
|
+
if (depth === 1 && ch === ",")
|
|
445
|
+
expectKey = true;
|
|
446
|
+
i++;
|
|
344
447
|
}
|
|
345
|
-
return
|
|
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);
|
|
346
456
|
}
|
|
347
|
-
function
|
|
348
|
-
const
|
|
349
|
-
const
|
|
350
|
-
const CLOSE = "}])";
|
|
457
|
+
function valueTextAt(obj, valueStart) {
|
|
458
|
+
const OPEN = OPEN_BRACKETS;
|
|
459
|
+
const CLOSE = CLOSE_BRACKETS;
|
|
351
460
|
let depth = 0;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
if (OPEN.includes(ch)) {
|
|
461
|
+
let j = valueStart;
|
|
462
|
+
for (; j < obj.code.length; j++) {
|
|
463
|
+
const c = obj.code[j];
|
|
464
|
+
if (OPEN.includes(c)) {
|
|
359
465
|
depth++;
|
|
360
466
|
continue;
|
|
361
467
|
}
|
|
362
|
-
if (
|
|
468
|
+
if (depth === 0 && (c === "," || c === "}"))
|
|
469
|
+
break;
|
|
470
|
+
if (CLOSE.includes(c))
|
|
363
471
|
depth--;
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
if (depth !== 1)
|
|
367
|
-
continue;
|
|
368
|
-
at.lastIndex = i;
|
|
369
|
-
const m = at.exec(objSource);
|
|
370
|
-
if (m === null)
|
|
371
|
-
continue;
|
|
372
|
-
let j = i + m[0].length;
|
|
373
|
-
let valueDepth = 0;
|
|
374
|
-
for (; j < objSource.length; j++) {
|
|
375
|
-
const c = objSource[j];
|
|
376
|
-
if (c === '"' || c === "'") {
|
|
377
|
-
j = skipQuoted(objSource, j, c) - 1;
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
if (OPEN.includes(c)) {
|
|
381
|
-
valueDepth++;
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
if (valueDepth === 0 && (c === "," || c === "}"))
|
|
385
|
-
break;
|
|
386
|
-
if (CLOSE.includes(c))
|
|
387
|
-
valueDepth--;
|
|
388
|
-
}
|
|
389
|
-
return objSource.slice(i + m[0].length, j).trim();
|
|
390
472
|
}
|
|
391
|
-
return
|
|
473
|
+
return obj.text.slice(valueStart, j).trim();
|
|
392
474
|
}
|
|
393
475
|
function splitArrayEntries(body) {
|
|
394
476
|
const parts = [];
|
|
395
477
|
let start = 0;
|
|
396
|
-
for (let i = 0; i < body.length; i++) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
i = skipQuoted(body, i, ch) - 1;
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
if (ch === ",") {
|
|
403
|
-
parts.push(body.slice(start, i));
|
|
478
|
+
for (let i = 0; i < body.code.length; i++) {
|
|
479
|
+
if (body.code[i] === ",") {
|
|
480
|
+
parts.push(body.text.slice(start, i));
|
|
404
481
|
start = i + 1;
|
|
405
482
|
}
|
|
406
483
|
}
|
|
407
|
-
parts.push(body.slice(start));
|
|
484
|
+
parts.push(body.text.slice(start));
|
|
408
485
|
return parts.map((p) => p.trim()).filter((p) => p !== "");
|
|
409
486
|
}
|
|
410
487
|
function stringLiteralValue(raw) {
|
|
@@ -415,10 +492,18 @@ function stringLiteralValue(raw) {
|
|
|
415
492
|
return /^'((?:[^'\\]|\\.)*)'$/.exec(s)?.[1];
|
|
416
493
|
return undefined;
|
|
417
494
|
}
|
|
418
|
-
|
|
419
|
-
|
|
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);
|
|
420
505
|
if (configObj === undefined) {
|
|
421
|
-
if (!
|
|
506
|
+
if (!mentionsKey(src, "unfilteredReads"))
|
|
422
507
|
return { errors: [] };
|
|
423
508
|
return {
|
|
424
509
|
errors: [
|
|
@@ -426,21 +511,53 @@ function extractFetchUnfilteredReads(masked) {
|
|
|
426
511
|
],
|
|
427
512
|
};
|
|
428
513
|
}
|
|
429
|
-
if (!
|
|
514
|
+
if (!mentionsKey(configObj, "unfilteredReads"))
|
|
430
515
|
return { errors: [] };
|
|
431
516
|
const shape = 'config.fetch.unfilteredReads must be { dbs: ["db-name"], why: "…" } with static string literals';
|
|
432
|
-
const
|
|
433
|
-
|
|
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);
|
|
434
535
|
if (decl === undefined)
|
|
435
536
|
return { errors: [shape] };
|
|
436
537
|
const errors = [];
|
|
437
|
-
const
|
|
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);
|
|
438
554
|
let dbs;
|
|
439
555
|
if (dbsValue === undefined || !dbsValue.startsWith("[") || !dbsValue.endsWith("]")) {
|
|
440
556
|
errors.push(`${shape} — dbs must be an array literal of string literals`);
|
|
441
557
|
}
|
|
442
558
|
else {
|
|
443
|
-
const
|
|
559
|
+
const bodyStart = dbsStart + 1;
|
|
560
|
+
const parts = splitArrayEntries(sliceAligned(decl, bodyStart, bodyStart + dbsValue.length - 2));
|
|
444
561
|
const parsed = parts.map(stringLiteralValue);
|
|
445
562
|
if (parts.length === 0) {
|
|
446
563
|
errors.push(`${shape} — dbs must name at least one database`);
|
|
@@ -455,7 +572,7 @@ function extractFetchUnfilteredReads(masked) {
|
|
|
455
572
|
dbs = parsed;
|
|
456
573
|
}
|
|
457
574
|
}
|
|
458
|
-
const whyValue =
|
|
575
|
+
const whyValue = whyStart === undefined ? undefined : valueTextAt(decl, whyStart);
|
|
459
576
|
const why = whyValue === undefined ? undefined : stringLiteralValue(whyValue);
|
|
460
577
|
if (why === undefined || why.trim() === "") {
|
|
461
578
|
errors.push(`${shape} — why must be a non-empty string literal stating why this handler authorizes these reads itself`);
|
|
@@ -469,12 +586,12 @@ export function parseBackendConfig(source) {
|
|
|
469
586
|
if (source === undefined || source.trim() === "") {
|
|
470
587
|
return { handlers: [], hasConfig: false, errors };
|
|
471
588
|
}
|
|
472
|
-
const
|
|
473
|
-
const handlers = HANDLER_NAMES.filter((name) => exportsName(
|
|
474
|
-
const hasConfig = exportsName(
|
|
589
|
+
const src = viewsOf(source);
|
|
590
|
+
const handlers = HANDLER_NAMES.filter((name) => exportsName(src.code, name));
|
|
591
|
+
const hasConfig = exportsName(src.code, "config");
|
|
475
592
|
const hasScheduled = handlers.includes("scheduled");
|
|
476
593
|
let schedule;
|
|
477
|
-
const rawInterval = extractIntervalLiteral(
|
|
594
|
+
const rawInterval = extractIntervalLiteral(src);
|
|
478
595
|
if (hasScheduled) {
|
|
479
596
|
if (rawInterval === undefined) {
|
|
480
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');
|
|
@@ -497,7 +614,7 @@ export function parseBackendConfig(source) {
|
|
|
497
614
|
}
|
|
498
615
|
let fetchUnfilteredReads;
|
|
499
616
|
if (handlers.includes("fetch")) {
|
|
500
|
-
const parsedReads = extractFetchUnfilteredReads(
|
|
617
|
+
const parsedReads = extractFetchUnfilteredReads(src);
|
|
501
618
|
errors.push(...parsedReads.errors);
|
|
502
619
|
fetchUnfilteredReads = parsedReads.value;
|
|
503
620
|
}
|