@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,160 +1,306 @@
1
- import type { SysCallMapping } from "../system.ts";
2
- import type {
3
- LuaCollectionQuery,
4
- LuaQueryCollection,
5
- } from "../../space_lua/query_collection.ts";
1
+ import type { ObjectValue } from "@silverbulletmd/silverbullet/type/index";
2
+ import type { Client } from "../../client.ts";
6
3
 
7
4
  import {
8
5
  type ObjectIndex,
9
6
  ObjectValidationError,
10
7
  } from "../../data/object_index.ts";
11
- import type { ObjectValue } from "@silverbulletmd/silverbullet/type/index";
12
- import type { Client } from "../../client.ts";
8
+ import type {
9
+ LuaCollectionQuery,
10
+ LuaQueryCollection,
11
+ } from "../../space_lua/query_collection.ts";
13
12
  import type { LuaTable } from "../../space_lua/runtime.ts";
13
+ import type { SysCallMapping } from "../system.ts";
14
+ import { describeSchemas, tagSchema } from "./schema_introspection.ts";
14
15
 
15
16
  export function indexSyscalls(
16
17
  objectIndex: ObjectIndex,
17
18
  client: Client,
18
19
  ): SysCallMapping {
19
20
  return {
20
- // Query collection API
21
- "index.tag": (_ctx, tagName: string): LuaQueryCollection => {
22
- return objectIndex.objectsWithTag(tagName);
21
+ "index.tag": {
22
+ callback: (_ctx, tagName: string): LuaQueryCollection => {
23
+ return objectIndex.objectsWithTag(tagName);
24
+ },
25
+ description: "Returns objects carrying a tag as a query collection.",
26
+ signatures: ["index.tag(tagName)"],
23
27
  },
24
28
  // Alias for tag
25
- "index.objects": (_ctx, tagName: string): LuaQueryCollection => {
26
- return objectIndex.objectsWithTag(tagName);
29
+ "index.objects": {
30
+ callback: (_ctx, tagName: string): LuaQueryCollection => {
31
+ return objectIndex.objectsWithTag(tagName);
32
+ },
33
+ description: "Returns objects carrying a tag as a query collection.",
34
+ signatures: ["index.objects(tagName)"],
35
+ },
36
+ "index.pages": {
37
+ callback: (_ctx, tagName?: string): LuaQueryCollection => {
38
+ return objectIndex.rootTaggedObjects("page", tagName);
39
+ },
40
+ description:
41
+ "Returns all pages, optionally filtered by an additional tag, as a query collection.",
42
+ signatures: ["index.pages(tagName?)"],
43
+ },
44
+ "index.subPages": {
45
+ callback: (_ctx, pageName: string): LuaQueryCollection => {
46
+ return objectIndex.subPages(pageName);
47
+ },
48
+ description:
49
+ "Returns pages nested below a page name as a query collection.",
50
+ signatures: ["index.subPages(pageName)"],
27
51
  },
28
- "index.pages": (_ctx, tagName?: string): LuaQueryCollection => {
29
- return objectIndex.rootTaggedObjects("page", tagName);
52
+ "index.documents": {
53
+ callback: (): LuaQueryCollection => {
54
+ return objectIndex.objectsWithTag("document");
55
+ },
56
+ description: "Returns all indexed documents as a query collection.",
30
57
  },
31
- "index.subPages": (_ctx, pageName: string): LuaQueryCollection => {
32
- return objectIndex.subPages(pageName);
58
+ "index.links": {
59
+ callback: (): LuaQueryCollection => {
60
+ return objectIndex.objectsWithTag("link");
61
+ },
62
+ description: "Returns all indexed links as a query collection.",
33
63
  },
34
- "index.documents": (): LuaQueryCollection => {
35
- return objectIndex.objectsWithTag("document");
64
+ "index.relations": {
65
+ callback: (_ctx, kind?: string): LuaQueryCollection => {
66
+ return objectIndex.relations(kind);
67
+ },
68
+ description:
69
+ "Returns all indexed relations, optionally filtered by kind, as a query collection.",
70
+ signatures: ["index.relations(kind?)"],
36
71
  },
37
- "index.links": (): LuaQueryCollection => {
38
- return objectIndex.objectsWithTag("link");
72
+ "index.contentPages": {
73
+ callback: (_ctx, tagName?: string): LuaQueryCollection => {
74
+ return objectIndex.contentPages(tagName);
75
+ },
76
+ description:
77
+ "Returns non-meta pages, optionally filtered by an additional tag, as a query collection.",
78
+ signatures: ["index.contentPages(tagName?)"],
39
79
  },
40
- "index.relations": (): LuaQueryCollection => {
41
- return objectIndex.objectsWithTag("relation");
80
+ "index.metaPages": {
81
+ callback: (): LuaQueryCollection => {
82
+ return objectIndex.metaPages();
83
+ },
84
+ description: "Returns all meta pages as a query collection.",
42
85
  },
43
- "index.contentPages": (_ctx, tagName?: string): LuaQueryCollection => {
44
- return objectIndex.contentPages(tagName);
86
+ "index.tasks": {
87
+ callback: (_ctx, tagName?: string): LuaQueryCollection => {
88
+ return objectIndex.rootTaggedObjects("task", tagName);
89
+ },
90
+ description:
91
+ "Returns all tasks, optionally filtered by an additional tag, as a query collection.",
92
+ signatures: ["index.tasks(tagName?)"],
45
93
  },
46
- "index.metaPages": (): LuaQueryCollection => {
47
- return objectIndex.metaPages();
94
+ "index.headers": {
95
+ callback: (_ctx, tagName?: string): LuaQueryCollection => {
96
+ return objectIndex.rootTaggedObjects("header", tagName);
97
+ },
98
+ description:
99
+ "Returns all headers, optionally filtered by an additional tag, as a query collection.",
100
+ signatures: ["index.headers(tagName?)"],
48
101
  },
49
- "index.tasks": (_ctx, tagName?: string): LuaQueryCollection => {
50
- return objectIndex.rootTaggedObjects("task", tagName);
102
+ "index.items": {
103
+ callback: (_ctx, tagName?: string): LuaQueryCollection => {
104
+ return objectIndex.rootTaggedObjects("item", tagName);
105
+ },
106
+ description:
107
+ "Returns all list items, optionally filtered by an additional tag, as a query collection.",
108
+ signatures: ["index.items(tagName?)"],
51
109
  },
52
- "index.headers": (_ctx, tagName?: string): LuaQueryCollection => {
53
- return objectIndex.rootTaggedObjects("header", tagName);
110
+ "index.paragraphs": {
111
+ callback: (_ctx, tagName?: string): LuaQueryCollection => {
112
+ return objectIndex.rootTaggedObjects("paragraph", tagName);
113
+ },
114
+ description:
115
+ "Returns indexed paragraphs, optionally filtered by an additional tag, as a query collection.",
116
+ signatures: ["index.paragraphs(tagName?)"],
54
117
  },
55
- "index.items": (_ctx, tagName?: string): LuaQueryCollection => {
56
- return objectIndex.rootTaggedObjects("item", tagName);
118
+ "index.tables": {
119
+ callback: (_ctx, tagName?: string): LuaQueryCollection => {
120
+ return objectIndex.rootTaggedObjects("table", tagName);
121
+ },
122
+ description:
123
+ "Returns indexed table rows, optionally filtered by an additional tag, as a query collection.",
124
+ signatures: ["index.tables(tagName?)"],
57
125
  },
58
- "index.paragraphs": (_ctx, tagName?: string): LuaQueryCollection => {
59
- return objectIndex.rootTaggedObjects("paragraph", tagName);
126
+ "index.tags": {
127
+ callback: (): LuaQueryCollection => {
128
+ return objectIndex.objectsWithTag("tag");
129
+ },
130
+ description: "Returns all indexed tag objects as a query collection.",
60
131
  },
61
- "index.tables": (_ctx, tagName?: string): LuaQueryCollection => {
62
- return objectIndex.rootTaggedObjects("table", tagName);
132
+ "index.describeSchema": {
133
+ callback: (): Record<string, unknown> => {
134
+ return describeSchemas(client.config.get(["tags"], {}));
135
+ },
136
+ description:
137
+ "Returns raw JSON Schemas for every configured tag that declares one.",
63
138
  },
64
- "index.tags": (): LuaQueryCollection => {
65
- return objectIndex.objectsWithTag("tag");
139
+ "index.tagSchema": {
140
+ callback: (_ctx, tagName: string): unknown | null => {
141
+ return tagSchema(client.config.get(["tags"], {}), tagName);
142
+ },
143
+ description:
144
+ "Returns the raw JSON Schema for a tag, or nil when none is declared.",
145
+ signatures: ["index.tagSchema(tagName)"],
66
146
  },
67
- "index.aspiringPages": (): LuaQueryCollection => {
68
- return objectIndex.aspiringPages();
147
+ "index.isAvailable": {
148
+ callback: (): Promise<boolean> => objectIndex.isIndexAvailable(),
149
+ description: `Whether a full indexing pass has ever completed for this space. False on a fresh client, and for as long as the first index takes on a large one: every object query answers with whatever has been indexed so far, which is nothing to begin with. Code that must work in that window reads the space directly instead.`,
150
+ returns: [
151
+ { type: "boolean", description: "Whether the index can be trusted." },
152
+ ],
69
153
  },
70
- // Internals
71
- "index.aggregates": (): LuaQueryCollection => {
72
- return objectIndex.aggregates();
154
+ "index.aspiringPages": {
155
+ callback: (): LuaQueryCollection => {
156
+ return objectIndex.aspiringPages();
157
+ },
158
+ description:
159
+ "Returns linked but not yet created pages as a query collection.",
73
160
  },
74
- "index.ensureFullIndex": () => {
75
- return objectIndex.ensureFullIndex(client.space);
161
+ "index.ambiguousLinks": {
162
+ callback: (): LuaQueryCollection => {
163
+ return objectIndex.ambiguousLinks();
164
+ },
165
+ description:
166
+ "Returns links whose page name matches more than one page as a query collection.",
76
167
  },
77
- "index.reindexSpace": () => {
78
- return objectIndex.reindexSpace(client.space);
168
+ "index.aggregates": {
169
+ callback: (): LuaQueryCollection => {
170
+ return objectIndex.aggregates();
171
+ },
172
+ description: "Returns stored aggregate records as a query collection.",
79
173
  },
80
- "index.indexObjects": (
81
- _ctx,
82
- page: string,
83
- objects: ObjectValue[],
84
- ): Promise<void> => {
85
- return objectIndex.indexObjects(page, objects);
174
+ "index.ensureFullIndex": {
175
+ callback: () => {
176
+ return objectIndex.ensureFullIndex(client.space);
177
+ },
178
+ description:
179
+ "Ensures the complete object index is available and current.",
86
180
  },
87
- "index.validateObjects": async (
88
- _ctx,
89
- page: string,
90
- objects: ObjectValue[],
91
- ): Promise<{ error: string; object: ObjectValue } | null> => {
92
- try {
93
- await objectIndex.validateObjects(page, objects);
94
- return null;
95
- } catch (e: any) {
96
- if (e instanceof ObjectValidationError) {
97
- return {
98
- error: e.message,
99
- object: e.object,
100
- };
101
- } else {
102
- throw e;
181
+ "index.reindexSpace": {
182
+ callback: () => {
183
+ return objectIndex.reindexSpace(client.space);
184
+ },
185
+ description: "Rebuilds the object index for the entire space.",
186
+ },
187
+ "index.indexObjects": {
188
+ callback: (_ctx, page: string, objects: ObjectValue[]): Promise<void> => {
189
+ return objectIndex.indexObjects(page, objects);
190
+ },
191
+ description: "Indexes a collection of objects for a page.",
192
+ signatures: ["index.indexObjects(page, objects)"],
193
+ },
194
+ "index.validateObjects": {
195
+ callback: async (
196
+ _ctx,
197
+ page: string,
198
+ objects: ObjectValue[],
199
+ ): Promise<{ error: string; object: ObjectValue } | null> => {
200
+ try {
201
+ await objectIndex.validateObjects(page, objects);
202
+ return null;
203
+ } catch (e: any) {
204
+ if (e instanceof ObjectValidationError) {
205
+ return {
206
+ error: e.message,
207
+ object: e.object,
208
+ };
209
+ } else {
210
+ throw e;
211
+ }
103
212
  }
104
- }
105
- },
106
- "index.previewProcessedObjects": (
107
- _ctx,
108
- page: string,
109
- objects: ObjectValue[],
110
- ): Promise<{ tag: string; object: ObjectValue }[]> => {
111
- return objectIndex.previewProcessedObjects(page, objects);
112
- },
113
- "index.getObjectByRef": (
114
- _ctx,
115
- page: string,
116
- tag: string,
117
- ref: string,
118
- ): Promise<ObjectValue | undefined> => {
119
- return objectIndex.getObjectByRef(page, tag, ref);
120
- },
121
- "index.queryLuaObjects": (
122
- _ctx,
123
- tag: string,
124
- query: LuaCollectionQuery,
125
- scopedVariables?: Record<string, any>,
126
- ): Promise<ObjectValue[]> => {
127
- return objectIndex.queryLuaObjects(
128
- client.clientSystem.spaceLuaEnv.env,
129
- tag,
130
- query,
131
- scopedVariables,
132
- );
213
+ },
214
+ description:
215
+ "Validates objects for a page and returns the first validation error, if any.",
216
+ signatures: ["index.validateObjects(page, objects)"],
217
+ },
218
+ "index.previewProcessedObjects": {
219
+ callback: (
220
+ _ctx,
221
+ page: string,
222
+ objects: ObjectValue[],
223
+ ): Promise<{ tag: string; object: ObjectValue }[]> => {
224
+ return objectIndex.previewProcessedObjects(page, objects);
225
+ },
226
+ description:
227
+ "Runs the indexing pipeline without writing and returns processed tag/object pairs.",
228
+ signatures: ["index.previewProcessedObjects(page, objects)"],
229
+ },
230
+ "index.getObjectByRef": {
231
+ callback: (
232
+ _ctx,
233
+ page: string,
234
+ tag: string,
235
+ ref: string,
236
+ ): Promise<ObjectValue | undefined> => {
237
+ return objectIndex.getObjectByRef(page, tag, ref);
238
+ },
239
+ description:
240
+ "Returns an indexed object identified by page, tag, and reference.",
241
+ signatures: ["index.getObjectByRef(page, tag, ref)"],
242
+ },
243
+ "index.queryLuaObjects": {
244
+ callback: (
245
+ _ctx,
246
+ tag: string,
247
+ query: LuaCollectionQuery,
248
+ scopedVariables?: Record<string, any>,
249
+ ): Promise<ObjectValue[]> => {
250
+ return objectIndex.queryLuaObjects(
251
+ client.clientSystem.spaceLuaEnv.env,
252
+ tag,
253
+ query,
254
+ scopedVariables,
255
+ );
256
+ },
257
+ description:
258
+ "Executes a structured Lua collection query against indexed objects.",
259
+ signatures: ["index.queryLuaObjects(tag, query, scopedVariables?)"],
133
260
  },
134
261
 
135
- "index.deleteObject": (
136
- _ctx,
137
- page: string,
138
- tag: string,
139
- ref: string,
140
- ): Promise<void> => {
141
- return objectIndex.deleteObject(page, tag, ref);
142
- },
143
- "lua:index.defineTag": (_ctx, tagDef: LuaTable) => {
144
- // Using 'lua:' prefix to _not_ convert tagDef to a JS version (but keep original LuaTable)
145
- if (!tagDef.has("name")) {
146
- throw new Error("A tag name is required");
147
- }
148
- const currentTag = client.config.get(["tags", tagDef.get("name")], null);
149
- if (!currentTag) {
150
- client.config.set(["tags", tagDef.get("name")], {
151
- name: tagDef.get("name"),
152
- });
153
- }
154
- client.config.set(
155
- ["tags", tagDef.get("name"), "metatable"],
156
- tagDef.get("metatable"),
157
- );
262
+ "index.clearFileIndex": {
263
+ callback: (_ctx, path: string): Promise<void> =>
264
+ objectIndex.clearFileIndex(path),
265
+ description:
266
+ "Drops every indexed object belonging to a file, so a re-index replaces them rather than merging on top.",
267
+ signatures: ["index.clearFileIndex(path)"],
268
+ },
269
+ "index.deleteObject": {
270
+ callback: (
271
+ _ctx,
272
+ page: string,
273
+ tag: string,
274
+ ref: string,
275
+ ): Promise<void> => {
276
+ return objectIndex.deleteObject(page, tag, ref);
277
+ },
278
+ description:
279
+ "Deletes an indexed object identified by page, tag, and reference.",
280
+ signatures: ["index.deleteObject(page, tag, ref)"],
281
+ },
282
+ "lua:index.defineTag": {
283
+ callback: (_ctx, tagDef: LuaTable) => {
284
+ // Using 'lua:' prefix to _not_ convert tagDef to a JS version (but keep original LuaTable)
285
+ if (!tagDef.has("name")) {
286
+ throw new Error("A tag name is required");
287
+ }
288
+ const currentTag = client.config.get(
289
+ ["tags", tagDef.get("name")],
290
+ null,
291
+ );
292
+ if (!currentTag) {
293
+ client.config.set(["tags", tagDef.get("name")], {
294
+ name: tagDef.get("name"),
295
+ });
296
+ }
297
+ client.config.set(
298
+ ["tags", tagDef.get("name"), "metatable"],
299
+ tagDef.get("metatable"),
300
+ );
301
+ },
302
+ description: "Defines or updates a tag and its Lua metatable.",
303
+ signatures: ["index.defineTag(tagDefinition)"],
158
304
  },
159
305
  };
160
306
  }
@@ -2,7 +2,6 @@ import type { SysCallMapping } from "../system.ts";
2
2
  import { type OutputUnit, Validator, format } from "@cfworker/json-schema";
3
3
  import { stripFunctions } from "../util.ts";
4
4
 
5
- // Register custom formats
6
5
  format.email = (data: string) => data.includes("@");
7
6
  format["page-ref"] = (data: string) =>
8
7
  data.startsWith("[[") && data.endsWith("]]");
@@ -75,17 +74,116 @@ export function validateSchema(schema: any): undefined | string {
75
74
  return;
76
75
  }
77
76
 
77
+ /**
78
+ * Best-effort: infer a JSON Schema (draft 2020-12) from the *shape* of a single
79
+ * sample value. Types are guessed from one example, so the result is a hint,
80
+ * not a contract — the top-level schema is marked `"x-inferred": true`.
81
+ *
82
+ * Useful when a tag/object type has no declared schema but an example object
83
+ * exists and you want a plausible schema for it.
84
+ */
85
+ export function inferFromObject(value: any): any {
86
+ const schema = inferSchemaNode(value);
87
+ schema["$schema"] = "https://json-schema.org/draft/2020-12/schema";
88
+ schema["x-inferred"] = true;
89
+ return schema;
90
+ }
91
+
92
+ /** Infer a bare JSON Schema node for a value, recursing into arrays/objects. */
93
+ function inferSchemaNode(value: any): any {
94
+ if (value === null || value === undefined) {
95
+ return { type: "null" };
96
+ }
97
+ if (Array.isArray(value)) {
98
+ const node: any = { type: "array" };
99
+ if (value.length > 0) {
100
+ node.items = inferSchemaNode(value[0]);
101
+ }
102
+ return node;
103
+ }
104
+ switch (typeof value) {
105
+ case "boolean":
106
+ return { type: "boolean" };
107
+ case "number":
108
+ return { type: Number.isInteger(value) ? "integer" : "number" };
109
+ case "string":
110
+ return { type: "string" };
111
+ case "object": {
112
+ const properties: Record<string, any> = {};
113
+ for (const [k, v] of Object.entries(value)) {
114
+ properties[k] = inferSchemaNode(v);
115
+ }
116
+ return { type: "object", properties };
117
+ }
118
+ default:
119
+ // functions, symbols, bigint, … — not representable in JSON Schema.
120
+ return {};
121
+ }
122
+ }
123
+
78
124
  export function jsonschemaSyscalls(): SysCallMapping {
79
125
  return {
80
- "jsonschema.validateObject": (
81
- _ctx,
82
- schema: any,
83
- object: any,
84
- ): undefined | string => {
85
- return validateObject(schema, object);
126
+ "jsonschema.validateObject": {
127
+ callback: (_ctx, schema: any, object: any): undefined | string => {
128
+ return validateObject(schema, object);
129
+ },
130
+ description: "Validates a value against a JSON Schema.",
131
+ parameters: [
132
+ {
133
+ name: "schema",
134
+ type: "table",
135
+ description: "JSON Schema to apply.",
136
+ },
137
+ { name: "object", description: "Value to validate." },
138
+ ],
139
+ returns: [
140
+ {
141
+ type: "string",
142
+ description: "Validation error, or nil when valid.",
143
+ },
144
+ ],
145
+ examples: [
146
+ {
147
+ code: 'local schema = {type = "object", properties = {name = {type = "string"}}, required = {"name"}}\nlocal err = jsonschema.validateObject(schema, {name = "John"})',
148
+ },
149
+ ],
150
+ },
151
+ "jsonschema.validateSchema": {
152
+ callback: (_ctx, schema: any): undefined | string => {
153
+ return validateSchema(schema);
154
+ },
155
+ description:
156
+ "Checks whether a JSON Schema has a supported top-level shape.",
157
+ parameters: [{ name: "schema", description: "JSON Schema to validate." }],
158
+ returns: [
159
+ { type: "string", description: "Schema error, or nil when valid." },
160
+ ],
161
+ examples: [
162
+ {
163
+ code: 'local err = jsonschema.validateSchema({type = "object"})',
164
+ },
165
+ ],
86
166
  },
87
- "jsonschema.validateSchema": (_ctx, schema: any): undefined | string => {
88
- return validateSchema(schema);
167
+ "jsonschema.inferFromObject": {
168
+ callback: (_ctx, object: any): any => {
169
+ return inferFromObject(object);
170
+ },
171
+ description:
172
+ "Infers a best-effort draft 2020-12 JSON Schema from a sample value.",
173
+ parameters: [
174
+ {
175
+ name: "object",
176
+ description: "Sample value whose shape is inferred.",
177
+ },
178
+ ],
179
+ returns: [
180
+ { type: "table", description: "Inferred schema marked x-inferred." },
181
+ ],
182
+ examples: [
183
+ {
184
+ code: 'local schema = jsonschema.inferFromObject({name = "Widget", count = 3})',
185
+ },
186
+ ],
89
187
  },
90
188
  };
91
189
  }
@@ -9,19 +9,45 @@ import {
9
9
 
10
10
  export function languageSyscalls(): SysCallMapping {
11
11
  return {
12
- "language.parseLanguage": async (
13
- _ctx,
14
- language: string,
15
- code: string,
16
- ): Promise<ParseTree> => {
17
- const lang = languageFor(language) ?? await loadLanguageFor(language);
18
- if (!lang) {
19
- throw new Error(`Unknown language ${language}`);
20
- }
21
- return parse(lang, code);
12
+ "language.parseLanguage": {
13
+ callback: async (
14
+ _ctx,
15
+ language: string,
16
+ code: string,
17
+ ): Promise<ParseTree> => {
18
+ const lang = languageFor(language) ?? (await loadLanguageFor(language));
19
+ if (!lang) {
20
+ throw new Error(`Unknown language ${language}`);
21
+ }
22
+ return parse(lang, code);
23
+ },
24
+ description: "Parses code using a supported fenced-code-block language.",
25
+ parameters: [
26
+ {
27
+ name: "language",
28
+ type: "string",
29
+ description: "Language name or alias.",
30
+ },
31
+ {
32
+ name: "code",
33
+ type: "string",
34
+ description: "Source code to parse.",
35
+ },
36
+ ],
37
+ returns: [{ type: "table", description: "Parsed syntax tree." }],
38
+ examples: [
39
+ {
40
+ code: 'local tree = language.parseLanguage("javascript", "const answer = 42")',
41
+ },
42
+ ],
22
43
  },
23
- "language.listLanguages": (): string[] => {
24
- return allLanguageNames;
44
+ "language.listLanguages": {
45
+ callback: (): string[] => {
46
+ return allLanguageNames;
47
+ },
48
+ description: "Lists all supported fenced-code-block languages.",
49
+ returns: [{ type: "table", description: "Supported language names." }],
50
+ examples: [{ code: "local languages = language.listLanguages()" }],
25
51
  },
26
52
  };
27
53
  }