@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
@@ -1,23 +1,27 @@
1
1
  import type { SysCallMapping } from "../system.ts";
2
2
  import { parse } from "../../markdown_parser/parse_tree.ts";
3
3
  import type { ParseTree } from "@silverbulletmd/silverbullet/lib/tree";
4
- import { builtinLanguages, languageFor } from "../../languages.ts";
4
+ import {
5
+ allLanguageNames,
6
+ languageFor,
7
+ loadLanguageFor,
8
+ } from "../../languages.ts";
5
9
 
6
10
  export function languageSyscalls(): SysCallMapping {
7
11
  return {
8
- "language.parseLanguage": (
12
+ "language.parseLanguage": async (
9
13
  _ctx,
10
14
  language: string,
11
15
  code: string,
12
- ): ParseTree => {
13
- const lang = languageFor(language);
16
+ ): Promise<ParseTree> => {
17
+ const lang = languageFor(language) ?? await loadLanguageFor(language);
14
18
  if (!lang) {
15
19
  throw new Error(`Unknown language ${language}`);
16
20
  }
17
21
  return parse(lang, code);
18
22
  },
19
23
  "language.listLanguages": (): string[] => {
20
- return Object.keys(builtinLanguages);
24
+ return allLanguageNames;
21
25
  },
22
26
  };
23
27
  }
@@ -4,7 +4,7 @@ import {
4
4
  type ParseTree,
5
5
  renderToText,
6
6
  } from "@silverbulletmd/silverbullet/lib/tree";
7
- import { extendedMarkdownLanguage } from "../../markdown_parser/parser.ts";
7
+ import { buildExtendedMarkdownLanguage } from "../../markdown_parser/parser.ts";
8
8
  import {
9
9
  expandMarkdown,
10
10
  type MarkdownExpandOptions,
@@ -18,11 +18,12 @@ import {
18
18
  jsonToMDTable,
19
19
  refCellTransformer,
20
20
  } from "../../markdown_renderer/result_render.ts";
21
+ import * as TagConstants from "../../../plugs/index/constants.ts";
21
22
 
22
23
  export function markdownSyscalls(client: Client): SysCallMapping {
23
24
  return {
24
25
  "markdown.parseMarkdown": (_ctx, text: string): ParseTree => {
25
- return parse(extendedMarkdownLanguage, text);
26
+ return parse(markdownLanguageWithUserExtensions(client), text);
26
27
  },
27
28
  "markdown.renderParseTree": (_ctx, tree: ParseTree): string => {
28
29
  return renderToText(tree);
@@ -34,7 +35,10 @@ export function markdownSyscalls(client: Client): SysCallMapping {
34
35
  ): Promise<ParseTree | string> => {
35
36
  const outputString = typeof treeOrText === "string";
36
37
  if (typeof treeOrText === "string") {
37
- treeOrText = parse(extendedMarkdownLanguage, treeOrText);
38
+ treeOrText = parse(
39
+ markdownLanguageWithUserExtensions(client),
40
+ treeOrText,
41
+ );
38
42
  }
39
43
  const result = await expandMarkdownWithClient(
40
44
  client,
@@ -52,23 +56,38 @@ export function markdownSyscalls(client: Client): SysCallMapping {
52
56
  text: string,
53
57
  options: MarkdownRenderOptions = {},
54
58
  ) => {
55
- let mdTree = parse(extendedMarkdownLanguage, text);
59
+ let mdTree = parse(markdownLanguageWithUserExtensions(client), text);
56
60
  if (options.expand) {
57
61
  mdTree = await expandMarkdownWithClient(client, mdTree);
58
62
  }
63
+ if (!options.resolveTagHref) {
64
+ options.resolveTagHref = (tagName: string) => {
65
+ return client.config.get<string | null>(
66
+ ["tags", tagName, "tagPage"],
67
+ null,
68
+ ) ?? TagConstants.tagPrefix + tagName;
69
+ };
70
+ }
59
71
  return renderMarkdownToHtml(mdTree, options);
60
72
  },
61
73
  "markdown.objectsToTable": (
62
74
  _ctx,
63
75
  data: any[],
64
- options: { renderCell?: (val: any, key: string) => Promise<any> | any } =
65
- {},
76
+ options: {
77
+ renderCell?: (val: any, key: string) => Promise<any> | any;
78
+ } = {},
66
79
  ) => {
67
80
  return jsonToMDTable(data, options.renderCell || refCellTransformer);
68
81
  },
69
82
  };
70
83
  }
71
84
 
85
+ function markdownLanguageWithUserExtensions(client: Client) {
86
+ return buildExtendedMarkdownLanguage(
87
+ client.config.get("syntaxExtensions", {}),
88
+ );
89
+ }
90
+
72
91
  function expandMarkdownWithClient(
73
92
  client: Client,
74
93
  tree: ParseTree,
@@ -79,6 +98,9 @@ function expandMarkdownWithClient(
79
98
  client.currentName(),
80
99
  tree,
81
100
  client.clientSystem.spaceLuaEnv,
82
- options,
101
+ {
102
+ ...options,
103
+ syntaxExtensions: client.config.get("syntaxExtensions", {}),
104
+ },
83
105
  );
84
106
  }
@@ -6,23 +6,15 @@ export type EventSubscription = MQListenerSpec & {
6
6
  run: (...args: any[]) => Promise<any>;
7
7
  };
8
8
 
9
- export function mqSyscalls(
10
- mq: DataStoreMQ,
11
- ): SysCallMapping {
9
+ export function mqSyscalls(mq: DataStoreMQ): SysCallMapping {
12
10
  return {
13
11
  /**
14
12
  * Define a Lua event listener
15
13
  */
16
- "mq.subscribe": (
17
- _ctx,
18
- def: MQListenerSpec,
19
- ) => {
20
- def.autoAck = def.autoAck != false;
14
+ "mq.subscribe": (_ctx, def: MQListenerSpec) => {
15
+ def.autoAck = def.autoAck !== false;
21
16
  // console.log("Registering Lua event listener: ", def.name);
22
- client.config.insert([
23
- "mqSubscriptions",
24
- def.queue,
25
- ], def);
17
+ client.config.insert(["mqSubscriptions", def.queue], def);
26
18
  },
27
19
  "mq.send": (_ctx, queue: string, body: any) => {
28
20
  return mq.send(queue, body);
@@ -12,10 +12,7 @@ export function serviceRegistrySyscalls(
12
12
  /**
13
13
  * Define a Lua event listener
14
14
  */
15
- "service.define": (
16
- _ctx,
17
- def: ServiceSpec,
18
- ) => {
15
+ "service.define": (_ctx, def: ServiceSpec) => {
19
16
  return serviceRegistry.define(def);
20
17
  },
21
18
  "service.discover": (
@@ -2,9 +2,7 @@ import type { SysCallMapping } from "../system.ts";
2
2
  import type { Client } from "../../client.ts";
3
3
  import { fsEndpoint } from "../../spaces/constants.ts";
4
4
 
5
- export function shellSyscalls(
6
- client: Client,
7
- ): SysCallMapping {
5
+ export function shellSyscalls(client: Client): SysCallMapping {
8
6
  return {
9
7
  "shell.run": async (
10
8
  _ctx,
@@ -34,6 +32,5 @@ export function shellSyscalls(
34
32
 
35
33
  function buildShellUrl(client: Client) {
36
34
  // Strip off the /.fs and replace with /.shell
37
- return client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) +
38
- "/.shell";
35
+ return `${client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length)}/.shell`;
39
36
  }
@@ -32,7 +32,7 @@ export function spaceReadSyscalls(client: Client): SysCallMapping {
32
32
  return (await client.space.readRef(ref)).text;
33
33
  },
34
34
  "space.pageExists": (_ctx, name: string): boolean => {
35
- return client.clientSystem.allKnownFiles.has(name + ".md");
35
+ return client.clientSystem.allKnownFiles.has(`${name}.md`);
36
36
  },
37
37
  "space.getPageMeta": (_ctx, name: string): Promise<PageMeta> => {
38
38
  return client.space.getPageMeta(name);
@@ -1,77 +1,86 @@
1
1
  import type { SysCallMapping } from "../system.ts";
2
2
  import type { Client } from "../../client.ts";
3
3
 
4
- // TODO: Reimplement this
5
4
  export function syncSyscalls(client: Client): SysCallMapping {
6
- return {
7
- "sync.hasInitialSyncCompleted": (): boolean => {
8
- return client.fullSyncCompleted;
9
- },
10
- "sync.performFileSync": (_ctx, path: string): Promise<void> => {
11
- client.postServiceWorkerMessage({ type: "perform-file-sync", path });
12
- return waitForServiceWorkerActivation(path);
13
- },
14
- "sync.performSpaceSync": (): Promise<number> => {
15
- client.postServiceWorkerMessage({ type: "perform-space-sync" });
16
- return waitForServiceWorkerActivation();
17
- },
18
- };
19
- }
5
+ const syncTimeoutMs = 30000;
20
6
 
21
- function waitForServiceWorkerActivation(path?: string): Promise<any> {
22
- return new Promise<any>((resolve, reject) => {
23
- client.eventHook.addLocalListener(
24
- "service-worker:file-sync-complete",
25
- eventHandler,
26
- );
27
- client.eventHook.addLocalListener(
28
- "service-worker:space-sync-complete",
29
- eventHandler,
30
- );
31
- client.eventHook.addLocalListener(
32
- "service-worker:sync-error",
33
- errorHandler,
34
- );
35
- function eventHandler(data: any) {
36
- // If data.path is set, we are notified about a specific file sync -> all good, even for an individual file sync
37
- // If data.path is not set, we are notified about a full space sync
38
- // If we were waiting for a specific path, ignore other paths
39
- if (data.path && path && data.path !== path) {
40
- // Event for other file sync
41
- return;
7
+ function waitForServiceWorkerActivation(path?: string): Promise<any> {
8
+ return new Promise<any>((resolve, reject) => {
9
+ const timeout = setTimeout(() => {
10
+ cleanup();
11
+ reject(new Error(`Sync timeout after ${syncTimeoutMs / 1000}s`));
12
+ }, syncTimeoutMs);
13
+
14
+ function cleanup() {
15
+ clearTimeout(timeout);
16
+ client.eventHook.removeLocalListener(
17
+ "service-worker:file-sync-complete",
18
+ eventHandler,
19
+ );
20
+ client.eventHook.removeLocalListener(
21
+ "service-worker:space-sync-complete",
22
+ eventHandler,
23
+ );
24
+ client.eventHook.removeLocalListener(
25
+ "service-worker:sync-error",
26
+ errorHandler,
27
+ );
42
28
  }
43
- // If we were waiting for a specific path, ignore other paths
44
- resolve(data);
45
29
 
46
- // Unsubscribe from all these events
47
- client.eventHook.removeLocalListener(
48
- "service-worker:file-sync-complete",
49
- eventHandler,
50
- );
51
- client.eventHook.removeLocalListener(
52
- "service-worker:space-sync-complete",
53
- eventHandler,
54
- );
55
- client.eventHook.removeLocalListener(
56
- "service-worker:sync-error",
57
- errorHandler,
58
- );
59
- }
60
- function errorHandler(e: any) {
61
- reject(e);
62
- // Unsubscribe from all these events
63
- client.eventHook.removeLocalListener(
30
+ client.eventHook.addLocalListener(
64
31
  "service-worker:file-sync-complete",
65
32
  eventHandler,
66
33
  );
67
- client.eventHook.removeLocalListener(
34
+ client.eventHook.addLocalListener(
68
35
  "service-worker:space-sync-complete",
69
36
  eventHandler,
70
37
  );
71
- client.eventHook.removeLocalListener(
38
+ client.eventHook.addLocalListener(
72
39
  "service-worker:sync-error",
73
40
  errorHandler,
74
41
  );
75
- }
76
- });
42
+
43
+ function eventHandler(data: any) {
44
+ if (data.path && path && data.path !== path) {
45
+ return;
46
+ }
47
+ cleanup();
48
+ resolve(data);
49
+ }
50
+
51
+ function errorHandler(e: any) {
52
+ // Only reject if the error is for our specific path, or if no path was specified
53
+ if (e.path && path && e.path !== path) {
54
+ return;
55
+ }
56
+ cleanup();
57
+ reject(e);
58
+ }
59
+ });
60
+ }
61
+
62
+ return {
63
+ "sync.hasInitialSyncCompleted": (): boolean => {
64
+ return client.fullSyncCompleted;
65
+ },
66
+ "sync.performFileSync": async (_ctx, path: string): Promise<void> => {
67
+ await client.postServiceWorkerMessage({
68
+ type: "perform-file-sync",
69
+ path,
70
+ });
71
+ // postServiceWorkerMessage returns silently if no SW, so only wait if SW is active
72
+ const registration = await navigator.serviceWorker.getRegistration();
73
+ if (registration?.active) {
74
+ return waitForServiceWorkerActivation(path);
75
+ }
76
+ },
77
+ "sync.performSpaceSync": async (): Promise<number> => {
78
+ await client.postServiceWorkerMessage({ type: "perform-space-sync" });
79
+ const registration = await navigator.serviceWorker.getRegistration();
80
+ if (registration?.active) {
81
+ return waitForServiceWorkerActivation();
82
+ }
83
+ return 0;
84
+ },
85
+ };
77
86
  }
@@ -66,9 +66,8 @@ export function systemSyscalls(
66
66
  },
67
67
  "system.listSyscalls": (): SyscallMeta[] => {
68
68
  const syscalls: SyscallMeta[] = [];
69
- for (
70
- const [name, info] of client.clientSystem.system.registeredSyscalls
71
- ) {
69
+ for (const [name, info] of client.clientSystem.system
70
+ .registeredSyscalls) {
72
71
  syscalls.push({
73
72
  name,
74
73
  requiredPermissions: info.requiredPermissions,
@@ -24,9 +24,7 @@ export type SyscallContext = {
24
24
  plug?: string;
25
25
  };
26
26
 
27
- type SyscallSignature = (
28
- ...args: any[]
29
- ) => Promise<any> | any;
27
+ type SyscallSignature = (...args: any[]) => Promise<any> | any;
30
28
 
31
29
  type Syscall = {
32
30
  requiredPermissions: string[];
@@ -112,11 +110,7 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
112
110
  return this.syscall({}, name, args);
113
111
  }
114
112
 
115
- syscall(
116
- ctx: SyscallContext,
117
- name: string,
118
- args: any[],
119
- ): Promise<any> {
113
+ syscall(ctx: SyscallContext, name: string, args: any[]): Promise<any> {
120
114
  const syscall = this.registeredSyscalls.get(name);
121
115
  if (!syscall) {
122
116
  throw Error(`Unregistered syscall ${name}`);
@@ -188,13 +182,13 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
188
182
  if (!plug) {
189
183
  return;
190
184
  }
191
- plug.stop();
192
- this.emit("plugUnloaded", name);
185
+ void plug.stop();
186
+ void this.emit("plugUnloaded", name);
193
187
  this.plugs.delete(name);
194
188
  }
195
189
 
196
- unloadAll(): Promise<void[]> {
197
- return Promise.all(
190
+ async unloadAll(): Promise<void> {
191
+ await Promise.all(
198
192
  Array.from(this.plugs.keys()).map(this.unload.bind(this)),
199
193
  );
200
194
  }
@@ -15,35 +15,15 @@ let workerPostMessage = (_msg: ControllerMessage): void => {
15
15
  throw new Error("Not initialized yet");
16
16
  };
17
17
 
18
- // Are we running in a (web) worker?
19
-
20
- // Determines if we're running in a web worker environment (Deno or browser)
21
- // - in a browser's main threads, typeof window is "object"
18
+ // Determines if we're running in a web worker environment
19
+ // - in a browser's main thread, typeof window is "object"
22
20
  // - in a browser's worker threads, typeof window === "undefined"
23
- // - in Deno's main thread typeof window === "object"
24
- // - in Deno's workers typeof window === "undefined
25
21
  // - in Cloudflare workers typeof window === "undefined", but typeof globalThis.WebSocketPair is defined
26
- const runningAsWebWorker = typeof window === "undefined" &&
27
- // @ts-ignore: globalThis
22
+ const runningAsWebWorker =
23
+ typeof window === "undefined" &&
24
+ // @ts-expect-error: globalThis
28
25
  typeof globalThis.WebSocketPair === "undefined";
29
26
 
30
- // @ts-ignore: Check if Deno is defined
31
- if (typeof Deno === "undefined") {
32
- // @ts-ignore: Deno hack
33
- self.Deno = {
34
- args: [],
35
- // @ts-ignore: Deno hack
36
- build: {
37
- arch: "x86_64",
38
- },
39
- env: {
40
- // @ts-ignore: Deno hack
41
- get() {
42
- },
43
- },
44
- };
45
- }
46
-
47
27
  const pendingRequests = new Map<
48
28
  number,
49
29
  {
@@ -157,20 +137,19 @@ export async function sandboxFetch(
157
137
  return syscall("sandboxFetch.fetch", reqInfo, options);
158
138
  }
159
139
 
160
- // @ts-ignore: monkey patching fetch
161
140
  globalThis.nativeFetch = globalThis.fetch;
162
141
 
163
142
  // Monkey patch fetch()
164
143
  export function monkeyPatchFetch() {
165
- // @ts-ignore: monkey patching fetch
166
- globalThis.fetch = async function (
144
+ // @ts-expect-error: monkey patching fetch
145
+ globalThis.fetch = async (
167
146
  reqInfo: RequestInfo,
168
147
  init?: RequestInit,
169
- ): Promise<Response> {
170
- const encodedBody = init && init.body
148
+ ): Promise<Response> => {
149
+ const encodedBody = init?.body
171
150
  ? base64Encode(
172
- new Uint8Array(await (new Response(init.body)).arrayBuffer()),
173
- )
151
+ new Uint8Array(await new Response(init.body).arrayBuffer()),
152
+ )
174
153
  : undefined;
175
154
  const r = await sandboxFetch(
176
155
  reqInfo,
@@ -180,7 +159,7 @@ export function monkeyPatchFetch() {
180
159
  base64Body: encodedBody,
181
160
  },
182
161
  );
183
- // Casting the response to "any" for now, since of weird Deno typing
162
+ // Casting to any due to TypeScript fetch type limitations
184
163
  return new Response(
185
164
  (r.base64Body ? base64Decode(r.base64Body) : null) as any,
186
165
  {