@silverbulletmd/silverbullet 2.4.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 (117) hide show
  1. package/LICENSE.md +18 -0
  2. package/README.md +98 -0
  3. package/client/asset_bundle/bundle.ts +95 -0
  4. package/client/data/datastore.ts +85 -0
  5. package/client/data/kv_primitives.ts +25 -0
  6. package/client/markdown_parser/constants.ts +13 -0
  7. package/client/plugos/event.ts +36 -0
  8. package/client/plugos/eventhook.ts +8 -0
  9. package/client/plugos/hooks/code_widget.ts +59 -0
  10. package/client/plugos/hooks/command.ts +104 -0
  11. package/client/plugos/hooks/document_editor.ts +77 -0
  12. package/client/plugos/hooks/event.ts +187 -0
  13. package/client/plugos/hooks/mq.ts +154 -0
  14. package/client/plugos/hooks/plug_namespace.ts +85 -0
  15. package/client/plugos/hooks/slash_command.ts +192 -0
  16. package/client/plugos/hooks/syscall.ts +66 -0
  17. package/client/plugos/manifest_cache.ts +67 -0
  18. package/client/plugos/plug.ts +99 -0
  19. package/client/plugos/plug_compile.ts +202 -0
  20. package/client/plugos/protocol.ts +40 -0
  21. package/client/plugos/proxy_fetch.ts +53 -0
  22. package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
  23. package/client/plugos/sandboxes/sandbox.ts +14 -0
  24. package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
  25. package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
  26. package/client/plugos/syscalls/asset.ts +35 -0
  27. package/client/plugos/syscalls/clientStore.ts +21 -0
  28. package/client/plugos/syscalls/client_code_widget.ts +12 -0
  29. package/client/plugos/syscalls/code_widget.ts +24 -0
  30. package/client/plugos/syscalls/config.ts +46 -0
  31. package/client/plugos/syscalls/datastore.ts +89 -0
  32. package/client/plugos/syscalls/editor.ts +673 -0
  33. package/client/plugos/syscalls/event.ts +36 -0
  34. package/client/plugos/syscalls/fetch.ts +128 -0
  35. package/client/plugos/syscalls/index.ts +102 -0
  36. package/client/plugos/syscalls/jsonschema.ts +69 -0
  37. package/client/plugos/syscalls/language.ts +23 -0
  38. package/client/plugos/syscalls/lua.ts +58 -0
  39. package/client/plugos/syscalls/markdown.ts +84 -0
  40. package/client/plugos/syscalls/mq.ts +52 -0
  41. package/client/plugos/syscalls/service_registry.ts +43 -0
  42. package/client/plugos/syscalls/shell.ts +39 -0
  43. package/client/plugos/syscalls/space.ts +139 -0
  44. package/client/plugos/syscalls/sync.ts +77 -0
  45. package/client/plugos/syscalls/system.ts +150 -0
  46. package/client/plugos/system.ts +201 -0
  47. package/client/plugos/types.ts +60 -0
  48. package/client/plugos/util.ts +14 -0
  49. package/client/plugos/worker_runtime.ts +195 -0
  50. package/client/space_lua/ast.ts +328 -0
  51. package/client/space_lua/ast_narrow.ts +81 -0
  52. package/client/space_lua/eval.ts +2478 -0
  53. package/client/space_lua/labels.ts +416 -0
  54. package/client/space_lua/numeric.ts +240 -0
  55. package/client/space_lua/parse.ts +1522 -0
  56. package/client/space_lua/query_collection.ts +232 -0
  57. package/client/space_lua/rp.ts +27 -0
  58. package/client/space_lua/runtime.ts +1702 -0
  59. package/client/space_lua/stdlib/crypto.ts +10 -0
  60. package/client/space_lua/stdlib/encoding.ts +19 -0
  61. package/client/space_lua/stdlib/format.ts +770 -0
  62. package/client/space_lua/stdlib/js.ts +73 -0
  63. package/client/space_lua/stdlib/load.ts +52 -0
  64. package/client/space_lua/stdlib/math.ts +193 -0
  65. package/client/space_lua/stdlib/net.ts +113 -0
  66. package/client/space_lua/stdlib/os.ts +368 -0
  67. package/client/space_lua/stdlib/space_lua.ts +153 -0
  68. package/client/space_lua/stdlib/string.ts +286 -0
  69. package/client/space_lua/stdlib/table.ts +401 -0
  70. package/client/space_lua/stdlib.ts +489 -0
  71. package/client/space_lua/tonumber.ts +501 -0
  72. package/client/space_lua/util.ts +96 -0
  73. package/dist/plug-compile.js +1513 -0
  74. package/package.json +120 -0
  75. package/plug-api/constants.ts +42 -0
  76. package/plug-api/lib/async.ts +162 -0
  77. package/plug-api/lib/crypto.ts +202 -0
  78. package/plug-api/lib/dates.ts +13 -0
  79. package/plug-api/lib/json.ts +136 -0
  80. package/plug-api/lib/limited_map.ts +72 -0
  81. package/plug-api/lib/memory_cache.ts +21 -0
  82. package/plug-api/lib/native_fetch.ts +6 -0
  83. package/plug-api/lib/ref.ts +275 -0
  84. package/plug-api/lib/resolve.ts +90 -0
  85. package/plug-api/lib/tags.ts +15 -0
  86. package/plug-api/lib/transclusion.ts +122 -0
  87. package/plug-api/lib/tree.ts +232 -0
  88. package/plug-api/lib/yaml.ts +284 -0
  89. package/plug-api/syscall.ts +15 -0
  90. package/plug-api/syscalls/asset.ts +36 -0
  91. package/plug-api/syscalls/client_store.ts +33 -0
  92. package/plug-api/syscalls/code_widget.ts +8 -0
  93. package/plug-api/syscalls/config.ts +58 -0
  94. package/plug-api/syscalls/datastore.ts +96 -0
  95. package/plug-api/syscalls/editor.ts +517 -0
  96. package/plug-api/syscalls/event.ts +47 -0
  97. package/plug-api/syscalls/index.ts +77 -0
  98. package/plug-api/syscalls/jsonschema.ts +25 -0
  99. package/plug-api/syscalls/language.ts +23 -0
  100. package/plug-api/syscalls/lua.ts +20 -0
  101. package/plug-api/syscalls/markdown.ts +38 -0
  102. package/plug-api/syscalls/mq.ts +79 -0
  103. package/plug-api/syscalls/shell.ts +14 -0
  104. package/plug-api/syscalls/space.ts +212 -0
  105. package/plug-api/syscalls/sync.ts +28 -0
  106. package/plug-api/syscalls/system.ts +102 -0
  107. package/plug-api/syscalls/yaml.ts +28 -0
  108. package/plug-api/syscalls.ts +21 -0
  109. package/plug-api/system_mock.ts +89 -0
  110. package/plug-api/types/client.ts +116 -0
  111. package/plug-api/types/config.ts +22 -0
  112. package/plug-api/types/datastore.ts +28 -0
  113. package/plug-api/types/event.ts +27 -0
  114. package/plug-api/types/index.ts +56 -0
  115. package/plug-api/types/manifest.ts +98 -0
  116. package/plug-api/types/namespace.ts +6 -0
  117. package/plugs/builtin_plugs.ts +14 -0
@@ -0,0 +1,232 @@
1
+ import { deepClone } from "@silverbulletmd/silverbullet/lib/json";
2
+
3
+ export type ParseTree = {
4
+ type?: string; // undefined === text node
5
+ from?: number;
6
+ to?: number;
7
+ text?: string;
8
+ children?: ParseTree[];
9
+ // Only present after running addParentPointers
10
+ parent?: ParseTree;
11
+ };
12
+
13
+ export function addParentPointers(tree: ParseTree) {
14
+ if (!tree.children) {
15
+ return;
16
+ }
17
+ for (const child of tree.children) {
18
+ child.parent = tree;
19
+ addParentPointers(child);
20
+ }
21
+ }
22
+
23
+ export function removeParentPointers(tree: ParseTree) {
24
+ delete tree.parent;
25
+ if (!tree.children) {
26
+ return;
27
+ }
28
+ for (const child of tree.children) {
29
+ removeParentPointers(child);
30
+ }
31
+ }
32
+
33
+ export function findParentMatching(
34
+ tree: ParseTree,
35
+ matchFn: (tree: ParseTree) => boolean,
36
+ ): ParseTree | null {
37
+ let node = tree.parent;
38
+ while (node) {
39
+ if (matchFn(node)) {
40
+ return node;
41
+ }
42
+ node = node.parent;
43
+ }
44
+ return null;
45
+ }
46
+
47
+ export function collectNodesOfType(
48
+ tree: ParseTree,
49
+ nodeType: string,
50
+ ): ParseTree[] {
51
+ return collectNodesMatching(tree, (n) => n.type === nodeType);
52
+ }
53
+
54
+ export function collectNodesMatching(
55
+ tree: ParseTree,
56
+ matchFn: (tree: ParseTree) => boolean,
57
+ ): ParseTree[] {
58
+ if (matchFn(tree)) {
59
+ return [tree];
60
+ }
61
+ let results: ParseTree[] = [];
62
+ if (tree.children) {
63
+ for (const child of tree.children) {
64
+ results = [...results, ...collectNodesMatching(child, matchFn)];
65
+ }
66
+ }
67
+ return results;
68
+ }
69
+
70
+ export async function collectNodesMatchingAsync(
71
+ tree: ParseTree,
72
+ matchFn: (tree: ParseTree) => Promise<boolean>,
73
+ ): Promise<ParseTree[]> {
74
+ if (await matchFn(tree)) {
75
+ return [tree];
76
+ }
77
+ let results: ParseTree[] = [];
78
+ if (tree.children) {
79
+ for (const child of tree.children) {
80
+ results = [
81
+ ...results,
82
+ ...await collectNodesMatchingAsync(child, matchFn),
83
+ ];
84
+ }
85
+ }
86
+ return results;
87
+ }
88
+
89
+ // return value: returning undefined = not matched, continue, null = delete, new node = replace
90
+ export function replaceNodesMatching(
91
+ tree: ParseTree,
92
+ substituteFn: (tree: ParseTree) => ParseTree | null | undefined,
93
+ ) {
94
+ if (tree && tree.children) {
95
+ const children = tree.children.slice();
96
+ for (const child of children) {
97
+ const subst = substituteFn(child);
98
+ if (subst !== undefined) {
99
+ const pos = tree.children.indexOf(child);
100
+ if (subst) {
101
+ tree.children.splice(pos, 1, subst);
102
+ } else {
103
+ // null = delete
104
+ tree.children.splice(pos, 1);
105
+ }
106
+ } else {
107
+ replaceNodesMatching(child, substituteFn);
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ export async function replaceNodesMatchingAsync(
114
+ tree: ParseTree,
115
+ substituteFn: (tree: ParseTree) => Promise<ParseTree | null | undefined>,
116
+ ) {
117
+ if (tree.children) {
118
+ const children = tree.children.slice();
119
+ for (const child of children) {
120
+ const subst = await substituteFn(child);
121
+ if (subst !== undefined) {
122
+ const pos = tree.children.indexOf(child);
123
+ if (subst) {
124
+ tree.children.splice(pos, 1, subst);
125
+ } else {
126
+ // null = delete
127
+ tree.children.splice(pos, 1);
128
+ }
129
+ } else {
130
+ await replaceNodesMatchingAsync(child, substituteFn);
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ export function findNodeMatching(
137
+ tree: ParseTree,
138
+ matchFn: (tree: ParseTree) => boolean,
139
+ ): ParseTree | null {
140
+ return collectNodesMatching(tree, matchFn)[0];
141
+ }
142
+
143
+ export function findNodeOfType(
144
+ tree: ParseTree,
145
+ nodeType: string,
146
+ ): ParseTree | null {
147
+ return collectNodesMatching(tree, (n) => n.type === nodeType)[0];
148
+ }
149
+
150
+ export function traverseTree(
151
+ tree: ParseTree,
152
+ // Return value = should stop traversal?
153
+ matchFn: (tree: ParseTree) => boolean,
154
+ ): void {
155
+ // Do a collect, but ignore the result
156
+ collectNodesMatching(tree, matchFn);
157
+ }
158
+
159
+ export async function traverseTreeAsync(
160
+ tree: ParseTree,
161
+ // Return value = should stop traversal?
162
+ matchFn: (tree: ParseTree) => Promise<boolean>,
163
+ ): Promise<void> {
164
+ // Do a collect, but ignore the result
165
+ await collectNodesMatchingAsync(tree, matchFn);
166
+ }
167
+
168
+ export function cloneTree(tree: ParseTree): ParseTree {
169
+ return deepClone(tree, ["parent"]);
170
+ }
171
+
172
+ // Finds non-text node at position
173
+ export function nodeAtPos(tree: ParseTree, pos: number): ParseTree | null {
174
+ if (pos < tree.from! || pos >= tree.to!) {
175
+ return null;
176
+ }
177
+ if (!tree.children) {
178
+ return tree;
179
+ }
180
+ for (const child of tree.children) {
181
+ const n = nodeAtPos(child, pos);
182
+ if (n && n.text !== undefined) {
183
+ // Got a text node, let's return its parent
184
+ return tree;
185
+ } else if (n) {
186
+ // Got it
187
+ return n;
188
+ }
189
+ }
190
+ return null;
191
+ }
192
+
193
+ // Turn ParseTree back into text
194
+ export function renderToText(tree?: ParseTree): string {
195
+ if (!tree) {
196
+ return "";
197
+ }
198
+ const pieces: string[] = [];
199
+ if (tree.text !== undefined) {
200
+ return tree.text;
201
+ }
202
+ for (const child of tree.children!) {
203
+ pieces.push(renderToText(child));
204
+ }
205
+ return pieces.join("");
206
+ }
207
+
208
+ export function cleanTree(tree: ParseTree, omitTrimmable = true): ParseTree {
209
+ if (tree.type === "⚠") {
210
+ throw new Error(
211
+ `Parse error at pos ${tree.from}`,
212
+ );
213
+ }
214
+ if (tree.text !== undefined) {
215
+ return tree;
216
+ }
217
+ const ast: ParseTree = {
218
+ type: tree.type,
219
+ children: [],
220
+ from: tree.from,
221
+ to: tree.to,
222
+ };
223
+ for (const node of tree.children!) {
224
+ if (node.type && node.type !== "Comment") {
225
+ ast.children!.push(cleanTree(node, omitTrimmable));
226
+ }
227
+ if (node.text && (omitTrimmable && node.text.trim() || !omitTrimmable)) {
228
+ ast.children!.push(node);
229
+ }
230
+ }
231
+ return ast;
232
+ }
@@ -0,0 +1,284 @@
1
+ // Define the patch structure
2
+ export interface YamlPatch {
3
+ op: "set-key" | "delete-key";
4
+ path: string; // Still assuming simple, top-level key names
5
+ value?: any; // Required for set-key, not used for delete-key
6
+ }
7
+
8
+ // Helper function specifically for serializing scalar types
9
+ function serializeToYamlScalar(
10
+ value: string | number | boolean | null,
11
+ ): string {
12
+ if (typeof value === "string") {
13
+ // Always quote empty strings and strings with special characters
14
+ if (
15
+ value === "" || // Empty string
16
+ value.match(/[:{#}[],&*!|>'"%@`]/) || // Special YAML characters (added % and ")
17
+ /^\d+(\.\d+)?([eE][+-]?\d+)?$/.test(value) || // Looks like a number
18
+ value.includes(":") || // Contains colons
19
+ ["true", "false", "null", "yes", "no", "on", "off"].includes(
20
+ value.toLowerCase(),
21
+ )
22
+ ) {
23
+ return JSON.stringify(value);
24
+ }
25
+ // Simple strings without special chars/meaning don't need quotes
26
+ return value;
27
+ } else if (
28
+ typeof value === "number" || typeof value === "boolean" || value === null
29
+ ) {
30
+ return String(value);
31
+ }
32
+ // Default for unsupported scalar types (e.g., undefined)
33
+ return "null";
34
+ }
35
+
36
+ // Updated helper function to serialize various JS types to YAML string representations
37
+ // Added baseIndentation parameter for handling nested lists correctly (though we only use it for top-level lists here)
38
+ function serializeToYamlValue(
39
+ value: any,
40
+ baseIndentation: string = "",
41
+ ): string {
42
+ if (Array.isArray(value)) {
43
+ if (value.length === 0) {
44
+ return "[]"; // Use flow style for empty arrays for simplicity
45
+ }
46
+ // Determine indentation for list items (base + 2 spaces)
47
+ const itemIndentation = baseIndentation + " ";
48
+ // Format each item recursively, preceded by '- ' marker
49
+ return "\n" +
50
+ value.map((item) =>
51
+ `${itemIndentation}- ${serializeToYamlValue(item, itemIndentation)}`
52
+ ).join("\n");
53
+ // Note: serializeToYamlValue is used recursively here to handle nested arrays/objects if needed in future
54
+ // However, the current `applyMinimalSetKeyPatches` only handles top-level keys.
55
+ } else if (typeof value === "object" && value !== null) {
56
+ // Basic object serialization (not requested, but good to consider)
57
+ // This is highly simplified and doesn't handle nesting well without more context
58
+ const itemIndentation = baseIndentation + " ";
59
+ const entries = Object.entries(value);
60
+ if (entries.length === 0) return "{}"; // Flow style empty objects
61
+ return "\n" +
62
+ entries.map(([key, val]) =>
63
+ `${itemIndentation}${key}: ${
64
+ serializeToYamlValue(val, itemIndentation)
65
+ }`
66
+ ).join("\n");
67
+ } else {
68
+ // Handle scalars using the dedicated function
69
+ return serializeToYamlScalar(value);
70
+ }
71
+ }
72
+
73
+ export function applyPatches(
74
+ yamlString: string,
75
+ patches: YamlPatch[],
76
+ ): string {
77
+ let currentYaml = yamlString;
78
+
79
+ for (const patch of patches) {
80
+ if (patch.op === "delete-key") {
81
+ // Handle delete operation
82
+ const key = patch.path;
83
+ const lines = currentYaml.split("\n");
84
+ let keyLineIndex = -1;
85
+ let startDeleteIndex = -1;
86
+ let endDeleteIndex = -1;
87
+
88
+ // Find the key line
89
+ for (let i = 0; i < lines.length; i++) {
90
+ const line = lines[i];
91
+ const trimmedLine = line.trim();
92
+ if (trimmedLine.startsWith(key + ":")) {
93
+ keyLineIndex = i;
94
+ startDeleteIndex = i;
95
+ endDeleteIndex = i;
96
+
97
+ // Look backwards for preceding comments (delete them too)
98
+ for (let j = i - 1; j >= 0; j--) {
99
+ const prevLine = lines[j].trim();
100
+ if (prevLine.startsWith("#")) {
101
+ startDeleteIndex = j;
102
+ } else if (prevLine !== "") {
103
+ break;
104
+ } else {
105
+ // Empty line - include it in deletion if followed by comments
106
+ if (startDeleteIndex < i) {
107
+ startDeleteIndex = j;
108
+ } else {
109
+ break;
110
+ }
111
+ }
112
+ }
113
+
114
+ // Determine the indentation of the key
115
+ const keyIndent = lines[i].match(/^(\s*)/)?.[1] || "";
116
+
117
+ // Look forwards to find all content belonging to this key
118
+ // This includes list items and nested objects
119
+ for (let j = i + 1; j < lines.length; j++) {
120
+ const nextLine = lines[j];
121
+ const trimmedNextLine = nextLine.trim();
122
+
123
+ // Empty line - continue looking
124
+ if (trimmedNextLine === "") {
125
+ continue;
126
+ }
127
+
128
+ // Comment at same or greater indentation - might be trailing comment
129
+ if (trimmedNextLine.startsWith("#")) {
130
+ const commentIndent = nextLine.match(/^(\s*)/)?.[1] || "";
131
+ if (commentIndent.length > keyIndent.length) {
132
+ endDeleteIndex = j;
133
+ continue;
134
+ } else {
135
+ // Comment at same or less indentation - not part of this key
136
+ break;
137
+ }
138
+ }
139
+
140
+ // Check indentation of next non-empty, non-comment line
141
+ const nextIndent = nextLine.match(/^(\s*)/)?.[1] || "";
142
+ if (nextIndent.length > keyIndent.length) {
143
+ // This line is indented more than the key, so it belongs to the key
144
+ endDeleteIndex = j;
145
+ } else {
146
+ // This line is at same or less indentation, so it's a new key
147
+ break;
148
+ }
149
+ }
150
+
151
+ break;
152
+ }
153
+ }
154
+
155
+ if (keyLineIndex !== -1) {
156
+ // Delete the lines from startDeleteIndex to endDeleteIndex (inclusive)
157
+ const beforeDelete = lines.slice(0, startDeleteIndex);
158
+ const afterDelete = lines.slice(endDeleteIndex + 1);
159
+
160
+ // Clean up excessive empty lines at the boundary
161
+ // Remove trailing empty lines from beforeDelete
162
+ while (
163
+ beforeDelete.length > 0 &&
164
+ beforeDelete[beforeDelete.length - 1].trim() === ""
165
+ ) {
166
+ beforeDelete.pop();
167
+ }
168
+
169
+ // Remove leading empty lines from afterDelete (but keep one if there's content after)
170
+ let leadingEmptyCount = 0;
171
+ for (const line of afterDelete) {
172
+ if (line.trim() === "") {
173
+ leadingEmptyCount++;
174
+ } else {
175
+ break;
176
+ }
177
+ }
178
+ const cleanedAfterDelete = afterDelete.slice(
179
+ Math.min(leadingEmptyCount, 1),
180
+ );
181
+
182
+ currentYaml = [...beforeDelete, ...cleanedAfterDelete].join("\n");
183
+ if (currentYaml && !currentYaml.endsWith("\n")) {
184
+ currentYaml += "\n";
185
+ }
186
+ }
187
+ // If key not found, do nothing
188
+ continue;
189
+ }
190
+
191
+ if (patch.op !== "set-key") continue;
192
+
193
+ const key = patch.path;
194
+
195
+ // Split the YAML into lines for easier processing
196
+ const lines = currentYaml.split("\n");
197
+ let keyLineIndex = -1;
198
+ let commentBlock = "";
199
+ let trailingComments = "";
200
+ let inlineComment = "";
201
+
202
+ // Find the key line and collect comments
203
+ for (let i = 0; i < lines.length; i++) {
204
+ const line = lines[i];
205
+ const trimmedLine = line.trim();
206
+ if (trimmedLine.startsWith(key + ":")) {
207
+ keyLineIndex = i;
208
+ // Extract inline comment if present
209
+ const commentMatch = line.match(/#.*$/);
210
+ if (commentMatch) {
211
+ inlineComment = commentMatch[0];
212
+ }
213
+ // Look backwards for comments
214
+ for (let j = i - 1; j >= 0; j--) {
215
+ const prevLine = lines[j].trim();
216
+ if (prevLine.startsWith("#")) {
217
+ commentBlock = lines[j] + "\n" + commentBlock;
218
+ } else if (prevLine !== "") {
219
+ break;
220
+ }
221
+ }
222
+ // Look forwards for comments
223
+ for (let j = i + 1; j < lines.length; j++) {
224
+ const nextLine = lines[j].trim();
225
+ if (nextLine.startsWith("#")) {
226
+ trailingComments += lines[j] + "\n";
227
+ } else if (nextLine !== "") {
228
+ break;
229
+ }
230
+ }
231
+ break;
232
+ }
233
+ }
234
+
235
+ // Serialize the new value
236
+ const serializedNewValue = serializeToYamlValue(patch.value);
237
+
238
+ // Create the replacement line
239
+ let replacementLine: string;
240
+ if (serializedNewValue.startsWith("\n")) {
241
+ // For lists/objects, the key line ends with just ':'
242
+ replacementLine = `${key}:${inlineComment}${serializedNewValue}`;
243
+ } else {
244
+ // For scalars, format as key: value
245
+ replacementLine = `${key}: ${serializedNewValue}${
246
+ inlineComment ? " " + inlineComment : ""
247
+ }`;
248
+ }
249
+
250
+ if (keyLineIndex !== -1) {
251
+ // Replace the existing line while preserving comments
252
+ const beforeKey = lines.slice(
253
+ 0,
254
+ keyLineIndex - commentBlock.split("\n").filter(Boolean).length,
255
+ );
256
+ const afterKey = lines.slice(
257
+ keyLineIndex + 1 + trailingComments.split("\n").filter(Boolean).length,
258
+ );
259
+
260
+ // Build the new content
261
+ const newContent = [
262
+ ...beforeKey,
263
+ ...commentBlock.split("\n").filter(Boolean),
264
+ replacementLine,
265
+ ...trailingComments.split("\n").filter(Boolean),
266
+ ...afterKey,
267
+ ];
268
+
269
+ // Join lines and ensure proper newlines
270
+ currentYaml = newContent.join("\n").replace(/\n*$/, "\n") + "\n";
271
+ } else {
272
+ // Key not found: Add the new key-value pair to the end
273
+ const newLineBlock = replacementLine;
274
+ if (currentYaml.trim() === "") {
275
+ currentYaml = newLineBlock + "\n";
276
+ } else {
277
+ currentYaml = currentYaml.replace(/\n*$/, "\n") + newLineBlock + "\n";
278
+ }
279
+ }
280
+ }
281
+
282
+ // Ensure the result ends with a newline
283
+ return currentYaml.replace(/\n*$/, "\n");
284
+ }
@@ -0,0 +1,15 @@
1
+ // declare global {
2
+ // function syscall(name: string, ...args: any[]): Promise<any>;
3
+ // }
4
+
5
+ // This is the case when running tests only, so giving it a dummy syscall function
6
+ if (typeof (globalThis as any).syscall === "undefined") {
7
+ (globalThis as any).syscall = () => {
8
+ throw new Error("Not implemented here");
9
+ };
10
+ }
11
+
12
+ // Late binding syscall
13
+ export function syscall(name: string, ...args: any[]): Promise<any> {
14
+ return (globalThis as any).syscall(name, ...args);
15
+ }
@@ -0,0 +1,36 @@
1
+ import { base64DecodeDataUrl } from "../lib/crypto.ts";
2
+ import { syscall } from "../syscall.ts";
3
+
4
+ import type { FileMeta } from "../../plug-api/types/index.ts";
5
+
6
+ /**
7
+ * Reads an asset embedded in a plug (via the `assets` field in the plug manifest).
8
+ * @param plugName name of the plug to read asset from
9
+ * @param name name of the asset to read
10
+ * @param encoding either "utf8" or "dataurl"
11
+ * @returns the content of the asset in the requested encoding
12
+ */
13
+ export async function readAsset(
14
+ plugName: string,
15
+ name: string,
16
+ encoding: "utf8" | "dataurl" = "utf8",
17
+ ): Promise<string> {
18
+ const dataUrl = await syscall("asset.readAsset", plugName, name) as string;
19
+ switch (encoding) {
20
+ case "utf8":
21
+ return new TextDecoder().decode(base64DecodeDataUrl(dataUrl));
22
+ case "dataurl":
23
+ return dataUrl;
24
+ }
25
+ }
26
+
27
+ export async function listFiles(plugName: string): Promise<FileMeta[]> {
28
+ return await syscall("asset.listFiles", plugName);
29
+ }
30
+
31
+ export async function getFileMeta(
32
+ plugName: string,
33
+ name: string,
34
+ ): Promise<FileMeta> {
35
+ return await syscall("asset.getFileMeta", plugName, name);
36
+ }
@@ -0,0 +1,33 @@
1
+ import { syscall } from "../syscall.ts";
2
+
3
+ /**
4
+ * Implements a very simple (string) key value store for the client.
5
+ * Generally should only be used to set some client-specific states, such as preferences.
6
+ * @module
7
+ */
8
+
9
+ /**
10
+ * Sets a value in the client store.
11
+ * @param key the key to set
12
+ * @param value the value to set
13
+ */
14
+ export function set(key: string, value: any): Promise<void> {
15
+ return syscall("clientStore.set", key, value);
16
+ }
17
+
18
+ /**
19
+ * Gets a value from the client store.
20
+ * @param key the key to get
21
+ * @returns the value associated with the key
22
+ */
23
+ export function get(key: string): Promise<any> {
24
+ return syscall("clientStore.get", key);
25
+ }
26
+
27
+ /**
28
+ * Deletes a value from the client store.
29
+ * @param key the key to delete
30
+ */
31
+ export function del(key: string): Promise<void> {
32
+ return syscall("clientStore.delete", key);
33
+ }
@@ -0,0 +1,8 @@
1
+ import { syscall } from "../syscall.ts";
2
+
3
+ /**
4
+ * Refreshes all code widgets on the page that support it.
5
+ */
6
+ export function refreshAll(): Promise<void> {
7
+ return syscall("codeWidget.refreshAll");
8
+ }
@@ -0,0 +1,58 @@
1
+ import { syscall } from "../syscall.ts";
2
+
3
+ /**
4
+ * Gets a config value by path, with support for dot notation.
5
+ * @param path The path to get the value from
6
+ * @param defaultValue The default value to return if the path doesn't exist
7
+ * @returns The value at the path, or the default value
8
+ */
9
+ export function get<T>(path: string | string[], defaultValue: T): Promise<T> {
10
+ return syscall("config.get", path, defaultValue);
11
+ }
12
+
13
+ /**
14
+ * Sets a config value by path, with support for dot notation.
15
+ * @param path The path to set the value at
16
+ * @param value The value to set
17
+ */
18
+ export function set<T>(path: string, value: T): Promise<void>;
19
+ /**
20
+ * Sets multiple config values at once.
21
+ * @param values An object containing key-value pairs to set
22
+ */
23
+ export function set(values: Record<string, any>): Promise<void>;
24
+ export function set<T>(
25
+ pathOrValues: string | Record<string, any>,
26
+ value?: T,
27
+ ): Promise<void> {
28
+ return syscall("config.set", pathOrValues, value);
29
+ }
30
+
31
+ /**
32
+ * Inserts a config value into an array
33
+ */
34
+ export function insert<T>(
35
+ path: string | string[],
36
+ value: T,
37
+ ): Promise<void> {
38
+ return syscall("config.insert", path, value);
39
+ }
40
+
41
+ /**
42
+ * Checks if a config path exists.
43
+ * @param path The path to check
44
+ * @returns True if the path exists, false otherwise
45
+ */
46
+ export function has(path: string): Promise<boolean> {
47
+ return syscall("config.has", path);
48
+ }
49
+
50
+ /**
51
+ * Defines a JSON schema for a configuration key.
52
+ * The schema will be used to validate values when setting this key.
53
+ * @param key The configuration key to define a schema for
54
+ * @param schema The JSON schema to validate against
55
+ */
56
+ export function define(key: string, schema: any): Promise<void> {
57
+ return syscall("config.define", key, schema);
58
+ }