@silverbulletmd/silverbullet 2.4.1 → 2.5.3

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 (57) hide show
  1. package/README.md +20 -4
  2. package/client/markdown_parser/constants.ts +2 -2
  3. package/client/plugos/hooks/code_widget.ts +0 -3
  4. package/client/plugos/hooks/document_editor.ts +0 -3
  5. package/client/plugos/hooks/event.ts +1 -1
  6. package/client/plugos/hooks/mq.ts +1 -1
  7. package/client/plugos/hooks/plug_namespace.ts +0 -3
  8. package/client/plugos/hooks/slash_command.ts +2 -2
  9. package/client/plugos/plug.ts +0 -1
  10. package/client/plugos/plug_compile.ts +28 -29
  11. package/client/plugos/proxy_fetch.ts +1 -1
  12. package/client/plugos/sandboxes/web_worker_sandbox.ts +1 -1
  13. package/client/plugos/sandboxes/worker_sandbox.ts +2 -3
  14. package/client/plugos/syscalls/editor.ts +12 -12
  15. package/client/plugos/syscalls/fetch.ts +1 -1
  16. package/client/plugos/syscalls/jsonschema.ts +1 -1
  17. package/client/plugos/syscalls/mq.ts +1 -1
  18. package/client/plugos/syscalls/space.ts +1 -1
  19. package/client/plugos/system.ts +2 -2
  20. package/client/plugos/worker_runtime.ts +8 -29
  21. package/client/space_lua/aggregates.ts +209 -0
  22. package/client/space_lua/ast.ts +24 -2
  23. package/client/space_lua/eval.ts +58 -53
  24. package/client/space_lua/labels.ts +1 -1
  25. package/client/space_lua/parse.ts +117 -12
  26. package/client/space_lua/query_collection.ts +850 -70
  27. package/client/space_lua/query_env.ts +26 -0
  28. package/client/space_lua/runtime.ts +47 -17
  29. package/client/space_lua/stdlib/format.ts +19 -19
  30. package/client/space_lua/stdlib/math.ts +73 -48
  31. package/client/space_lua/stdlib/net.ts +2 -2
  32. package/client/space_lua/stdlib/os.ts +5 -0
  33. package/client/space_lua/stdlib/pattern.ts +702 -0
  34. package/client/space_lua/stdlib/prng.ts +145 -0
  35. package/client/space_lua/stdlib/space_lua.ts +3 -8
  36. package/client/space_lua/stdlib/string.ts +103 -181
  37. package/client/space_lua/stdlib/string_pack.ts +486 -0
  38. package/client/space_lua/stdlib/table.ts +73 -9
  39. package/client/space_lua/stdlib.ts +38 -14
  40. package/client/space_lua/tonumber.ts +3 -2
  41. package/client/space_lua/util.ts +43 -9
  42. package/dist/plug-compile.js +23 -69
  43. package/dist/worker_runtime_bundle.js +233 -0
  44. package/package.json +16 -11
  45. package/plug-api/constants.ts +0 -32
  46. package/plug-api/lib/async.ts +2 -2
  47. package/plug-api/lib/crypto.ts +11 -11
  48. package/plug-api/lib/json.ts +1 -1
  49. package/plug-api/lib/limited_map.ts +1 -1
  50. package/plug-api/lib/native_fetch.ts +2 -0
  51. package/plug-api/lib/ref.ts +5 -5
  52. package/plug-api/lib/transclusion.ts +5 -5
  53. package/plug-api/lib/tree.ts +50 -2
  54. package/plug-api/lib/yaml.ts +10 -10
  55. package/plug-api/syscalls/editor.ts +1 -1
  56. package/plug-api/system_mock.ts +0 -1
  57. package/client/plugos/sandboxes/deno_worker_sandbox.ts +0 -6
@@ -8,35 +8,3 @@ export const wrongSpacePathError: Error = new Error(
8
8
  );
9
9
  export const pingTimeout: number = 2000;
10
10
  export const pingInterval: number = 5000;
11
-
12
- /**
13
- * HTTP status codes that should be treated as "offline" conditions.
14
- *
15
- * This is particularly useful for cases where a proxy (such as Cloudflare or other reverse proxies)
16
- * indicates that the backend server is down, but there is still network connectivity between
17
- * the user and the proxy. In these scenarios, we want to allow the user to continue working
18
- * with their cached data rather than showing an error, even though technically there is network
19
- * connectivity to the proxy.
20
- *
21
- * This enables SilverBullet to work in a true "offline-first" manner, falling back to cached
22
- * content when the backend is unavailable through no fault of the user's network connection.
23
- *
24
- * All 5xx server errors are included to prevent the client from caching error HTML pages
25
- * (e.g., Nginx 500 error pages) which would prevent the client from booting in offline mode.
26
- */
27
- export const offlineStatusCodes = {
28
- 500: "Internal Server Error", // Server encountered an unexpected condition
29
- 501: "Not Implemented", // Server does not support the functionality required
30
- 502: "Bad Gateway", // Proxy server received invalid response from upstream server
31
- 503: "Service Unavailable", // Server is temporarily unable to handle the request
32
- 504: "Gateway Timeout", // Proxy server did not receive a timely response from upstream server
33
- 505: "HTTP Version Not Supported", // Server does not support the HTTP version
34
- 506: "Variant Also Negotiates", // Server has an internal configuration error
35
- 507: "Insufficient Storage", // Server is unable to store the representation
36
- 508: "Loop Detected", // Server detected an infinite loop while processing
37
- 509: "Bandwidth Limit Exceeded", // Server bandwidth limit has been exceeded
38
- 510: "Not Extended", // Further extensions to the request are required
39
- 511: "Network Authentication Required", // Client needs to authenticate to gain network access
40
-
41
- 530: "Unable to resolve origin hostname", // Served when cloudflared is down on the host
42
- } as const;
@@ -1,6 +1,6 @@
1
1
  export function throttle(func: () => void, limit: number): () => void {
2
2
  let timer: any = null;
3
- return function () {
3
+ return () => {
4
4
  if (!timer) {
5
5
  timer = setTimeout(() => {
6
6
  func();
@@ -15,7 +15,7 @@ export function throttleImmediately(
15
15
  limit: number,
16
16
  ): () => void {
17
17
  let timer: any = null;
18
- return function () {
18
+ return () => {
19
19
  if (!timer) {
20
20
  func();
21
21
  timer = setTimeout(() => {
@@ -85,7 +85,7 @@ export async function encryptStringDeterministic(
85
85
  key: CryptoKey,
86
86
  clearText: string,
87
87
  ): Promise<string> {
88
- const encrypted = await crypto.subtle.encrypt(
88
+ const encrypted = await globalThis.crypto.subtle.encrypt(
89
89
  { name: "AES-CTR", counter: fixedCounter, length: fixedCounter.length * 8 },
90
90
  key,
91
91
  new TextEncoder().encode(clearText),
@@ -97,7 +97,7 @@ export async function decryptStringDeterministic(
97
97
  key: CryptoKey,
98
98
  cipherText: string,
99
99
  ): Promise<string> {
100
- const decrypted = await crypto.subtle.decrypt(
100
+ const decrypted = await globalThis.crypto.subtle.decrypt(
101
101
  { name: "AES-CTR", counter: fixedCounter, length: fixedCounter.length * 8 },
102
102
  key,
103
103
  base64Decode(cipherText) as BufferSource,
@@ -110,8 +110,8 @@ export async function encryptAesGcm(
110
110
  key: CryptoKey,
111
111
  data: Uint8Array,
112
112
  ): Promise<Uint8Array> {
113
- const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV recommended for GCM
114
- const encryptedBuffer = await crypto.subtle.encrypt(
113
+ const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV recommended for GCM
114
+ const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
115
115
  { name: "AES-GCM", iv },
116
116
  key,
117
117
  data as BufferSource,
@@ -132,7 +132,7 @@ export async function decryptAesGcm(
132
132
  ): Promise<Uint8Array> {
133
133
  const iv = encryptedData.slice(0, 12); // extract IV (first 12 bytes)
134
134
  const ciphertext = encryptedData.slice(12);
135
- const decryptedBuffer = await crypto.subtle.decrypt(
135
+ const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
136
136
  { name: "AES-GCM", iv },
137
137
  key,
138
138
  ciphertext,
@@ -148,7 +148,7 @@ export async function deriveCTRKeyFromPassword(
148
148
  const passwordBytes = new TextEncoder().encode(password);
149
149
 
150
150
  // Import password as a CryptoKey
151
- const baseKey = await crypto.subtle.importKey(
151
+ const baseKey = await globalThis.crypto.subtle.importKey(
152
152
  "raw",
153
153
  passwordBytes,
154
154
  { name: "PBKDF2" },
@@ -156,7 +156,7 @@ export async function deriveCTRKeyFromPassword(
156
156
  ["deriveBits", "deriveKey"],
157
157
  );
158
158
 
159
- return crypto.subtle.deriveKey(
159
+ return globalThis.crypto.subtle.deriveKey(
160
160
  {
161
161
  name: "PBKDF2",
162
162
  salt: salt as BufferSource,
@@ -174,7 +174,7 @@ export async function deriveCTRKeyFromPassword(
174
174
  }
175
175
 
176
176
  export function importKey(b64EncodedKey: string): Promise<CryptoKey> {
177
- return crypto.subtle.importKey(
177
+ return globalThis.crypto.subtle.importKey(
178
178
  "raw",
179
179
  base64Decode(b64EncodedKey) as BufferSource,
180
180
  { name: "AES-CTR" },
@@ -184,15 +184,15 @@ export function importKey(b64EncodedKey: string): Promise<CryptoKey> {
184
184
  }
185
185
 
186
186
  export async function exportKey(ctrKey: CryptoKey): Promise<string> {
187
- const key = await crypto.subtle.exportKey("raw", ctrKey);
187
+ const key = await globalThis.crypto.subtle.exportKey("raw", ctrKey);
188
188
  return base64Encode(new Uint8Array(key));
189
189
  }
190
190
 
191
191
  export async function deriveGCMKeyFromCTR(
192
192
  ctrKey: CryptoKey,
193
193
  ): Promise<CryptoKey> {
194
- const rawKey = await crypto.subtle.exportKey("raw", ctrKey);
195
- return crypto.subtle.importKey(
194
+ const rawKey = await globalThis.crypto.subtle.exportKey("raw", ctrKey);
195
+ return globalThis.crypto.subtle.importKey(
196
196
  "raw",
197
197
  rawKey,
198
198
  { name: "AES-GCM" },
@@ -125,7 +125,7 @@ export function deepClone<T>(obj: T, ignoreKeys: string[] = []): T {
125
125
  for (const key in obj) {
126
126
  if (ignoreKeys.includes(key)) {
127
127
  objClone[key] = obj[key];
128
- } else if (Object.prototype.hasOwnProperty.call(obj, key)) {
128
+ } else if (Object.hasOwn(obj, key)) {
129
129
  objClone[key] = deepClone(obj[key], ignoreKeys);
130
130
  }
131
131
  }
@@ -1,7 +1,7 @@
1
1
  type LimitedMapRecord<V> = {
2
2
  value: V;
3
3
  la: number;
4
- expTimer?: number;
4
+ expTimer?: ReturnType<typeof setTimeout>;
5
5
  };
6
6
 
7
7
  export class LimitedMap<V> {
@@ -1,3 +1,5 @@
1
+ export {};
2
+
1
3
  declare global {
2
4
  function nativeFetch(
3
5
  input: RequestInfo | URL,
@@ -87,7 +87,7 @@ export function isValidName(name: string): boolean {
87
87
  export function isValidPath(path: string): path is Path {
88
88
  const ref = parseToRef(path);
89
89
 
90
- return !!ref && ref.path === path;
90
+ return !!ref && ref.path === path && path !== "";
91
91
  }
92
92
 
93
93
  /**
@@ -95,7 +95,7 @@ export function isValidPath(path: string): path is Path {
95
95
  * TO THE INNER WORKINGS OF SILVERBULLET AND CHANGES COULD INTRODUCE MAJOR BUGS
96
96
  */
97
97
  const refRegex =
98
- /^(?<meta>\^)?(?<path>(?!.*\.[a-zA-Z0-9]+\.md$)(?!\/?(\.|\^))(?!.*(?:\/|^)\.{1,2}(?:\/|$)|.*\/{2})(?!.*(?:\]\]|\[\[))[^@#\|<>]*)(@(?<pos>\d+)|@[Ll](?<line>\d+)(?:[Cc](?<col>\d+))?|#\s*(?<header>.*))?$/;
98
+ /^(?<meta>\^)?(?<path>(?!.*\.[a-zA-Z0-9]+\.md$)(?!\/?(\.|\^))(?!.*(?:\/|^)\.{1,2}(?:\/|$)|.*\/{2})(?!.*(?:\]\]|\[\[))[^@#|<>]*)(@(?<pos>\d+)|@[Ll](?<line>\d+)(?:[Cc](?<col>\d+))?|#\s*(?<header>.*))?$/;
99
99
 
100
100
  /**
101
101
  * Parses a reference string into a ref object.
@@ -118,13 +118,13 @@ export function parseToRef(stringRef: string): Ref | null {
118
118
  if (groups.pos !== undefined) {
119
119
  ref.details = {
120
120
  type: "position",
121
- pos: parseInt(groups.pos),
121
+ pos: parseInt(groups.pos, 10),
122
122
  };
123
123
  } else if (groups.line !== undefined) {
124
124
  ref.details = {
125
125
  type: "linecolumn",
126
- line: parseInt(groups.line),
127
- column: groups.col !== undefined ? parseInt(groups.col) : 1,
126
+ line: parseInt(groups.line, 10),
127
+ column: groups.col !== undefined ? parseInt(groups.col, 10) : 1,
128
128
  };
129
129
  } else if (groups.header !== undefined) {
130
130
  ref.details = {
@@ -45,19 +45,19 @@ export function parseDimensionFromAlias(
45
45
  const [width, height] = dimPart.split("x");
46
46
  dim = {};
47
47
  if (width) {
48
- dim.width = parseInt(width);
48
+ dim.width = parseInt(width, 10);
49
49
  }
50
50
  if (height) {
51
- dim.height = parseInt(height);
51
+ dim.height = parseInt(height, 10);
52
52
  }
53
53
  } else if (/^[x\d]/.test(text)) {
54
54
  const [width, height] = text.split("x");
55
55
  dim = {};
56
56
  if (width) {
57
- dim.width = parseInt(width);
57
+ dim.width = parseInt(width, 10);
58
58
  }
59
59
  if (height) {
60
- dim.height = parseInt(height);
60
+ dim.height = parseInt(height, 10);
61
61
  }
62
62
  alias = "";
63
63
  } else {
@@ -74,7 +74,7 @@ export function parseDimensionFromAlias(
74
74
  export function parseTransclusion(
75
75
  text: string,
76
76
  ): Transclusion | null {
77
- let url, alias = undefined;
77
+ let url, alias ;
78
78
  let linktype: LinkType = "markdownlink";
79
79
  // TODO: Take in the tree and use tree nodes to get url and alias (Applies to all regex uses)
80
80
  mdLinkRegex.lastIndex = 0;
@@ -91,7 +91,7 @@ export function replaceNodesMatching(
91
91
  tree: ParseTree,
92
92
  substituteFn: (tree: ParseTree) => ParseTree | null | undefined,
93
93
  ) {
94
- if (tree && tree.children) {
94
+ if (tree?.children) {
95
95
  const children = tree.children.slice();
96
96
  for (const child of children) {
97
97
  const subst = substituteFn(child);
@@ -182,7 +182,8 @@ export function nodeAtPos(tree: ParseTree, pos: number): ParseTree | null {
182
182
  if (n && n.text !== undefined) {
183
183
  // Got a text node, let's return its parent
184
184
  return tree;
185
- } else if (n) {
185
+ }
186
+ if (n) {
186
187
  // Got it
187
188
  return n;
188
189
  }
@@ -190,6 +191,53 @@ export function nodeAtPos(tree: ParseTree, pos: number): ParseTree | null {
190
191
  return null;
191
192
  }
192
193
 
194
+ // Ensure a TableRow/TableHeader has a TableCell between every pair of
195
+ // TableDelimiters, and optionally pad to match columnCount.
196
+ // headerHasLeadingDelim indicates whether the header starts with a delimiter.
197
+ export function normalizeTableRow(
198
+ row: ParseTree,
199
+ columnCount?: number,
200
+ headerHasLeadingDelim?: boolean,
201
+ ): void {
202
+ const children = row.children;
203
+ if (!children) return;
204
+ const normalized: ParseTree[] = [];
205
+ let lookingForCell = false;
206
+ for (const child of children) {
207
+ if (child.type === "TableDelimiter" && lookingForCell) {
208
+ normalized.push({ type: "TableCell", children: [] });
209
+ }
210
+ if (child.type === "TableDelimiter") {
211
+ lookingForCell = true;
212
+ }
213
+ if (child.type === "TableCell") {
214
+ lookingForCell = false;
215
+ }
216
+ normalized.push(child);
217
+ }
218
+ row.children = normalized;
219
+
220
+ // Fix leading-pipe mismatch: row has leading delimiter but header doesn't
221
+ if (headerHasLeadingDelim === false) {
222
+ if (row.children.length > 0 && row.children[0].type === "TableDelimiter") {
223
+ // Insert empty cell after the leading delimiter
224
+ row.children.splice(1, 0, { type: "TableCell", children: [] });
225
+ }
226
+ }
227
+
228
+ // Pad trailing empty cells to match header column count
229
+ if (columnCount !== undefined) {
230
+ let cellCount = 0;
231
+ for (const child of row.children) {
232
+ if (child.type === "TableCell") cellCount++;
233
+ }
234
+ while (cellCount < columnCount) {
235
+ row.children.push({ type: "TableCell", children: [] });
236
+ cellCount++;
237
+ }
238
+ }
239
+ }
240
+
193
241
  // Turn ParseTree back into text
194
242
  export function renderToText(tree?: ParseTree): string {
195
243
  if (!tree) {
@@ -44,7 +44,7 @@ function serializeToYamlValue(
44
44
  return "[]"; // Use flow style for empty arrays for simplicity
45
45
  }
46
46
  // Determine indentation for list items (base + 2 spaces)
47
- const itemIndentation = baseIndentation + " ";
47
+ const itemIndentation = `${baseIndentation} `;
48
48
  // Format each item recursively, preceded by '- ' marker
49
49
  return "\n" +
50
50
  value.map((item) =>
@@ -55,7 +55,7 @@ function serializeToYamlValue(
55
55
  } else if (typeof value === "object" && value !== null) {
56
56
  // Basic object serialization (not requested, but good to consider)
57
57
  // This is highly simplified and doesn't handle nesting well without more context
58
- const itemIndentation = baseIndentation + " ";
58
+ const itemIndentation = `${baseIndentation} `;
59
59
  const entries = Object.entries(value);
60
60
  if (entries.length === 0) return "{}"; // Flow style empty objects
61
61
  return "\n" +
@@ -89,7 +89,7 @@ export function applyPatches(
89
89
  for (let i = 0; i < lines.length; i++) {
90
90
  const line = lines[i];
91
91
  const trimmedLine = line.trim();
92
- if (trimmedLine.startsWith(key + ":")) {
92
+ if (trimmedLine.startsWith(`${key}:`)) {
93
93
  keyLineIndex = i;
94
94
  startDeleteIndex = i;
95
95
  endDeleteIndex = i;
@@ -203,7 +203,7 @@ export function applyPatches(
203
203
  for (let i = 0; i < lines.length; i++) {
204
204
  const line = lines[i];
205
205
  const trimmedLine = line.trim();
206
- if (trimmedLine.startsWith(key + ":")) {
206
+ if (trimmedLine.startsWith(`${key}:`)) {
207
207
  keyLineIndex = i;
208
208
  // Extract inline comment if present
209
209
  const commentMatch = line.match(/#.*$/);
@@ -214,7 +214,7 @@ export function applyPatches(
214
214
  for (let j = i - 1; j >= 0; j--) {
215
215
  const prevLine = lines[j].trim();
216
216
  if (prevLine.startsWith("#")) {
217
- commentBlock = lines[j] + "\n" + commentBlock;
217
+ commentBlock = `${lines[j]}\n${commentBlock}`;
218
218
  } else if (prevLine !== "") {
219
219
  break;
220
220
  }
@@ -223,7 +223,7 @@ export function applyPatches(
223
223
  for (let j = i + 1; j < lines.length; j++) {
224
224
  const nextLine = lines[j].trim();
225
225
  if (nextLine.startsWith("#")) {
226
- trailingComments += lines[j] + "\n";
226
+ trailingComments += `${lines[j]}\n`;
227
227
  } else if (nextLine !== "") {
228
228
  break;
229
229
  }
@@ -243,7 +243,7 @@ export function applyPatches(
243
243
  } else {
244
244
  // For scalars, format as key: value
245
245
  replacementLine = `${key}: ${serializedNewValue}${
246
- inlineComment ? " " + inlineComment : ""
246
+ inlineComment ? ` ${inlineComment}` : ""
247
247
  }`;
248
248
  }
249
249
 
@@ -267,14 +267,14 @@ export function applyPatches(
267
267
  ];
268
268
 
269
269
  // Join lines and ensure proper newlines
270
- currentYaml = newContent.join("\n").replace(/\n*$/, "\n") + "\n";
270
+ currentYaml = `${newContent.join("\n").replace(/\n*$/, "\n")}\n`;
271
271
  } else {
272
272
  // Key not found: Add the new key-value pair to the end
273
273
  const newLineBlock = replacementLine;
274
274
  if (currentYaml.trim() === "") {
275
- currentYaml = newLineBlock + "\n";
275
+ currentYaml = `${newLineBlock}\n`;
276
276
  } else {
277
- currentYaml = currentYaml.replace(/\n*$/, "\n") + newLineBlock + "\n";
277
+ currentYaml = `${currentYaml.replace(/\n*$/, "\n") + newLineBlock}\n`;
278
278
  }
279
279
  }
280
280
  }
@@ -249,7 +249,7 @@ export function filterBox(
249
249
  export function showPanel(
250
250
  id: "lhs" | "rhs" | "bhs" | "modal",
251
251
  mode: number,
252
- html: string,
252
+ html: HTMLElement | HTMLElement[] | string,
253
253
  script = "",
254
254
  ): Promise<void> {
255
255
  return syscall("editor.showPanel", id, mode, html, script);
@@ -71,7 +71,6 @@ export function createMockSystem() {
71
71
  systemSyscalls(clientMock, false),
72
72
  );
73
73
 
74
- // @ts-ignore: global
75
74
  globalThis.syscall = (name: string, ...args: any): Promise<any> => {
76
75
  return system.localSyscall(name, args);
77
76
  };
@@ -1,6 +0,0 @@
1
- import { WorkerSandbox } from "./worker_sandbox.ts";
2
- import type { SandboxFactory } from "./sandbox.ts";
3
-
4
- export function createSandbox<HookT>(workerUrl: URL): SandboxFactory<HookT> {
5
- return (plug) => new WorkerSandbox(plug, workerUrl);
6
- }