@silverbulletmd/silverbullet 2.4.2 → 2.6.1

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.
Files changed (97) hide show
  1. package/README.md +19 -4
  2. package/client/asset_bundle/bundle.ts +3 -9
  3. package/client/data/datastore.ts +4 -5
  4. package/client/markdown_parser/constants.ts +5 -4
  5. package/client/plugos/hooks/code_widget.ts +3 -8
  6. package/client/plugos/hooks/command.ts +8 -8
  7. package/client/plugos/hooks/document_editor.ts +10 -15
  8. package/client/plugos/hooks/event.ts +33 -36
  9. package/client/plugos/hooks/mq.ts +17 -17
  10. package/client/plugos/hooks/plug_namespace.ts +3 -8
  11. package/client/plugos/hooks/slash_command.ts +13 -28
  12. package/client/plugos/hooks/syscall.ts +3 -3
  13. package/client/plugos/manifest_cache.ts +22 -15
  14. package/client/plugos/plug.ts +2 -6
  15. package/client/plugos/plug_compile.ts +79 -78
  16. package/client/plugos/protocol.ts +28 -28
  17. package/client/plugos/proxy_fetch.ts +7 -6
  18. package/client/plugos/sandboxes/web_worker_sandbox.ts +1 -1
  19. package/client/plugos/sandboxes/worker_sandbox.ts +18 -18
  20. package/client/plugos/syscalls/asset.ts +1 -3
  21. package/client/plugos/syscalls/code_widget.ts +1 -3
  22. package/client/plugos/syscalls/config.ts +1 -5
  23. package/client/plugos/syscalls/datastore.ts +1 -1
  24. package/client/plugos/syscalls/editor.ts +72 -69
  25. package/client/plugos/syscalls/event.ts +9 -12
  26. package/client/plugos/syscalls/fetch.ts +31 -23
  27. package/client/plugos/syscalls/index.ts +10 -1
  28. package/client/plugos/syscalls/jsonschema.ts +72 -32
  29. package/client/plugos/syscalls/language.ts +9 -5
  30. package/client/plugos/syscalls/markdown.ts +29 -7
  31. package/client/plugos/syscalls/mq.ts +4 -12
  32. package/client/plugos/syscalls/service_registry.ts +1 -4
  33. package/client/plugos/syscalls/shell.ts +2 -5
  34. package/client/plugos/syscalls/space.ts +1 -1
  35. package/client/plugos/syscalls/sync.ts +69 -60
  36. package/client/plugos/syscalls/system.ts +2 -3
  37. package/client/plugos/system.ts +6 -12
  38. package/client/plugos/worker_runtime.ts +12 -33
  39. package/client/space_lua/aggregates.ts +782 -0
  40. package/client/space_lua/ast.ts +42 -8
  41. package/client/space_lua/ast_narrow.ts +4 -2
  42. package/client/space_lua/eval.ts +886 -575
  43. package/client/space_lua/labels.ts +7 -12
  44. package/client/space_lua/liq_null.ts +6 -0
  45. package/client/space_lua/numeric.ts +5 -8
  46. package/client/space_lua/parse.ts +346 -120
  47. package/client/space_lua/query_collection.ts +926 -82
  48. package/client/space_lua/query_env.ts +26 -0
  49. package/client/space_lua/render_lua_markdown.ts +369 -0
  50. package/client/space_lua/rp.ts +5 -4
  51. package/client/space_lua/runtime.ts +288 -155
  52. package/client/space_lua/stdlib/format.ts +53 -39
  53. package/client/space_lua/stdlib/js.ts +3 -7
  54. package/client/space_lua/stdlib/load.ts +1 -3
  55. package/client/space_lua/stdlib/math.ts +84 -58
  56. package/client/space_lua/stdlib/net.ts +27 -17
  57. package/client/space_lua/stdlib/os.ts +81 -85
  58. package/client/space_lua/stdlib/pattern.ts +695 -0
  59. package/client/space_lua/stdlib/prng.ts +148 -0
  60. package/client/space_lua/stdlib/space_lua.ts +17 -23
  61. package/client/space_lua/stdlib/string.ts +102 -190
  62. package/client/space_lua/stdlib/string_pack.ts +490 -0
  63. package/client/space_lua/stdlib/table.ts +76 -16
  64. package/client/space_lua/stdlib.ts +53 -39
  65. package/client/space_lua/tonumber.ts +82 -42
  66. package/client/space_lua/util.ts +53 -15
  67. package/dist/plug-compile.js +55 -98
  68. package/package.json +27 -20
  69. package/plug-api/constants.ts +0 -32
  70. package/plug-api/lib/async.ts +20 -7
  71. package/plug-api/lib/crypto.ts +16 -17
  72. package/plug-api/lib/dates.ts +15 -7
  73. package/plug-api/lib/json.ts +11 -5
  74. package/plug-api/lib/limited_map.ts +1 -1
  75. package/plug-api/lib/native_fetch.ts +2 -0
  76. package/plug-api/lib/ref.ts +23 -23
  77. package/plug-api/lib/resolve.ts +7 -11
  78. package/plug-api/lib/tags.ts +13 -4
  79. package/plug-api/lib/transclusion.ts +10 -21
  80. package/plug-api/lib/tree.ts +165 -45
  81. package/plug-api/lib/yaml.ts +35 -25
  82. package/plug-api/syscalls/asset.ts +1 -1
  83. package/plug-api/syscalls/config.ts +1 -4
  84. package/plug-api/syscalls/editor.ts +15 -15
  85. package/plug-api/syscalls/jsonschema.ts +1 -3
  86. package/plug-api/syscalls/lua.ts +3 -9
  87. package/plug-api/syscalls/mq.ts +1 -4
  88. package/plug-api/syscalls/shell.ts +4 -1
  89. package/plug-api/syscalls/space.ts +3 -10
  90. package/plug-api/syscalls/system.ts +1 -4
  91. package/plug-api/syscalls/yaml.ts +2 -6
  92. package/plug-api/system_mock.ts +0 -1
  93. package/plug-api/types/client.ts +16 -1
  94. package/plug-api/types/event.ts +6 -4
  95. package/plug-api/types/manifest.ts +8 -9
  96. package/plugs/builtin_plugs.ts +2 -2
  97. package/client/plugos/sandboxes/deno_worker_sandbox.ts +0 -6
@@ -0,0 +1,26 @@
1
+ import { LuaEnv, luaGet, luaKeys, type LuaStackFrame } from "./runtime.ts";
2
+
3
+ /**
4
+ * Build an environment for evaluating per-item expressions in queries.
5
+ * Extracted to its own module to avoid circular imports between
6
+ * query_collection.ts and aggregates.ts.
7
+ */
8
+ export function buildItemEnv(
9
+ objectVariable: string | undefined,
10
+ item: any,
11
+ env: LuaEnv,
12
+ sf: LuaStackFrame,
13
+ ): LuaEnv {
14
+ const itemEnv = new LuaEnv(env);
15
+ if (!objectVariable) {
16
+ // Inject all item keys as variables
17
+ for (const key of luaKeys(item)) {
18
+ itemEnv.setLocal(key, luaGet(item, key, sf.astCtx ?? null, sf));
19
+ }
20
+ // As well as _
21
+ itemEnv.setLocal("_", item);
22
+ } else {
23
+ itemEnv.setLocal(objectVariable, item);
24
+ }
25
+ return itemEnv;
26
+ }
@@ -0,0 +1,369 @@
1
+ import {
2
+ defaultTransformer,
3
+ escapeRegularPipes,
4
+ jsonToMDTable,
5
+ } from "../markdown_renderer/result_render.ts";
6
+ import { isSqlNull } from "../space_lua/liq_null.ts";
7
+ import { isTaggedFloat } from "../space_lua/numeric.ts";
8
+ import { LuaTable, luaFormatNumber } from "../space_lua/runtime.ts";
9
+
10
+ /**
11
+ * Applies some heuristics to figure out if a string should be rendered
12
+ * as a markdown block or inline markdown.
13
+ */
14
+ export function isBlockMarkdown(s: string) {
15
+ if (s.includes("\n")) {
16
+ return true;
17
+ }
18
+ return !!s.match(/[-*]\s+/);
19
+ }
20
+
21
+ function isEmpty(v: any): boolean {
22
+ return v === undefined || v === null || isSqlNull(v);
23
+ }
24
+
25
+ function isPlainObject(v: any): v is Record<string, any> {
26
+ return (
27
+ typeof v === "object" &&
28
+ v !== null &&
29
+ !Array.isArray(v) &&
30
+ v.constructor === Object
31
+ );
32
+ }
33
+
34
+ function formatScalar(v: any): string {
35
+ if (isEmpty(v)) return "";
36
+ if (isTaggedFloat(v)) return luaFormatNumber(v.value, "float");
37
+ if (typeof v === "number") return luaFormatNumber(v);
38
+ if (typeof v === "boolean") return v ? "true" : "false";
39
+ return `${v}`.trim();
40
+ }
41
+
42
+ const emptyTable = "<table data-table-empty></table>";
43
+
44
+ /**
45
+ * Build an HTML table from string headers and a row accessor.
46
+ * Each row is rendered by calling `getCell(rowIndex, header)`.
47
+ */
48
+ function buildHtmlTable(
49
+ headers: string[],
50
+ rowCount: number,
51
+ getCell: (rowIndex: number, header: string) => any,
52
+ ): string {
53
+ if (headers.length === 0) return emptyTable;
54
+ const parts: string[] = ["<table><thead><tr>"];
55
+ for (const h of headers) parts.push(`<th>${h}</th>`);
56
+ parts.push("</tr></thead><tbody>");
57
+ for (let i = 0; i < rowCount; i++) {
58
+ parts.push("<tr>");
59
+ for (const h of headers) parts.push(renderTd(getCell(i, h)));
60
+ parts.push("</tr>");
61
+ }
62
+ parts.push("</tbody></table>");
63
+ return parts.join("");
64
+ }
65
+
66
+ /** Collect the union of string keys across items. */
67
+ function collectHeaders<T>(
68
+ items: T[],
69
+ getKeys: (item: T) => Iterable<string>,
70
+ ): string[] {
71
+ const set = new Set<string>();
72
+ for (const item of items) {
73
+ for (const k of getKeys(item)) set.add(k);
74
+ }
75
+ return [...set];
76
+ }
77
+
78
+ /**
79
+ * Classification of a JS/Lua value that drives both the HTML and the
80
+ * clean-markdown renderers. Keeping this in one place ensures the two
81
+ * output paths stay in lock-step when new cases are added.
82
+ */
83
+ export type Classified =
84
+ | { kind: "nil"; dataType: "nil" }
85
+ | {
86
+ kind: "scalar";
87
+ text: string;
88
+ dataType: "string" | "number" | "boolean";
89
+ }
90
+ | { kind: "emptyTable"; dataType: "table" }
91
+ | {
92
+ kind: "record";
93
+ headers: string[];
94
+ getCell: (header: string) => any;
95
+ dataType: "table";
96
+ }
97
+ | {
98
+ kind: "recordArray";
99
+ headers: string[];
100
+ rowCount: number;
101
+ getCell: (rowIndex: number, header: string) => any;
102
+ // LuaTable record-arrays are historically tagged as "list", while
103
+ // JS record-arrays are tagged as "table". Preserved for compatibility.
104
+ dataType: "table" | "list";
105
+ }
106
+ | { kind: "scalarArray"; items: any[]; dataType: "list" };
107
+
108
+ export function classifyResult(result: any): Classified {
109
+ if (isEmpty(result)) return { kind: "nil", dataType: "nil" };
110
+ if (typeof result === "string") {
111
+ return { kind: "scalar", text: result, dataType: "string" };
112
+ }
113
+ if (isTaggedFloat(result)) {
114
+ return {
115
+ kind: "scalar",
116
+ text: luaFormatNumber(result.value, "float"),
117
+ dataType: "number",
118
+ };
119
+ }
120
+ if (typeof result === "number") {
121
+ return {
122
+ kind: "scalar",
123
+ text: luaFormatNumber(result),
124
+ dataType: "number",
125
+ };
126
+ }
127
+ if (typeof result === "boolean") {
128
+ return {
129
+ kind: "scalar",
130
+ text: result ? "true" : "false",
131
+ dataType: "boolean",
132
+ };
133
+ }
134
+
135
+ if (result instanceof LuaTable) {
136
+ if (result.empty()) return { kind: "emptyTable", dataType: "table" };
137
+ const keys = result.keys();
138
+ const arrayLen = result.length;
139
+ const hasStrKeys = keys.some((k) => typeof k === "string");
140
+
141
+ // Pure array
142
+ if (arrayLen > 0 && !hasStrKeys) {
143
+ const elements: any[] = [];
144
+ for (let i = 1; i <= arrayLen; i++) elements.push(result.rawGet(i));
145
+ if (elements.every((el) => el instanceof LuaTable)) {
146
+ const tables = elements as LuaTable[];
147
+ const headers = collectHeaders(tables, (t) => t.keys().map(String));
148
+ if (headers.length === 0) {
149
+ return { kind: "emptyTable", dataType: "table" };
150
+ }
151
+ return {
152
+ kind: "recordArray",
153
+ headers,
154
+ rowCount: tables.length,
155
+ getCell: (i, h) => {
156
+ const key = /^\d+$/.test(h) ? Number(h) : h;
157
+ return tables[i].rawGet(key);
158
+ },
159
+ dataType: "list",
160
+ };
161
+ }
162
+ return { kind: "scalarArray", items: elements, dataType: "list" };
163
+ }
164
+
165
+ // Has string keys (record or mixed) — single-row table
166
+ const headers = keys.map(String);
167
+ return {
168
+ kind: "record",
169
+ headers,
170
+ getCell: (h) => {
171
+ const key = keys[headers.indexOf(h)];
172
+ return result.rawGet(key);
173
+ },
174
+ dataType: "table",
175
+ };
176
+ }
177
+
178
+ if (Array.isArray(result)) {
179
+ if (result.length === 0) return { kind: "emptyTable", dataType: "table" };
180
+ if (result.every(isPlainObject)) {
181
+ const headers = collectHeaders(result, Object.keys);
182
+ return {
183
+ kind: "recordArray",
184
+ headers,
185
+ rowCount: result.length,
186
+ getCell: (i, h) => result[i][h],
187
+ dataType: "table",
188
+ };
189
+ }
190
+ return { kind: "scalarArray", items: result, dataType: "list" };
191
+ }
192
+
193
+ if (isPlainObject(result)) {
194
+ if (Object.keys(result).length === 0) {
195
+ return { kind: "emptyTable", dataType: "table" };
196
+ }
197
+ const headers = Object.keys(result);
198
+ return {
199
+ kind: "record",
200
+ headers,
201
+ getCell: (h) => result[h],
202
+ dataType: "table",
203
+ };
204
+ }
205
+
206
+ return { kind: "scalar", text: `${result}`, dataType: "string" };
207
+ }
208
+
209
+ /**
210
+ * Render any Lua/JS value to a markdown string (with embedded HTML
211
+ * for structured data like tables).
212
+ *
213
+ * Scalar lists are rendered as plain lines. Tables use HTML for
214
+ * full nesting support.
215
+ *
216
+ * The returned markdown can be fed through the markdown parser and
217
+ * renderer to produce final HTML, getting wiki links, hashtags,
218
+ * formatting etc. for free.
219
+ */
220
+ export function renderResultToMarkdown(
221
+ result: any,
222
+ classified: Classified = classifyResult(result),
223
+ ): {
224
+ markdown: string;
225
+ dataType: string;
226
+ } {
227
+ switch (classified.kind) {
228
+ case "nil":
229
+ return { markdown: "", dataType: "nil" };
230
+ case "scalar":
231
+ return { markdown: classified.text, dataType: classified.dataType };
232
+ case "emptyTable":
233
+ return { markdown: emptyTable, dataType: "table" };
234
+ case "record":
235
+ return {
236
+ markdown: buildHtmlTable(classified.headers, 1, (_i, h) =>
237
+ classified.getCell(h),
238
+ ),
239
+ dataType: "table",
240
+ };
241
+ case "recordArray":
242
+ return {
243
+ markdown: buildHtmlTable(
244
+ classified.headers,
245
+ classified.rowCount,
246
+ classified.getCell,
247
+ ),
248
+ dataType: classified.dataType,
249
+ };
250
+ case "scalarArray":
251
+ return {
252
+ markdown: renderArrayToMarkdown(classified.items),
253
+ dataType: "list",
254
+ };
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Render any Lua/JS value as "clean" GFM-style markdown suitable for
260
+ * the Copy button. Tables render as pipe tables, scalar arrays as
261
+ * newline-joined lines, scalars as their plain text.
262
+ *
263
+ * Nested structures inside a table cell degrade to their Lua literal
264
+ * form (via `LuaTable.toStringAsync()` in `defaultTransformer`), since
265
+ * GFM table cells cannot contain block-level content.
266
+ */
267
+ /**
268
+ * Cell transformer for the clean-markdown (copy) path. Renders:
269
+ * - `ref` columns as wiki links,
270
+ * - scalar arrays as `<br/>`-joined lines (mirrors the HTML display
271
+ * path, and relies on the markdown renderer now handling self-closing
272
+ * `<br/>` inside GFM table cells),
273
+ * - everything else via `defaultTransformer` (which Lua-encodes nested
274
+ * tables and escapes pipes for scalars).
275
+ */
276
+ function cleanCellTransformer(v: any, k: string): Promise<string> {
277
+ if (k === "ref") return Promise.resolve(`[[${v}]]`);
278
+ const c = classifyResult(v);
279
+ if (c.kind === "scalarArray") {
280
+ return Promise.resolve(
281
+ c.items
282
+ .map(formatScalar)
283
+ .map((s) => escapeRegularPipes(s.replaceAll("\n", " ")))
284
+ .join("<br/>"),
285
+ );
286
+ }
287
+ return defaultTransformer(v, k);
288
+ }
289
+
290
+ export async function renderResultToCleanMarkdown(
291
+ result: any,
292
+ classified: Classified = classifyResult(result),
293
+ ): Promise<string> {
294
+ switch (classified.kind) {
295
+ case "nil":
296
+ return "";
297
+ case "scalar":
298
+ return classified.text;
299
+ case "emptyTable":
300
+ return "*(empty table)*";
301
+ case "record": {
302
+ const row: Record<string, any> = {};
303
+ for (const h of classified.headers) row[h] = classified.getCell(h);
304
+ return jsonToMDTable([row], cleanCellTransformer);
305
+ }
306
+ case "recordArray": {
307
+ const rows: Record<string, any>[] = [];
308
+ for (let i = 0; i < classified.rowCount; i++) {
309
+ const row: Record<string, any> = {};
310
+ for (const h of classified.headers) row[h] = classified.getCell(i, h);
311
+ rows.push(row);
312
+ }
313
+ return jsonToMDTable(rows, cleanCellTransformer);
314
+ }
315
+ case "scalarArray":
316
+ return classified.items.map(formatScalar).join("\n");
317
+ }
318
+ }
319
+
320
+ function luaTypeName(v: any): string | undefined {
321
+ if (isEmpty(v)) return undefined;
322
+ if (typeof v === "number" || isTaggedFloat(v)) return "number";
323
+ if (typeof v === "string") return "string";
324
+ if (typeof v === "boolean") return "boolean";
325
+ if (v instanceof LuaTable) {
326
+ return v.keys().some((k) => typeof k === "string") ? "table" : "array";
327
+ }
328
+ if (Array.isArray(v)) return "array";
329
+ if (isPlainObject(v)) return "table";
330
+ return "string";
331
+ }
332
+
333
+ function renderTd(v: any): string {
334
+ if (isEmpty(v)) return "<td data-table-cell-empty></td>";
335
+ const type = luaTypeName(v);
336
+ const attr = type ? ` data-table-cell-type="${type}"` : "";
337
+ return `<td${attr}>${renderCellContent(v)}</td>`;
338
+ }
339
+
340
+ function renderArrayToMarkdown(items: any[]): string {
341
+ return items.map((item) => renderCellContent(item)).join("\n");
342
+ }
343
+
344
+ function renderArrayToHtmlLines(items: any[]): string {
345
+ return items.map((item) => renderCellContent(item)).join("<br/>");
346
+ }
347
+
348
+ /**
349
+ * Render a value as cell content. Used inside HTML contexts (table
350
+ * cells, nested structures) where markdown block syntax won't be
351
+ * parsed, so scalar arrays are joined with `<br/>` rather than newlines.
352
+ */
353
+ function renderCellContent(v: any): string {
354
+ const c = classifyResult(v);
355
+ switch (c.kind) {
356
+ case "nil":
357
+ return "";
358
+ case "scalar":
359
+ return c.text;
360
+ case "emptyTable":
361
+ return emptyTable;
362
+ case "record":
363
+ return buildHtmlTable(c.headers, 1, (_i, h) => c.getCell(h));
364
+ case "recordArray":
365
+ return buildHtmlTable(c.headers, c.rowCount, c.getCell);
366
+ case "scalarArray":
367
+ return renderArrayToHtmlLines(c.items);
368
+ }
369
+ }
@@ -2,11 +2,12 @@
2
2
 
3
3
  export type RP<T> = T | Promise<T>;
4
4
 
5
- // Returns true when v is a Promise or a has a then function.
5
+ // Returns true when v is a Promise or has a then function.
6
+ // Optimized: skip the property access for primitives (number, string, boolean, null, undefined).
6
7
  export function isPromise<T>(v: RP<T>): v is Promise<T> {
7
- return v !== null &&
8
- (typeof v === "object" || typeof v === "function") &&
9
- typeof (v as any).then === "function";
8
+ return (
9
+ typeof v === "object" && v !== null && typeof (v as any).then === "function"
10
+ );
10
11
  }
11
12
 
12
13
  export function rpThen<A, B>(v: RP<A>, f: (a: A) => RP<B>): RP<B> {