@silverbulletmd/silverbullet 2.9.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -25
- package/client/asset_bundle/bundle.ts +0 -1
- package/client/config.ts +343 -0
- package/client/markdown_parser/constants.ts +3 -1
- package/client/plugos/hooks/command.ts +0 -5
- package/client/plugos/hooks/event.ts +0 -6
- package/client/plugos/hooks/mq.ts +0 -3
- package/client/plugos/hooks/slash_command.ts +1 -7
- package/client/plugos/hooks/syscall.ts +24 -14
- package/client/plugos/manifest_cache.ts +0 -32
- package/client/plugos/plug.ts +0 -3
- package/client/plugos/plug_compile.ts +3 -13
- package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
- package/client/plugos/syscalls/asset.ts +61 -23
- package/client/plugos/syscalls/clientStore.ts +32 -6
- package/client/plugos/syscalls/client_code_widget.ts +9 -4
- package/client/plugos/syscalls/code_widget.ts +26 -11
- package/client/plugos/syscalls/config.ts +146 -30
- package/client/plugos/syscalls/datastore.ts +146 -40
- package/client/plugos/syscalls/editor.ts +1518 -722
- package/client/plugos/syscalls/event.ts +44 -13
- package/client/plugos/syscalls/fetch.ts +120 -83
- package/client/plugos/syscalls/icon.ts +46 -0
- package/client/plugos/syscalls/index.ts +267 -121
- package/client/plugos/syscalls/jsonschema.ts +107 -9
- package/client/plugos/syscalls/language.ts +38 -12
- package/client/plugos/syscalls/markdown.ts +166 -50
- package/client/plugos/syscalls/mq.ts +92 -20
- package/client/plugos/syscalls/navigator.ts +143 -0
- package/client/plugos/syscalls/schema_introspection.ts +33 -0
- package/client/plugos/syscalls/search.ts +44 -0
- package/client/plugos/syscalls/service_registry.ts +74 -20
- package/client/plugos/syscalls/shell.ts +51 -21
- package/client/plugos/syscalls/space.ts +412 -113
- package/client/plugos/syscalls/sync.ts +103 -20
- package/client/plugos/syscalls/system.ts +274 -139
- package/client/plugos/system.ts +28 -10
- package/client/plugos/worker_runtime.ts +0 -2
- package/client/space_lua/aggregates.ts +0 -16
- package/client/space_lua/api_documentation.ts +107 -0
- package/client/space_lua/ast.ts +13 -5
- package/client/space_lua/ast_narrow.ts +0 -5
- package/client/space_lua/budget.ts +113 -0
- package/client/space_lua/budget_ui.ts +49 -0
- package/client/space_lua/eval.ts +125 -92
- package/client/space_lua/labels.ts +0 -4
- package/client/space_lua/numeric.ts +0 -5
- package/client/space_lua/parse.ts +243 -125
- package/client/space_lua/pretty_print.ts +128 -54
- package/client/space_lua/quarantine.ts +127 -0
- package/client/space_lua/query_collection.ts +7 -51
- package/client/space_lua/query_env.ts +0 -2
- package/client/space_lua/render_lua_markdown.ts +2 -7
- package/client/space_lua/render_widget.ts +173 -0
- package/client/space_lua/runtime.ts +133 -92
- package/client/space_lua/stdlib/crypto.ts +8 -3
- package/client/space_lua/stdlib/encoding.ts +27 -9
- package/client/space_lua/stdlib/format.ts +0 -12
- package/client/space_lua/stdlib/js.ts +136 -23
- package/client/space_lua/stdlib/load.ts +24 -16
- package/client/space_lua/stdlib/math.ts +331 -133
- package/client/space_lua/stdlib/net.ts +57 -13
- package/client/space_lua/stdlib/os.ts +111 -54
- package/client/space_lua/stdlib/pattern.ts +10 -15
- package/client/space_lua/stdlib/space_lua.ts +344 -21
- package/client/space_lua/stdlib/string.ts +282 -104
- package/client/space_lua/stdlib/string_pack.ts +58 -22
- package/client/space_lua/stdlib/table.ts +195 -45
- package/client/space_lua/stdlib.ts +354 -125
- package/client/space_lua/syscalls.ts +270 -0
- package/client/space_lua/tonumber.ts +0 -9
- package/dist/plug-compile.js +2 -3
- package/package.json +15 -5
- package/plug-api/lib/async.ts +1 -6
- package/plug-api/lib/collation.ts +22 -0
- package/plug-api/lib/crypto.ts +16 -15
- package/plug-api/lib/dates.ts +33 -0
- package/plug-api/lib/fuzzy.ts +282 -0
- package/plug-api/lib/json.ts +0 -6
- package/plug-api/lib/limited_map.ts +0 -2
- package/plug-api/lib/link_write.ts +27 -0
- package/plug-api/{ui → lib}/panel_styles.ts +4 -3
- package/plug-api/lib/ref.ts +124 -5
- package/plug-api/lib/resolve.ts +0 -1
- package/plug-api/lib/resolve_path.ts +290 -0
- package/plug-api/lib/shortcut.ts +17 -1
- package/plug-api/lib/tags.ts +0 -3
- package/plug-api/lib/transclusion.ts +28 -1
- package/plug-api/lib/tree.ts +0 -5
- package/plug-api/lib/yaml.ts +3 -42
- package/plug-api/syscall.ts +1 -5
- package/plug-api/syscalls/config.ts +7 -5
- package/plug-api/syscalls/editor.ts +70 -208
- package/plug-api/syscalls/icon.ts +18 -0
- package/plug-api/syscalls/index.ts +39 -5
- package/plug-api/syscalls/jsonschema.ts +9 -0
- package/plug-api/syscalls/lua.ts +7 -0
- package/plug-api/syscalls/search.ts +11 -0
- package/plug-api/syscalls/shell.ts +0 -1
- package/plug-api/syscalls/space.ts +122 -5
- package/plug-api/syscalls/sync.ts +23 -2
- package/plug-api/syscalls/system.ts +53 -0
- package/plug-api/syscalls.ts +2 -0
- package/plug-api/system_mock.ts +15 -2
- package/plug-api/types/client.ts +4 -3
- package/plug-api/types/datastore.ts +11 -2
- package/plug-api/types/index.ts +62 -1
- package/plug-api/types/manifest.ts +22 -4
- package/plug-api/types/profile.ts +13 -0
- package/plug-api/types/revisions.ts +67 -0
- package/plug-api/ui/cx.ts +1 -3
- package/plug-api/ui/description.ts +64 -0
- package/plug-api/ui/hover.ts +69 -0
- package/plug-api/ui/index.ts +61 -2
- package/plug-api/ui/scroll.ts +30 -0
- package/plug-api/ui/slugify.ts +44 -0
- package/plug-api/ui/tree_model.ts +263 -0
- package/plug-api/ui/tree_types.ts +66 -0
- package/plug-api/ui/use_fit_collapse.ts +67 -0
- package/plugs/builtin_plugs.ts +0 -1
- package/plugs/index/types.ts +9 -0
- package/client/plugos/syscalls/lua.ts +0 -81
- package/plug-api/lib/memory_cache.ts +0 -21
|
@@ -29,9 +29,28 @@ function resolveOptions(opts?: PrintOptions): ResolvedOptions {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const RESERVED = new Set([
|
|
32
|
-
"and",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
32
|
+
"and",
|
|
33
|
+
"break",
|
|
34
|
+
"do",
|
|
35
|
+
"else",
|
|
36
|
+
"elseif",
|
|
37
|
+
"end",
|
|
38
|
+
"false",
|
|
39
|
+
"for",
|
|
40
|
+
"function",
|
|
41
|
+
"goto",
|
|
42
|
+
"if",
|
|
43
|
+
"in",
|
|
44
|
+
"local",
|
|
45
|
+
"nil",
|
|
46
|
+
"not",
|
|
47
|
+
"or",
|
|
48
|
+
"repeat",
|
|
49
|
+
"return",
|
|
50
|
+
"then",
|
|
51
|
+
"true",
|
|
52
|
+
"until",
|
|
53
|
+
"while",
|
|
35
54
|
]);
|
|
36
55
|
|
|
37
56
|
function isLuaIdentifier(s: string): boolean {
|
|
@@ -39,16 +58,26 @@ function isLuaIdentifier(s: string): boolean {
|
|
|
39
58
|
}
|
|
40
59
|
|
|
41
60
|
const BINARY_PREC: Record<string, number> = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"<": 3,
|
|
61
|
+
or: 1,
|
|
62
|
+
and: 2,
|
|
63
|
+
"<": 3,
|
|
64
|
+
">": 3,
|
|
65
|
+
"<=": 3,
|
|
66
|
+
">=": 3,
|
|
67
|
+
"~=": 3,
|
|
68
|
+
"==": 3,
|
|
45
69
|
"|": 4,
|
|
46
70
|
"~": 5, // binary bitwise xor
|
|
47
71
|
"&": 6,
|
|
48
|
-
"<<": 7,
|
|
72
|
+
"<<": 7,
|
|
73
|
+
">>": 7,
|
|
49
74
|
"..": 8,
|
|
50
|
-
"+": 9,
|
|
51
|
-
"
|
|
75
|
+
"+": 9,
|
|
76
|
+
"-": 9,
|
|
77
|
+
"*": 10,
|
|
78
|
+
"/": 10,
|
|
79
|
+
"//": 10,
|
|
80
|
+
"%": 10,
|
|
52
81
|
"^": 12,
|
|
53
82
|
};
|
|
54
83
|
const UNARY_PREC = 11;
|
|
@@ -62,7 +91,7 @@ function exprPrec(e: LuaExpression): number {
|
|
|
62
91
|
}
|
|
63
92
|
|
|
64
93
|
class Printer {
|
|
65
|
-
constructor(private opts: ResolvedOptions) {
|
|
94
|
+
constructor(private opts: ResolvedOptions) {}
|
|
66
95
|
|
|
67
96
|
private indent(depth: number): string {
|
|
68
97
|
return " ".repeat(depth * this.opts.indentWidth);
|
|
@@ -129,22 +158,29 @@ class Printer {
|
|
|
129
158
|
case "TableConstructor":
|
|
130
159
|
return this.tableConstructor(e.fields, depth);
|
|
131
160
|
case "FunctionDefinition":
|
|
132
|
-
return
|
|
133
|
-
|
|
161
|
+
return `function${this.functionRest(
|
|
162
|
+
e.body.parameters,
|
|
163
|
+
e.body.block,
|
|
164
|
+
depth,
|
|
165
|
+
)}`;
|
|
134
166
|
case "FunctionCall":
|
|
135
167
|
return this.functionCall(e, depth);
|
|
136
168
|
case "Query": {
|
|
137
|
-
const lines = e.clauses.map(
|
|
138
|
-
this.indent(depth + 1) + this.queryClause(c, depth + 1)
|
|
169
|
+
const lines = e.clauses.map(
|
|
170
|
+
(c) => this.indent(depth + 1) + this.queryClause(c, depth + 1),
|
|
139
171
|
);
|
|
140
172
|
return `query[[\n${lines.join("\n")}\n${this.indent(depth)}]]`;
|
|
141
173
|
}
|
|
142
174
|
case "FilteredCall":
|
|
143
|
-
return `${this.expression(e.call, depth)} filter ${this.expression(
|
|
144
|
-
|
|
175
|
+
return `${this.expression(e.call, depth)} filter ${this.expression(
|
|
176
|
+
e.filter,
|
|
177
|
+
depth,
|
|
178
|
+
)}`;
|
|
145
179
|
case "AggregateCall":
|
|
146
|
-
return `${this.expression(e.call, depth)} order by ${this.orderByList(
|
|
147
|
-
|
|
180
|
+
return `${this.expression(e.call, depth)} order by ${this.orderByList(
|
|
181
|
+
e.orderBy,
|
|
182
|
+
depth,
|
|
183
|
+
)}`;
|
|
148
184
|
default:
|
|
149
185
|
throw new Error(
|
|
150
186
|
`pretty_print: unsupported expression type: ${(e as any).type}`,
|
|
@@ -193,8 +229,8 @@ class Printer {
|
|
|
193
229
|
private tableConstructor(fields: LuaTableField[], depth: number): string {
|
|
194
230
|
if (fields.length === 0) return "{}";
|
|
195
231
|
if (fields.length === 1) return `{${this.field(fields[0], depth)}}`;
|
|
196
|
-
const lines = fields.map(
|
|
197
|
-
this.indent(depth + 1) + this.field(f, depth + 1)
|
|
232
|
+
const lines = fields.map(
|
|
233
|
+
(f) => this.indent(depth + 1) + this.field(f, depth + 1),
|
|
198
234
|
);
|
|
199
235
|
const tail = this.opts.trailingComma ? "," : "";
|
|
200
236
|
return `{\n${lines.join(",\n")}${tail}\n${this.indent(depth)}}`;
|
|
@@ -224,7 +260,8 @@ class Printer {
|
|
|
224
260
|
? ` order by ${this.orderByList(e.orderBy, depth)}`
|
|
225
261
|
: "";
|
|
226
262
|
if (
|
|
227
|
-
!e.orderBy &&
|
|
263
|
+
!e.orderBy &&
|
|
264
|
+
args.length === 1 &&
|
|
228
265
|
(args[0].type === "TableConstructor" || args[0].type === "String")
|
|
229
266
|
) {
|
|
230
267
|
return `${base} ${this.expression(args[0], depth)}`;
|
|
@@ -254,8 +291,10 @@ class Printer {
|
|
|
254
291
|
case "Where":
|
|
255
292
|
return `where ${this.expression(c.expression, depth)}`;
|
|
256
293
|
case "Limit":
|
|
257
|
-
return
|
|
258
|
-
|
|
294
|
+
return (
|
|
295
|
+
`limit ${this.expression(c.limit, depth)}` +
|
|
296
|
+
(c.offset ? ` offset ${this.expression(c.offset, depth)}` : "")
|
|
297
|
+
);
|
|
259
298
|
case "Offset":
|
|
260
299
|
return `offset ${this.expression(c.offset, depth)}`;
|
|
261
300
|
case "OrderBy":
|
|
@@ -271,16 +310,32 @@ class Printer {
|
|
|
271
310
|
|
|
272
311
|
block(b: LuaBlock, depth: number): string {
|
|
273
312
|
const stmts = b.statements.filter((s) => s.type !== "Semicolon");
|
|
274
|
-
const
|
|
313
|
+
const items: (LuaStatement | NonNullable<LuaBlock["comments"]>[number])[] =
|
|
314
|
+
[...stmts, ...(b.comments ?? [])].sort(
|
|
315
|
+
(a, z) => (a.ctx.from ?? 0) - (z.ctx.from ?? 0),
|
|
316
|
+
);
|
|
317
|
+
const isDef = (s: (typeof items)[number]) =>
|
|
275
318
|
s.type === "Function" || s.type === "LocalFunction";
|
|
276
319
|
let out = "";
|
|
277
|
-
for (let i = 0; i <
|
|
320
|
+
for (let i = 0; i < items.length; i++) {
|
|
278
321
|
if (i > 0) {
|
|
279
322
|
out += "\n";
|
|
280
323
|
// blank line separating function definitions from neighbours
|
|
281
|
-
|
|
324
|
+
const previous = items[i - 1];
|
|
325
|
+
const isDocumentationPair =
|
|
326
|
+
previous.type === "Comment" &&
|
|
327
|
+
previous.text.startsWith("---") &&
|
|
328
|
+
isDef(items[i]);
|
|
329
|
+
if (!isDocumentationPair && (isDef(previous) || isDef(items[i]))) {
|
|
330
|
+
out += "\n";
|
|
331
|
+
}
|
|
282
332
|
}
|
|
283
|
-
|
|
333
|
+
const item = items[i];
|
|
334
|
+
out +=
|
|
335
|
+
item.type === "Comment"
|
|
336
|
+
? this.indent(depth) +
|
|
337
|
+
item.text.replaceAll("\n", `\n${this.indent(depth)}`)
|
|
338
|
+
: this.statement(item, depth);
|
|
284
339
|
}
|
|
285
340
|
return out;
|
|
286
341
|
}
|
|
@@ -290,11 +345,14 @@ class Printer {
|
|
|
290
345
|
switch (s.type) {
|
|
291
346
|
case "Local": {
|
|
292
347
|
const names = s.names
|
|
293
|
-
.map(
|
|
294
|
-
n
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
348
|
+
.map(
|
|
349
|
+
(n) =>
|
|
350
|
+
n.name +
|
|
351
|
+
(n.attribute
|
|
352
|
+
? ` <${n.attribute}>`
|
|
353
|
+
: n.attributes?.length
|
|
354
|
+
? ` <${n.attributes[0]}>`
|
|
355
|
+
: ""),
|
|
298
356
|
)
|
|
299
357
|
.join(", ");
|
|
300
358
|
const exprs = s.expressions?.length
|
|
@@ -303,12 +361,16 @@ class Printer {
|
|
|
303
361
|
return `${ind}local ${names}${exprs}`;
|
|
304
362
|
}
|
|
305
363
|
case "Assignment":
|
|
306
|
-
return `${ind}${s.variables
|
|
307
|
-
|
|
364
|
+
return `${ind}${s.variables
|
|
365
|
+
.map((v) => this.expression(v, depth))
|
|
366
|
+
.join(
|
|
367
|
+
", ",
|
|
368
|
+
)} = ${s.expressions.map((e) => this.expression(e, depth)).join(", ")}`;
|
|
308
369
|
case "Return":
|
|
309
370
|
return s.expressions.length
|
|
310
|
-
? `${ind}return ${s.expressions
|
|
311
|
-
|
|
371
|
+
? `${ind}return ${s.expressions
|
|
372
|
+
.map((e) => this.expression(e, depth))
|
|
373
|
+
.join(", ")}`
|
|
312
374
|
: `${ind}return`;
|
|
313
375
|
case "FunctionCallStatement":
|
|
314
376
|
return ind + this.expression(s.call, depth);
|
|
@@ -328,7 +390,8 @@ class Printer {
|
|
|
328
390
|
const kw = i === 0 ? "if" : "elseif";
|
|
329
391
|
const head = i === 0 ? `${ind}${kw} ` : `\n${ind}${kw} `;
|
|
330
392
|
const body = this.block(c.block, depth + 1);
|
|
331
|
-
out +=
|
|
393
|
+
out +=
|
|
394
|
+
`${head}${this.expression(c.condition, depth)} then` +
|
|
332
395
|
(body ? `\n${body}` : "");
|
|
333
396
|
});
|
|
334
397
|
if (s.elseBlock) {
|
|
@@ -340,34 +403,48 @@ class Printer {
|
|
|
340
403
|
}
|
|
341
404
|
case "While": {
|
|
342
405
|
const body = this.block(s.block, depth + 1);
|
|
343
|
-
return
|
|
344
|
-
|
|
406
|
+
return (
|
|
407
|
+
`${ind}while ${this.expression(s.condition, depth)} do` +
|
|
408
|
+
(body ? `\n${body}` : "") +
|
|
409
|
+
`\n${ind}end`
|
|
410
|
+
);
|
|
345
411
|
}
|
|
346
412
|
case "Repeat": {
|
|
347
413
|
const body = this.block(s.block, depth + 1);
|
|
348
|
-
return
|
|
349
|
-
|
|
414
|
+
return (
|
|
415
|
+
`${ind}repeat` +
|
|
416
|
+
(body ? `\n${body}` : "") +
|
|
417
|
+
`\n${ind}until ${this.expression(s.condition, depth)}`
|
|
418
|
+
);
|
|
350
419
|
}
|
|
351
420
|
case "For": {
|
|
352
421
|
const step = s.step ? `, ${this.expression(s.step, depth)}` : "";
|
|
353
422
|
const body = this.block(s.block, depth + 1);
|
|
354
|
-
return `${ind}for ${s.name} = ${this.expression(s.start, depth)}, ${this.expression(
|
|
355
|
-
|
|
423
|
+
return `${ind}for ${s.name} = ${this.expression(s.start, depth)}, ${this.expression(
|
|
424
|
+
s.end,
|
|
425
|
+
depth,
|
|
426
|
+
)}${step} do${body ? `\n${body}` : ""}\n${ind}end`;
|
|
356
427
|
}
|
|
357
428
|
case "ForIn": {
|
|
358
429
|
const body = this.block(s.block, depth + 1);
|
|
359
|
-
return `${ind}for ${s.names.join(", ")} in ${s.expressions
|
|
360
|
-
|
|
430
|
+
return `${ind}for ${s.names.join(", ")} in ${s.expressions
|
|
431
|
+
.map((e) => this.expression(e, depth))
|
|
432
|
+
.join(", ")} do${body ? `\n${body}` : ""}\n${ind}end`;
|
|
361
433
|
}
|
|
362
434
|
case "Function": {
|
|
363
|
-
const name =
|
|
435
|
+
const name =
|
|
436
|
+
s.name.propNames.join(".") +
|
|
364
437
|
(s.name.colonName ? `:${s.name.colonName}` : "");
|
|
365
|
-
return
|
|
366
|
-
|
|
438
|
+
return (
|
|
439
|
+
`${ind}function ${name}` +
|
|
440
|
+
this.functionRest(s.body.parameters, s.body.block, depth)
|
|
441
|
+
);
|
|
367
442
|
}
|
|
368
443
|
case "LocalFunction":
|
|
369
|
-
return
|
|
370
|
-
|
|
444
|
+
return (
|
|
445
|
+
`${ind}local function ${s.name}` +
|
|
446
|
+
this.functionRest(s.body.parameters, s.body.block, depth)
|
|
447
|
+
);
|
|
371
448
|
default:
|
|
372
449
|
throw new Error(
|
|
373
450
|
`pretty_print: unsupported statement type: ${(s as any).type}`,
|
|
@@ -383,9 +460,6 @@ export function prettyPrintExpression(
|
|
|
383
460
|
return new Printer(resolveOptions(opts)).expression(expr, 0);
|
|
384
461
|
}
|
|
385
462
|
|
|
386
|
-
export function prettyPrintBlock(
|
|
387
|
-
block: LuaBlock,
|
|
388
|
-
opts?: PrintOptions,
|
|
389
|
-
): string {
|
|
463
|
+
export function prettyPrintBlock(block: LuaBlock, opts?: PrintOptions): string {
|
|
390
464
|
return new Printer(resolveOptions(opts)).block(block, 0);
|
|
391
465
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export const QUARANTINE_KEY = "spaceLuaQuarantine";
|
|
2
|
+
|
|
3
|
+
export type QuarantineStore = {
|
|
4
|
+
getItem(k: string): string | null;
|
|
5
|
+
setItem(k: string, v: string): void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function defaultStore(): QuarantineStore {
|
|
9
|
+
return localStorage;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Dashboard can host multiple spaces on the same origin, sharing
|
|
14
|
+
* localStorage. Namespace by document.baseURI the same way boot.ts's
|
|
15
|
+
* cachedFetch does (and client.ts's deriveDbName), so one space's
|
|
16
|
+
* quarantine entries never collide with or get pruned by another's.
|
|
17
|
+
*/
|
|
18
|
+
export function quarantineStorageKey(): string {
|
|
19
|
+
const namespace = typeof document !== "undefined" ? document.baseURI : "";
|
|
20
|
+
return `silverbullet.${namespace}.${QUARANTINE_KEY}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function hashScript(source: string): string {
|
|
24
|
+
let h = 2166136261;
|
|
25
|
+
for (let i = 0; i < source.length; i++) {
|
|
26
|
+
h ^= source.charCodeAt(i);
|
|
27
|
+
h = Math.imul(h, 16777619);
|
|
28
|
+
}
|
|
29
|
+
return (h >>> 0).toString(36);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function read(store: QuarantineStore): Record<string, string> {
|
|
33
|
+
let raw: string | null;
|
|
34
|
+
try {
|
|
35
|
+
raw = store.getItem(quarantineStorageKey());
|
|
36
|
+
} catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
if (!raw) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(raw);
|
|
44
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
45
|
+
? parsed
|
|
46
|
+
: {};
|
|
47
|
+
} catch {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function write(store: QuarantineStore, v: Record<string, string>): void {
|
|
53
|
+
try {
|
|
54
|
+
store.setItem(quarantineStorageKey(), JSON.stringify(v));
|
|
55
|
+
} catch {
|
|
56
|
+
// Quarantine is a convenience: a store that refuses to persist must never
|
|
57
|
+
// prevent scripts from loading.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isQuarantined(
|
|
62
|
+
ref: string,
|
|
63
|
+
source: string,
|
|
64
|
+
store: QuarantineStore = defaultStore(),
|
|
65
|
+
): boolean {
|
|
66
|
+
const entries = read(store);
|
|
67
|
+
const stored = entries[ref];
|
|
68
|
+
if (stored === undefined) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (stored === hashScript(source)) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
delete entries[ref];
|
|
75
|
+
write(store, entries);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function quarantine(
|
|
80
|
+
ref: string,
|
|
81
|
+
source: string,
|
|
82
|
+
store: QuarantineStore = defaultStore(),
|
|
83
|
+
): void {
|
|
84
|
+
const entries = read(store);
|
|
85
|
+
entries[ref] = hashScript(source);
|
|
86
|
+
write(store, entries);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function unquarantine(
|
|
90
|
+
ref: string,
|
|
91
|
+
store: QuarantineStore = defaultStore(),
|
|
92
|
+
): void {
|
|
93
|
+
const entries = read(store);
|
|
94
|
+
delete entries[ref];
|
|
95
|
+
write(store, entries);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function listQuarantined(
|
|
99
|
+
store: QuarantineStore = defaultStore(),
|
|
100
|
+
): string[] {
|
|
101
|
+
return Object.keys(read(store));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Drops quarantine entries whose ref no longer appears among the live
|
|
106
|
+
* scripts. A ref changes whenever the script it belongs to moves (e.g. an
|
|
107
|
+
* edit shifts its page@charOffset), so the hash-mismatch self-heal in
|
|
108
|
+
* isQuarantined never revisits the old ref to prune it. Call this with the
|
|
109
|
+
* full live ref list before quarantine state is read for display.
|
|
110
|
+
*/
|
|
111
|
+
export function reconcileQuarantine(
|
|
112
|
+
liveRefs: string[],
|
|
113
|
+
store: QuarantineStore = defaultStore(),
|
|
114
|
+
): void {
|
|
115
|
+
const entries = read(store);
|
|
116
|
+
const live = new Set(liveRefs);
|
|
117
|
+
let changed = false;
|
|
118
|
+
for (const ref of Object.keys(entries)) {
|
|
119
|
+
if (!live.has(ref)) {
|
|
120
|
+
delete entries[ref];
|
|
121
|
+
changed = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (changed) {
|
|
125
|
+
write(store, entries);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -33,6 +33,7 @@ import type { DataStore } from "../data/datastore.ts";
|
|
|
33
33
|
import type { KvPrimitives } from "../data/kv_primitives.ts";
|
|
34
34
|
|
|
35
35
|
import type { QueryCollationConfig } from "../../plug-api/types/config.ts";
|
|
36
|
+
import { compareCollated } from "../../plug-api/lib/collation.ts";
|
|
36
37
|
|
|
37
38
|
import type { KvKey } from "../../plug-api/types/datastore.ts";
|
|
38
39
|
|
|
@@ -77,7 +78,6 @@ function buildGroupItemEnv(
|
|
|
77
78
|
itemEnv.setLocal("group", groupVal);
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
// Unpack named fields from multi-key LuaTable keys
|
|
81
81
|
if (keyVal instanceof LuaTable) {
|
|
82
82
|
for (const k of luaKeys(keyVal)) {
|
|
83
83
|
if (typeof k !== "string") continue;
|
|
@@ -85,12 +85,8 @@ function buildGroupItemEnv(
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
// Bind all `group by` aliases/names to their key values. For
|
|
89
|
-
// single key bind the name to the scalar `keyVal`. For multi-key
|
|
90
|
-
// bind each name to the field from the key table.
|
|
91
88
|
if (groupByNames && groupByNames.length > 0) {
|
|
92
89
|
if (!(keyVal instanceof LuaTable)) {
|
|
93
|
-
// Bind all names to scalar
|
|
94
90
|
for (const gbn of groupByNames) {
|
|
95
91
|
itemEnv.setLocal(gbn, keyVal);
|
|
96
92
|
}
|
|
@@ -109,8 +105,6 @@ function buildGroupItemEnv(
|
|
|
109
105
|
}
|
|
110
106
|
|
|
111
107
|
/**
|
|
112
|
-
* Build an environment for evaluating per-item expressions in queries.
|
|
113
|
-
*
|
|
114
108
|
* When `objectVariable` is NOT set: item fields are unpacked as locals
|
|
115
109
|
* and shadow any globals. The item is also bound to `_`.
|
|
116
110
|
*
|
|
@@ -128,7 +122,6 @@ function buildItemEnvLocal(
|
|
|
128
122
|
if (objectVariable) {
|
|
129
123
|
itemEnv.setLocal(objectVariable, item);
|
|
130
124
|
} else {
|
|
131
|
-
// Unpack item fields as locals so unqualified access works
|
|
132
125
|
itemEnv.setLocal("_", item);
|
|
133
126
|
if (item instanceof LuaTable) {
|
|
134
127
|
for (const key of luaKeys(item)) {
|
|
@@ -157,26 +150,15 @@ export type LuaGroupByEntry = {
|
|
|
157
150
|
alias?: string;
|
|
158
151
|
};
|
|
159
152
|
|
|
160
|
-
/**
|
|
161
|
-
* Represents a query for a collection
|
|
162
|
-
*/
|
|
163
153
|
export type LuaCollectionQuery = {
|
|
164
154
|
objectVariable?: string;
|
|
165
|
-
// The filter expression evaluated with Lua
|
|
166
155
|
where?: LuaExpression;
|
|
167
|
-
// The order by expression evaluated with Lua
|
|
168
156
|
orderBy?: LuaOrderBy[];
|
|
169
|
-
// The select expression evaluated with Lua
|
|
170
157
|
select?: LuaExpression;
|
|
171
|
-
// The limit of the query
|
|
172
158
|
limit?: number;
|
|
173
|
-
// The offset of the query
|
|
174
159
|
offset?: number;
|
|
175
|
-
// Whether to return only distinct values
|
|
176
160
|
distinct?: boolean;
|
|
177
|
-
// The group by entries evaluated with Lua
|
|
178
161
|
groupBy?: LuaGroupByEntry[];
|
|
179
|
-
// The having expression evaluated with Lua
|
|
180
162
|
having?: LuaExpression;
|
|
181
163
|
};
|
|
182
164
|
|
|
@@ -189,11 +171,8 @@ export interface LuaQueryCollection {
|
|
|
189
171
|
): Promise<any[]>;
|
|
190
172
|
}
|
|
191
173
|
|
|
192
|
-
/**
|
|
193
|
-
* Implements a query collection for a regular JavaScript array
|
|
194
|
-
*/
|
|
195
174
|
export class ArrayQueryCollection<T> implements LuaQueryCollection {
|
|
196
|
-
constructor(private readonly array: T[]) {
|
|
175
|
+
constructor(private readonly array: T[]) {}
|
|
197
176
|
query(
|
|
198
177
|
query: LuaCollectionQuery,
|
|
199
178
|
env: LuaEnv,
|
|
@@ -204,7 +183,6 @@ export class ArrayQueryCollection<T> implements LuaQueryCollection {
|
|
|
204
183
|
}
|
|
205
184
|
}
|
|
206
185
|
|
|
207
|
-
// Wrap any object, array, or LuaQueryCollection as a queryable collection
|
|
208
186
|
export function toCollection(obj: any): LuaQueryCollection {
|
|
209
187
|
if (
|
|
210
188
|
obj instanceof ArrayQueryCollection ||
|
|
@@ -515,7 +493,6 @@ function resolveUsing(
|
|
|
515
493
|
return new LuaFunction(using, env);
|
|
516
494
|
}
|
|
517
495
|
|
|
518
|
-
// Compare values using a custom comparator with SWO violation detection
|
|
519
496
|
async function usingCompare(
|
|
520
497
|
luaCmp: LuaValue,
|
|
521
498
|
aVal: any,
|
|
@@ -621,7 +598,6 @@ async function sortKeyCompare(
|
|
|
621
598
|
const aVal = aKeys[idx];
|
|
622
599
|
const bVal = bKeys[idx];
|
|
623
600
|
|
|
624
|
-
// Handle nulls positioning
|
|
625
601
|
const aIsNull = aVal === null || aVal === undefined || isSqlNull(aVal);
|
|
626
602
|
const bIsNull = bVal === null || bVal === undefined || isSqlNull(bVal);
|
|
627
603
|
if (aIsNull || bIsNull) {
|
|
@@ -646,25 +622,16 @@ async function sortKeyCompare(
|
|
|
646
622
|
idx,
|
|
647
623
|
);
|
|
648
624
|
if (cmp !== 0) return cmp;
|
|
649
|
-
} else
|
|
650
|
-
collation
|
|
651
|
-
typeof aVal === "string" &&
|
|
652
|
-
typeof bVal === "string"
|
|
653
|
-
) {
|
|
654
|
-
const order = collator.compare(aVal, bVal);
|
|
625
|
+
} else {
|
|
626
|
+
const order = compareCollated(aVal, bVal, collation, collator);
|
|
655
627
|
if (order !== 0) {
|
|
656
628
|
return desc ? -order : order;
|
|
657
629
|
}
|
|
658
|
-
} else if (aVal < bVal) {
|
|
659
|
-
return desc ? 1 : -1;
|
|
660
|
-
} else if (aVal > bVal) {
|
|
661
|
-
return desc ? -1 : 1;
|
|
662
630
|
}
|
|
663
631
|
}
|
|
664
632
|
return 0;
|
|
665
633
|
}
|
|
666
634
|
|
|
667
|
-
// Build a select-result table from a non-aggregate select expression
|
|
668
635
|
async function evalSelectExpression(
|
|
669
636
|
selectExpr: LuaExpression,
|
|
670
637
|
itemEnv: LuaEnv,
|
|
@@ -711,14 +678,11 @@ export async function applyQuery(
|
|
|
711
678
|
|
|
712
679
|
const grouped = !!query.groupBy;
|
|
713
680
|
|
|
714
|
-
// Collect group-by key names for unpacking into the post-group environment.
|
|
715
681
|
let groupByNames: string[] | undefined;
|
|
716
682
|
|
|
717
683
|
if (query.groupBy) {
|
|
718
|
-
// Extract expressions and names from `group by` entries
|
|
719
684
|
const groupByEntries = query.groupBy;
|
|
720
685
|
|
|
721
|
-
// Derive canonical name (explicit alias first, or from expression)
|
|
722
686
|
groupByNames = groupByEntries
|
|
723
687
|
.map((entry) => {
|
|
724
688
|
if (entry.alias) return entry.alias;
|
|
@@ -740,7 +704,6 @@ export async function applyQuery(
|
|
|
740
704
|
const entry = groupByEntries[ei];
|
|
741
705
|
const v = await evalExpression(entry.expr, itemEnv, sf);
|
|
742
706
|
keyParts.push(v);
|
|
743
|
-
// Use alias if provided, or from expression
|
|
744
707
|
const name =
|
|
745
708
|
entry.alias ??
|
|
746
709
|
(entry.expr.type === "Variable" ? entry.expr.name : undefined) ??
|
|
@@ -769,11 +732,9 @@ export async function applyQuery(
|
|
|
769
732
|
keyVal = keyParts[0];
|
|
770
733
|
} else {
|
|
771
734
|
const kt = new LuaTable();
|
|
772
|
-
// Always populate array indices from keyParts
|
|
773
735
|
for (let i = 0; i < keyParts.length; i++) {
|
|
774
736
|
kt.rawSetArrayIndex(i + 1, keyParts[i]);
|
|
775
737
|
}
|
|
776
|
-
// Additionally set named fields for Variable/PropertyAccess exprs
|
|
777
738
|
for (const name in keyRecord) {
|
|
778
739
|
void kt.rawSet(name, keyRecord[name]);
|
|
779
740
|
}
|
|
@@ -842,12 +803,11 @@ export async function applyQuery(
|
|
|
842
803
|
|
|
843
804
|
const mkEnv = grouped
|
|
844
805
|
? (ov: string | undefined, item: any, e: LuaEnv, s: LuaStackFrame) =>
|
|
845
|
-
|
|
806
|
+
buildGroupItemEnv(ov, groupByNames, item, e, s)
|
|
846
807
|
: buildItemEnvLocal;
|
|
847
808
|
|
|
848
809
|
let selectResults: any[] | undefined;
|
|
849
810
|
|
|
850
|
-
// Pre-compute select for grouped + ordered queries
|
|
851
811
|
if (grouped && query.select && query.orderBy) {
|
|
852
812
|
const selectExpr = query.select;
|
|
853
813
|
selectResults = [];
|
|
@@ -879,7 +839,6 @@ export async function applyQuery(
|
|
|
879
839
|
violated.push(false);
|
|
880
840
|
}
|
|
881
841
|
|
|
882
|
-
// Decorate: pre-compute all sort keys once (Schwartzian transform)
|
|
883
842
|
const sortKeys = await precomputeSortKeys(
|
|
884
843
|
results,
|
|
885
844
|
query.orderBy,
|
|
@@ -892,10 +851,8 @@ export async function applyQuery(
|
|
|
892
851
|
config,
|
|
893
852
|
);
|
|
894
853
|
|
|
895
|
-
// Tag each result with its original index for stable sorting
|
|
896
854
|
const tagged = results.map((val, idx) => ({ val, idx }));
|
|
897
855
|
|
|
898
|
-
// Sort: compare cached keys only, no Lua eval in comparator
|
|
899
856
|
await asyncMergeSort(tagged, (a, b) =>
|
|
900
857
|
sortKeyCompare(
|
|
901
858
|
a,
|
|
@@ -911,7 +868,6 @@ export async function applyQuery(
|
|
|
911
868
|
),
|
|
912
869
|
);
|
|
913
870
|
|
|
914
|
-
// Check for SWO violations in comparators
|
|
915
871
|
for (let i = 0; i < violated.length; i++) {
|
|
916
872
|
if (violated[i]) {
|
|
917
873
|
throw new LuaRuntimeError(
|
|
@@ -1002,7 +958,7 @@ export async function queryLua<T = any>(
|
|
|
1002
958
|
for await (let { key, value } of kv.query({ prefix })) {
|
|
1003
959
|
if (enricher) {
|
|
1004
960
|
value = enricher(key, value);
|
|
1005
|
-
// Enrichers may return undefined to filter a row out
|
|
961
|
+
// Enrichers may return undefined to filter a row out
|
|
1006
962
|
if (value === undefined) continue;
|
|
1007
963
|
}
|
|
1008
964
|
results.push(value);
|
|
@@ -1056,7 +1012,7 @@ export class DataStoreQueryCollection implements LuaQueryCollection {
|
|
|
1056
1012
|
constructor(
|
|
1057
1013
|
private readonly dataStore: DataStore,
|
|
1058
1014
|
readonly prefix: string[],
|
|
1059
|
-
) {
|
|
1015
|
+
) {}
|
|
1060
1016
|
query(
|
|
1061
1017
|
query: LuaCollectionQuery,
|
|
1062
1018
|
env: LuaEnv,
|
|
@@ -13,11 +13,9 @@ export function buildItemEnv(
|
|
|
13
13
|
): LuaEnv {
|
|
14
14
|
const itemEnv = new LuaEnv(env);
|
|
15
15
|
if (!objectVariable) {
|
|
16
|
-
// Inject all item keys as variables
|
|
17
16
|
for (const key of luaKeys(item)) {
|
|
18
17
|
itemEnv.setLocal(key, luaGet(item, key, sf.astCtx ?? null, sf));
|
|
19
18
|
}
|
|
20
|
-
// As well as _
|
|
21
19
|
itemEnv.setLocal("_", item);
|
|
22
20
|
} else {
|
|
23
21
|
itemEnv.setLocal(objectVariable, item);
|