@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.
Files changed (123) hide show
  1. package/README.md +38 -25
  2. package/client/asset_bundle/bundle.ts +0 -1
  3. package/client/config.ts +343 -0
  4. package/client/markdown_parser/constants.ts +3 -1
  5. package/client/plugos/hooks/command.ts +0 -5
  6. package/client/plugos/hooks/event.ts +0 -6
  7. package/client/plugos/hooks/mq.ts +0 -3
  8. package/client/plugos/hooks/slash_command.ts +1 -7
  9. package/client/plugos/hooks/syscall.ts +24 -14
  10. package/client/plugos/manifest_cache.ts +0 -32
  11. package/client/plugos/plug.ts +0 -3
  12. package/client/plugos/plug_compile.ts +3 -13
  13. package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
  14. package/client/plugos/syscalls/asset.ts +61 -23
  15. package/client/plugos/syscalls/clientStore.ts +32 -6
  16. package/client/plugos/syscalls/client_code_widget.ts +9 -4
  17. package/client/plugos/syscalls/code_widget.ts +26 -11
  18. package/client/plugos/syscalls/config.ts +146 -30
  19. package/client/plugos/syscalls/datastore.ts +146 -40
  20. package/client/plugos/syscalls/editor.ts +1518 -722
  21. package/client/plugos/syscalls/event.ts +44 -13
  22. package/client/plugos/syscalls/fetch.ts +120 -83
  23. package/client/plugos/syscalls/icon.ts +46 -0
  24. package/client/plugos/syscalls/index.ts +267 -121
  25. package/client/plugos/syscalls/jsonschema.ts +107 -9
  26. package/client/plugos/syscalls/language.ts +38 -12
  27. package/client/plugos/syscalls/markdown.ts +166 -50
  28. package/client/plugos/syscalls/mq.ts +92 -20
  29. package/client/plugos/syscalls/navigator.ts +143 -0
  30. package/client/plugos/syscalls/schema_introspection.ts +33 -0
  31. package/client/plugos/syscalls/search.ts +44 -0
  32. package/client/plugos/syscalls/service_registry.ts +74 -20
  33. package/client/plugos/syscalls/shell.ts +51 -21
  34. package/client/plugos/syscalls/space.ts +412 -113
  35. package/client/plugos/syscalls/sync.ts +103 -20
  36. package/client/plugos/syscalls/system.ts +274 -139
  37. package/client/plugos/system.ts +28 -10
  38. package/client/plugos/worker_runtime.ts +0 -2
  39. package/client/space_lua/aggregates.ts +0 -16
  40. package/client/space_lua/api_documentation.ts +107 -0
  41. package/client/space_lua/ast.ts +13 -5
  42. package/client/space_lua/ast_narrow.ts +0 -5
  43. package/client/space_lua/budget.ts +113 -0
  44. package/client/space_lua/budget_ui.ts +49 -0
  45. package/client/space_lua/eval.ts +125 -92
  46. package/client/space_lua/labels.ts +0 -4
  47. package/client/space_lua/numeric.ts +0 -5
  48. package/client/space_lua/parse.ts +243 -125
  49. package/client/space_lua/pretty_print.ts +128 -54
  50. package/client/space_lua/quarantine.ts +127 -0
  51. package/client/space_lua/query_collection.ts +7 -51
  52. package/client/space_lua/query_env.ts +0 -2
  53. package/client/space_lua/render_lua_markdown.ts +2 -7
  54. package/client/space_lua/render_widget.ts +173 -0
  55. package/client/space_lua/runtime.ts +133 -92
  56. package/client/space_lua/stdlib/crypto.ts +8 -3
  57. package/client/space_lua/stdlib/encoding.ts +27 -9
  58. package/client/space_lua/stdlib/format.ts +0 -12
  59. package/client/space_lua/stdlib/js.ts +136 -23
  60. package/client/space_lua/stdlib/load.ts +24 -16
  61. package/client/space_lua/stdlib/math.ts +331 -133
  62. package/client/space_lua/stdlib/net.ts +57 -13
  63. package/client/space_lua/stdlib/os.ts +111 -54
  64. package/client/space_lua/stdlib/pattern.ts +10 -15
  65. package/client/space_lua/stdlib/space_lua.ts +344 -21
  66. package/client/space_lua/stdlib/string.ts +282 -104
  67. package/client/space_lua/stdlib/string_pack.ts +58 -22
  68. package/client/space_lua/stdlib/table.ts +195 -45
  69. package/client/space_lua/stdlib.ts +354 -125
  70. package/client/space_lua/syscalls.ts +270 -0
  71. package/client/space_lua/tonumber.ts +0 -9
  72. package/dist/plug-compile.js +2 -3
  73. package/package.json +15 -5
  74. package/plug-api/lib/async.ts +1 -6
  75. package/plug-api/lib/collation.ts +22 -0
  76. package/plug-api/lib/crypto.ts +16 -15
  77. package/plug-api/lib/dates.ts +33 -0
  78. package/plug-api/lib/fuzzy.ts +282 -0
  79. package/plug-api/lib/json.ts +0 -6
  80. package/plug-api/lib/limited_map.ts +0 -2
  81. package/plug-api/lib/link_write.ts +27 -0
  82. package/plug-api/{ui → lib}/panel_styles.ts +4 -3
  83. package/plug-api/lib/ref.ts +124 -5
  84. package/plug-api/lib/resolve.ts +0 -1
  85. package/plug-api/lib/resolve_path.ts +290 -0
  86. package/plug-api/lib/shortcut.ts +17 -1
  87. package/plug-api/lib/tags.ts +0 -3
  88. package/plug-api/lib/transclusion.ts +28 -1
  89. package/plug-api/lib/tree.ts +0 -5
  90. package/plug-api/lib/yaml.ts +3 -42
  91. package/plug-api/syscall.ts +1 -5
  92. package/plug-api/syscalls/config.ts +7 -5
  93. package/plug-api/syscalls/editor.ts +70 -208
  94. package/plug-api/syscalls/icon.ts +18 -0
  95. package/plug-api/syscalls/index.ts +39 -5
  96. package/plug-api/syscalls/jsonschema.ts +9 -0
  97. package/plug-api/syscalls/lua.ts +7 -0
  98. package/plug-api/syscalls/search.ts +11 -0
  99. package/plug-api/syscalls/shell.ts +0 -1
  100. package/plug-api/syscalls/space.ts +122 -5
  101. package/plug-api/syscalls/sync.ts +23 -2
  102. package/plug-api/syscalls/system.ts +53 -0
  103. package/plug-api/syscalls.ts +2 -0
  104. package/plug-api/system_mock.ts +15 -2
  105. package/plug-api/types/client.ts +4 -3
  106. package/plug-api/types/datastore.ts +11 -2
  107. package/plug-api/types/index.ts +62 -1
  108. package/plug-api/types/manifest.ts +22 -4
  109. package/plug-api/types/profile.ts +13 -0
  110. package/plug-api/types/revisions.ts +67 -0
  111. package/plug-api/ui/cx.ts +1 -3
  112. package/plug-api/ui/description.ts +64 -0
  113. package/plug-api/ui/hover.ts +69 -0
  114. package/plug-api/ui/index.ts +61 -2
  115. package/plug-api/ui/scroll.ts +30 -0
  116. package/plug-api/ui/slugify.ts +44 -0
  117. package/plug-api/ui/tree_model.ts +263 -0
  118. package/plug-api/ui/tree_types.ts +66 -0
  119. package/plug-api/ui/use_fit_collapse.ts +67 -0
  120. package/plugs/builtin_plugs.ts +0 -1
  121. package/plugs/index/types.ts +9 -0
  122. package/client/plugos/syscalls/lua.ts +0 -81
  123. package/plug-api/lib/memory_cache.ts +0 -21
@@ -1,5 +1,5 @@
1
1
  import type { Hook, Manifest } from "../types.ts";
2
- import type { SysCallMapping, System } from "../system.ts";
2
+ import type { SysCallMapping, SyscallContext, System } from "../system.ts";
3
3
  import type { SyscallHookT } from "@silverbulletmd/silverbullet/type/manifest";
4
4
 
5
5
  export class SyscallHook implements Hook<SyscallHookT> {
@@ -13,7 +13,6 @@ export class SyscallHook implements Hook<SyscallHookT> {
13
13
  }
14
14
 
15
15
  registerSyscalls(system: System<SyscallHookT>) {
16
- // Register syscalls from all loaded plugs
17
16
  for (const plug of system.loadedPlugs.values()) {
18
17
  const syscalls: SysCallMapping = {};
19
18
 
@@ -24,18 +23,27 @@ export class SyscallHook implements Hook<SyscallHookT> {
24
23
  continue;
25
24
  }
26
25
 
27
- const syscallName = functionDef.syscall;
26
+ const syscallDefinition = functionDef.syscall;
27
+ const syscallName =
28
+ typeof syscallDefinition === "string"
29
+ ? syscallDefinition
30
+ : syscallDefinition.name;
28
31
 
29
- // Add the syscall to our mapping
30
- syscalls[syscallName] = (ctx, ...args) => {
31
- // Delegate to the system to invoke the function
32
- return system.syscall(ctx, "system.invokeFunction", [
32
+ const callback = (ctx: SyscallContext, ...args: any[]) =>
33
+ system.syscall(ctx, "system.invokeFunction", [
33
34
  `${plug.manifest!.name}.${name}`,
34
35
  ...args,
35
36
  ]);
36
- };
37
+ if (typeof syscallDefinition === "string") {
38
+ syscalls[syscallName] = callback;
39
+ } else {
40
+ const { name: _name, ...metadata } = syscallDefinition;
41
+ syscalls[syscallName] = {
42
+ callback,
43
+ ...metadata,
44
+ };
45
+ }
37
46
 
38
- // Register the syscalls with no required permissions
39
47
  system.registerSyscalls([], syscalls);
40
48
  }
41
49
  }
@@ -48,16 +56,18 @@ export class SyscallHook implements Hook<SyscallHookT> {
48
56
  continue;
49
57
  }
50
58
 
51
- // Validate syscall name is provided
52
- if (!functionDef.syscall) {
59
+ const syscallName =
60
+ typeof functionDef.syscall === "string"
61
+ ? functionDef.syscall
62
+ : functionDef.syscall.name;
63
+ if (!syscallName) {
53
64
  errors.push(`Function ${name} has a syscall but no name`);
54
65
  continue;
55
66
  }
56
67
 
57
- // Validate syscall name format (should be namespaced)
58
- if (!functionDef.syscall.includes(".")) {
68
+ if (!syscallName.includes(".")) {
59
69
  errors.push(
60
- `Function ${name} has invalid syscall name "${functionDef.syscall}" - must be in format "namespace.name"`,
70
+ `Function ${name} has invalid syscall name "${syscallName}" - must be in format "namespace.name"`,
61
71
  );
62
72
  }
63
73
  }
@@ -23,7 +23,6 @@ export class KVPrimitivesManifestCache<T> implements ManifestCache<T> {
23
23
  ): Promise<Manifest<T>> {
24
24
  const [cached] = await this.kv.batchGet([[this.manifestPrefix, cacheKey]]);
25
25
  if (cached && cached.hash === cacheHash) {
26
- // console.log("Using KV cached manifest for", plug.name);
27
26
  return cached.manifest;
28
27
  }
29
28
  await plug.sandbox.init();
@@ -41,34 +40,3 @@ export class KVPrimitivesManifestCache<T> implements ManifestCache<T> {
41
40
  return manifest;
42
41
  }
43
42
  }
44
-
45
- export class InMemoryManifestCache<T> implements ManifestCache<T> {
46
- private cache = new Map<
47
- string,
48
- {
49
- manifest: Manifest<T>;
50
- hash: number;
51
- }
52
- >();
53
-
54
- async getManifest(
55
- plug: Plug<T>,
56
- cacheKey: string,
57
- cacheHash: number,
58
- ): Promise<Manifest<T>> {
59
- const cached = this.cache.get(cacheKey);
60
- if (cached && cached.hash === cacheHash) {
61
- // console.log("Using memory cached manifest for", plug.name);
62
- return cached.manifest;
63
- }
64
- await plug.sandbox.init();
65
- const manifest = plug.sandbox.manifest!;
66
-
67
- // Deliverately removing the assets from the manifest to preserve space, will be re-added upon load of actual worker
68
- this.cache.set(cacheKey, {
69
- manifest: { ...manifest, assets: undefined },
70
- hash: cacheHash,
71
- });
72
- return manifest;
73
- }
74
- }
@@ -31,7 +31,6 @@ export class Plug<HookT> {
31
31
  const plug = new Plug(system, sandboxFactory);
32
32
  plug.path = path;
33
33
 
34
- // Retrieve the manifest, which may either come from a cache or be loaded from the worker
35
34
  plug.manifest = await system.options.manifestCache!.getManifest(
36
35
  plug,
37
36
  path,
@@ -62,14 +61,12 @@ export class Plug<HookT> {
62
61
 
63
62
  // Invoke a function
64
63
  async invoke(name: string, args: any[]): Promise<any> {
65
- // Before we access the manifest
66
64
  const funDef = this.manifest!.functions[name];
67
65
  if (!funDef) {
68
66
  throw new Error(`Function ${name} not found in manifest`);
69
67
  }
70
68
  const sandbox = this.sandbox!;
71
69
  if (funDef.redirect) {
72
- // Function redirect, look up
73
70
  let plug: Plug<HookT> | undefined = this;
74
71
  if (funDef.redirect.indexOf(".") !== -1) {
75
72
  const [plugName, functionName] = funDef.redirect.split(".");
@@ -24,7 +24,6 @@ const workerRuntimePlugin: esbuild.Plugin = {
24
24
  name: "worker-runtime",
25
25
  setup(build) {
26
26
  if (typeof __EMBEDDED_WORKER_RUNTIME_JS__ !== "undefined") {
27
- // Bundled CLI: serve pre-bundled JS from the embedded constant
28
27
  build.onResolve({ filter: /^worker-runtime$/ }, () => ({
29
28
  path: "worker-runtime",
30
29
  namespace: "worker-runtime",
@@ -34,7 +33,6 @@ const workerRuntimePlugin: esbuild.Plugin = {
34
33
  loader: "js",
35
34
  }));
36
35
  } else {
37
- // From source: point directly at the .ts file, esbuild handles it
38
36
  build.onResolve({ filter: /^worker-runtime$/ }, () => ({
39
37
  path: path.join(import.meta.dirname, "worker_runtime.ts"),
40
38
  }));
@@ -70,14 +68,12 @@ export async function compileManifest(
70
68
  delete manifest.build;
71
69
  }
72
70
 
73
- // Assets
74
71
  const assetsBundle = await bundleAssets(
75
72
  path.resolve(rootPath),
76
73
  (manifest.assets as string[]) || [],
77
74
  );
78
75
  manifest.assets = assetsBundle.toJSON();
79
76
 
80
- // Normalize the edge case of a plug with no functions
81
77
  if (!manifest.functions) {
82
78
  manifest.functions = {};
83
79
  }
@@ -92,13 +88,11 @@ ${Object.entries(manifest.functions)
92
88
  return "";
93
89
  }
94
90
  let [filePath, jsFunctionName] = def.path.split(":");
95
- // Resolve path
96
91
  filePath = path.join(rootPath, filePath);
97
92
 
98
- return `import {${jsFunctionName} as ${funcName}} from "${
99
- // Replacing \ with / for Windows
100
- path.resolve(filePath).replaceAll("\\", "\\\\")
101
- }";\n`;
93
+ return `import {${jsFunctionName} as ${funcName}} from "${path
94
+ .resolve(filePath)
95
+ .replaceAll("\\", "\\\\")}";\n`;
102
96
  })
103
97
  .join("")}
104
98
 
@@ -122,8 +116,6 @@ export const plug = {manifest, functionMapping};
122
116
  setupMessageListener(functionMapping, manifest, self.postMessage);
123
117
  `;
124
118
 
125
- // console.log("Code:", jsFile);
126
-
127
119
  const tempDir = await mkdtemp(path.join(tmpdir(), "plug-compile-"));
128
120
  const inFile = path.join(tempDir, "input.js");
129
121
  const outFile = `${destPath}/${manifest.name}.plug.js`;
@@ -152,7 +144,6 @@ setupMessageListener(functionMapping, manifest, self.postMessage);
152
144
  jsCode = patchBundledJS(jsCode);
153
145
  await writeFile(outFile, jsCode, "utf-8");
154
146
 
155
- // Clean up temp directory
156
147
  await rm(tempDir, { recursive: true, force: true });
157
148
 
158
149
  console.log(`Plug ${manifest.name} written to ${outFile}.`);
@@ -175,7 +166,6 @@ export async function compileManifests(
175
166
  building = true;
176
167
  await mkdir(dist, { recursive: true });
177
168
  const startTime = Date.now();
178
- // Build all plugs in parallel
179
169
  await Promise.all(
180
170
  manifestFiles.map(async (plugManifestPath) => {
181
171
  const manifestPath = plugManifestPath as string;
@@ -7,7 +7,6 @@ import { race, timeout } from "@silverbulletmd/silverbullet/lib/async";
7
7
  import { fsEndpoint } from "../../spaces/constants.ts";
8
8
 
9
9
  /**
10
- * Represents a "safe" execution environment for plug code
11
10
  * Effectively this wraps a web worker, the reason to have this split from Plugs is to allow plugs to manage multiple sandboxes, e.g. for performance in the future
12
11
  */
13
12
  export class WorkerSandbox<HookT> implements Sandbox<HookT> {
@@ -25,7 +24,6 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
25
24
  private workerOptions = {},
26
25
  ) {}
27
26
 
28
- /** Factory producing a WorkerSandbox for a plug file at a local space path. */
29
27
  static forPath<HookT>(path: string): SandboxFactory<HookT> {
30
28
  return (plug) =>
31
29
  new WorkerSandbox(
@@ -40,7 +38,6 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
40
38
  init(): Promise<void> {
41
39
  console.log("Booting up worker from", this.workerUrl.pathname);
42
40
  if (this.worker) {
43
- // Race condition
44
41
  console.warn("Double init of sandbox, ignoring");
45
42
  return Promise.resolve();
46
43
  }
@@ -54,14 +51,12 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
54
51
  timeout(5000).catch((_) =>
55
52
  Promise.reject(new Error("Plug timed out during creation")),
56
53
  ),
57
- new Promise((resolve) => {
54
+ new Promise<void>((resolve) => {
58
55
  this.worker!.onmessage = (ev) => {
59
56
  if (ev.data.type === "manifest") {
60
57
  this.manifest = ev.data.manifest;
61
- // Set manifest in the plug
62
58
  this.plug.manifest = this.manifest!;
63
59
 
64
- // Set assets in the plug
65
60
  this.plug.assets = new AssetBundle(
66
61
  this.manifest?.assets ? (this.manifest.assets as AssetJson) : {},
67
62
  );
@@ -72,7 +67,15 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
72
67
  void this.onMessage(ev.data);
73
68
  };
74
69
  }),
75
- ]);
70
+ ]).catch((e) => {
71
+ // A worker that failed to come up must not linger: its script fetch may
72
+ // still be in flight (e.g. on a slow link), and a late boot would
73
+ // resolve into a sandbox the system already gave up on. Terminating it
74
+ // also lets a retry start from a clean slate.
75
+ this.worker?.terminate();
76
+ this.worker = undefined;
77
+ throw e;
78
+ });
76
79
  }
77
80
 
78
81
  async onMessage(data: ControllerMessage) {
@@ -92,7 +95,6 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
92
95
  result: result,
93
96
  } as WorkerMessage);
94
97
  } catch (e: any) {
95
- // console.error("Syscall fail", e);
96
98
  this.worker &&
97
99
  this.worker!.postMessage({
98
100
  type: "sysr",
@@ -118,7 +120,6 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
118
120
 
119
121
  async invoke(name: string, args: any[]): Promise<any> {
120
122
  if (!this.worker) {
121
- // Lazy initialization
122
123
  await this.init();
123
124
  }
124
125
  this.reqId++;
@@ -138,5 +139,14 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
138
139
  this.worker.terminate();
139
140
  this.worker = undefined;
140
141
  }
142
+ // A terminated worker will never post the `invr` that would otherwise
143
+ // settle these.
144
+ if (this.outstandingInvocations.size > 0) {
145
+ const error = new Error("Plug sandbox stopped");
146
+ for (const { reject } of this.outstandingInvocations.values()) {
147
+ reject(error);
148
+ }
149
+ this.outstandingInvocations.clear();
150
+ }
141
151
  }
142
152
  }
@@ -3,31 +3,69 @@ import type { FileMeta } from "../../../plug-api/types/index.ts";
3
3
 
4
4
  export default function assetSyscalls(system: System<any>): SysCallMapping {
5
5
  return {
6
- "asset.readAsset": (_ctx, plugName: string, name: string): string => {
7
- return system.loadedPlugs.get(plugName)!.assets!.readFileAsDataUrl(name);
6
+ "asset.readAsset": {
7
+ callback: (_ctx, plugName: string, name: string): string => {
8
+ return system.loadedPlugs
9
+ .get(plugName)!
10
+ .assets!.readFileAsDataUrl(name);
11
+ },
12
+ description: "Reads an asset embedded in a plug as a data URL.",
13
+ parameters: [
14
+ { name: "plugName", type: "string", description: "Plug name." },
15
+ { name: "name", type: "string", description: "Asset path." },
16
+ ],
17
+ returns: [{ type: "string", description: "Asset data URL." }],
18
+ examples: [
19
+ { code: 'local image = asset.readAsset("myplug", "image.png")' },
20
+ ],
8
21
  },
9
- "asset.listFiles": (_ctx, plugName: string): FileMeta[] => {
10
- const assets = system.loadedPlugs.get(plugName)!.assets!;
11
- const fileNames = assets.listFiles();
12
- return fileNames.map((name) => ({
13
- name,
14
- contentType: assets.getMimeType(name),
15
- created: assets.getMtime(name),
16
- lastModified: assets.getMtime(name),
17
- size: -1,
18
- perm: "ro",
19
- }));
22
+ "asset.listFiles": {
23
+ callback: (_ctx, plugName: string): FileMeta[] => {
24
+ const assets = system.loadedPlugs.get(plugName)!.assets!;
25
+ const fileNames = assets.listFiles();
26
+ return fileNames.map((name) => ({
27
+ name,
28
+ contentType: assets.getMimeType(name),
29
+ created: assets.getMtime(name),
30
+ lastModified: assets.getMtime(name),
31
+ size: -1,
32
+ perm: "ro",
33
+ }));
34
+ },
35
+ description: "Lists the assets embedded in a plug.",
36
+ parameters: [
37
+ { name: "plugName", type: "string", description: "Plug name." },
38
+ ],
39
+ returns: [{ type: "table", description: "List of file metadata." }],
40
+ examples: [
41
+ {
42
+ code: 'for _, file in ipairs(asset.listFiles("myplug")) do\n print(file.name)\nend',
43
+ },
44
+ ],
20
45
  },
21
- "asset.getFileMeta": (_ctx, plugName: string, name: string): FileMeta => {
22
- const assets = system.loadedPlugs.get(plugName)!.assets!;
23
- return {
24
- name,
25
- contentType: assets.getMimeType(name),
26
- created: assets.getMtime(name),
27
- lastModified: assets.getMtime(name),
28
- size: -1,
29
- perm: "ro",
30
- };
46
+ "asset.getFileMeta": {
47
+ callback: (_ctx, plugName: string, name: string): FileMeta => {
48
+ const assets = system.loadedPlugs.get(plugName)!.assets!;
49
+ return {
50
+ name,
51
+ contentType: assets.getMimeType(name),
52
+ created: assets.getMtime(name),
53
+ lastModified: assets.getMtime(name),
54
+ size: -1,
55
+ perm: "ro",
56
+ };
57
+ },
58
+ description: "Gets metadata for an asset embedded in a plug.",
59
+ parameters: [
60
+ { name: "plugName", type: "string", description: "Plug name." },
61
+ { name: "name", type: "string", description: "Asset path." },
62
+ ],
63
+ returns: [{ type: "table", description: "File metadata." }],
64
+ examples: [
65
+ {
66
+ code: 'local meta = asset.getFileMeta("myplug", "data.txt")\nprint(meta.lastModified)',
67
+ },
68
+ ],
31
69
  },
32
70
  };
33
71
  }
@@ -8,14 +8,40 @@ export function clientStoreSyscalls(
8
8
  prefix: KvKey = ["client"],
9
9
  ): SysCallMapping {
10
10
  return {
11
- "clientStore.get": (_ctx, key: string): Promise<any> => {
12
- return ds.get([...prefix, key]);
11
+ "clientStore.get": {
12
+ callback: (_ctx, key: string): Promise<any> => {
13
+ return ds.get([...prefix, key]);
14
+ },
15
+ description:
16
+ "Gets a client-specific value from the local key-value store.",
17
+ parameters: [
18
+ { name: "key", type: "string", description: "Key to read." },
19
+ ],
20
+ returns: [{ description: "Stored value, or nil when absent." }],
21
+ examples: [{ code: 'local theme = clientStore.get("theme")' }],
13
22
  },
14
- "clientStore.set": (_ctx, key: string, val: any): Promise<void> => {
15
- return ds.set([...prefix, key], val);
23
+ "clientStore.set": {
24
+ callback: (_ctx, key: string, val: any): Promise<void> => {
25
+ return ds.set([...prefix, key], val);
26
+ },
27
+ description:
28
+ "Stores a client-specific value in the local key-value store.",
29
+ parameters: [
30
+ { name: "key", type: "string", description: "Key to set." },
31
+ { name: "value", description: "Value to store." },
32
+ ],
33
+ examples: [{ code: 'clientStore.set("theme", "dark")' }],
16
34
  },
17
- "clientStore.delete": (_ctx, key: string): Promise<void> => {
18
- return ds.delete([...prefix, key]);
35
+ "clientStore.delete": {
36
+ callback: (_ctx, key: string): Promise<void> => {
37
+ return ds.delete([...prefix, key]);
38
+ },
39
+ description:
40
+ "Deletes a client-specific value from the local key-value store.",
41
+ parameters: [
42
+ { name: "key", type: "string", description: "Key to delete." },
43
+ ],
44
+ examples: [{ code: 'clientStore.delete("theme")' }],
19
45
  },
20
46
  };
21
47
  }
@@ -5,10 +5,15 @@ import { broadcastReload } from "../../components/widget_sandbox_iframe.ts";
5
5
 
6
6
  export function clientCodeWidgetSyscalls(client: Client): SysCallMapping {
7
7
  return {
8
- "codeWidget.refreshAll": () => {
9
- client.widgetCache.clearPrewarm();
10
- broadcastReload();
11
- return reloadAllWidgets();
8
+ "codeWidget.refreshAll": {
9
+ callback: () => {
10
+ client.widgetCache.clearPrewarm();
11
+ broadcastReload();
12
+ return reloadAllWidgets();
13
+ },
14
+ description:
15
+ "Refreshes all code widgets on the current page that support refreshing.",
16
+ examples: [{ code: "codeWidget.refreshAll()" }],
12
17
  },
13
18
  };
14
19
  }
@@ -6,17 +6,32 @@ export function codeWidgetSyscalls(
6
6
  codeWidgetHook: CodeWidgetHook,
7
7
  ): SysCallMapping {
8
8
  return {
9
- "codeWidget.render": (
10
- _ctx,
11
- lang: string,
12
- body: string,
13
- pageName: string,
14
- ): Promise<CodeWidgetContent | null> => {
15
- const langCallback = codeWidgetHook.codeWidgetCallbacks.get(lang);
16
- if (!langCallback) {
17
- throw new Error(`Code widget ${lang} not found`);
18
- }
19
- return langCallback(body, pageName);
9
+ "codeWidget.render": {
10
+ callback: (
11
+ _ctx,
12
+ lang: string,
13
+ body: string,
14
+ pageName: string,
15
+ ): Promise<CodeWidgetContent | null> => {
16
+ const langCallback = codeWidgetHook.codeWidgetCallbacks.get(lang);
17
+ if (!langCallback) {
18
+ throw new Error(`Code widget ${lang} not found`);
19
+ }
20
+ return langCallback(body, pageName);
21
+ },
22
+ description: "Renders code through the widget registered for a language.",
23
+ parameters: [
24
+ { name: "language", type: "string", description: "Widget language." },
25
+ { name: "body", type: "string", description: "Code block body." },
26
+ {
27
+ name: "pageName",
28
+ type: "string",
29
+ description: "Containing page name.",
30
+ },
31
+ ],
32
+ returns: [
33
+ { type: "table", description: "Rendered widget content, or nil." },
34
+ ],
20
35
  },
21
36
  };
22
37
  }