diff --git a/common/syscalls/index.ts b/common/syscalls/index.ts index 4fa012db..87458c33 100644 --- a/common/syscalls/index.ts +++ b/common/syscalls/index.ts @@ -1,8 +1,3 @@ -import type { - KvQuery, - ObjectQuery, - ObjectValue, -} from "@silverbulletmd/silverbullet/types"; import type { SysCallMapping } from "$lib/plugos/system.ts"; import { findAllQueryVariables, @@ -21,61 +16,6 @@ import type { CommonSystem } from "$common/common_system.ts"; export function indexSyscalls(commonSystem: CommonSystem): SysCallMapping { return { - "index.indexObjects": (ctx, page: string, objects: ObjectValue[]) => { - return commonSystem.system.syscall(ctx, "system.invokeFunction", [ - "index.indexObjects", - page, - objects, - ]); - }, - "index.queryObjects": ( - ctx, - tag: string, - query: ObjectQuery, - ttlSecs?: number, - ) => { - return commonSystem.system.syscall(ctx, "system.invokeFunction", [ - "index.queryObjects", - tag, - query, - ttlSecs, - ]); - }, - "index.queryLuaObjects": ( - ctx, - tag: string, - query: LuaCollectionQuery, - scopedVariables?: Record, - ) => { - return commonSystem.system.syscall(ctx, "system.invokeFunction", [ - "index.queryLuaObjects", - tag, - query, - scopedVariables, - ]); - }, - "index.queryDeleteObjects": (ctx, tag: string, query: ObjectQuery) => { - return commonSystem.system.syscall(ctx, "system.invokeFunction", [ - "index.queryDeleteObjects", - tag, - query, - ]); - }, - "index.query": (ctx, query: KvQuery, variables?: Record) => { - return commonSystem.system.syscall(ctx, "system.invokeFunction", [ - "index.query", - query, - variables, - ]); - }, - "index.getObjectByRef": (ctx, page: string, tag: string, ref: string) => { - return commonSystem.system.syscall(ctx, "system.invokeFunction", [ - "index.getObjectByRef", - page, - tag, - ref, - ]); - }, "index.tag": (_ctx, tagName: string): LuaQueryCollection => { return { query: async ( diff --git a/lib/manifest.ts b/lib/manifest.ts index d57036fd..5d34c3f2 100644 --- a/lib/manifest.ts +++ b/lib/manifest.ts @@ -62,6 +62,10 @@ export type SlashCommandHookT = { slashCommand?: SlashCommandDef; }; +export type SyscallHookT = { + syscall?: string; +}; + /** Silverbullet hooks give plugs access to silverbullet core systems. * * Hooks are associated with typescript functions through a manifest file. @@ -78,7 +82,8 @@ export type SilverBulletHooks = & CodeWidgetT & PanelWidgetT & EndpointHookT - & PlugNamespaceHookT; + & PlugNamespaceHookT + & SyscallHookT; /** A plug manifest configures hooks, declares syntax extensions, and describes plug metadata. * diff --git a/plugs/index/index.plug.yaml b/plugs/index/index.plug.yaml index 3acf3cd6..39e0ee8c 100644 --- a/plugs/index/index.plug.yaml +++ b/plugs/index/index.plug.yaml @@ -6,17 +6,22 @@ functions: env: server query: path: api.ts:query + syscall: index.query indexObjects: path: api.ts:indexObjects + syscall: index.indexObjects env: server queryObjects: path: api.ts:queryObjects + syscall: index.queryObjects # Note: not setting env: server to allow for client-side datastore query caching queryLuaObjects: path: api.ts:queryLuaObjects + syscall: index.queryLuaObjects # Note: not setting env: server to allow for client-side datastore query caching getObjectByRef: path: api.ts:getObjectByRef + syscall: index.getObjectByRef env: server objectSourceProvider: path: api.ts:objectSourceProvider diff --git a/server/server_system.ts b/server/server_system.ts index 0d6c32c5..de99102c 100644 --- a/server/server_system.ts +++ b/server/server_system.ts @@ -37,6 +37,7 @@ import { ensureSpaceIndex } from "$common/space_index.ts"; import type { FileMeta } from "../plug-api/types.ts"; import { CommandHook } from "$common/hooks/command.ts"; import { CommonSystem } from "$common/common_system.ts"; +import { SyscallHook } from "../web/hooks/syscall.ts"; import type { DataStoreMQ } from "$lib/data/mq.datastore.ts"; import { plugPrefix } from "$common/spaces/constants.ts"; import { base64EncodedDataUrl } from "$lib/crypto.ts"; @@ -100,6 +101,9 @@ export class ServerSystem extends CommonSystem { this.system.addHook(new MQHook(this.system, this.mq)); + // Syscall hook + this.system.addHook(new SyscallHook()); + const codeWidgetHook = new CodeWidgetHook(); this.system.addHook(codeWidgetHook); diff --git a/web/client_system.ts b/web/client_system.ts index d71b07fd..53c0f1a0 100644 --- a/web/client_system.ts +++ b/web/client_system.ts @@ -11,6 +11,7 @@ import type { Client } from "./client.ts"; import { CodeWidgetHook } from "./hooks/code_widget.ts"; import { CommandHook } from "$common/hooks/command.ts"; import { SlashCommandHook } from "./hooks/slash_command.ts"; +import { SyscallHook } from "./hooks/syscall.ts"; import { clientStoreSyscalls } from "./syscalls/clientStore.ts"; import { debugSyscalls } from "./syscalls/debug.ts"; import { editorSyscalls } from "./syscalls/editor.ts"; @@ -128,6 +129,9 @@ export class ClientSystem extends CommonSystem { this.slashCommandHook = new SlashCommandHook(this.client, this); this.system.addHook(this.slashCommandHook); + // Syscall hook + this.system.addHook(new SyscallHook()); + this.eventHook.addLocalListener( "file:changed", async (path: string, _selfUpdate, _oldHash, newHash) => { diff --git a/web/hooks/syscall.ts b/web/hooks/syscall.ts new file mode 100644 index 00000000..bf22d58a --- /dev/null +++ b/web/hooks/syscall.ts @@ -0,0 +1,68 @@ +import type { Hook, Manifest } from "$lib/plugos/types.ts"; +import type { System } from "$lib/plugos/system.ts"; +import type { SyscallHookT } from "$lib/manifest.ts"; +import type { SysCallMapping } from "$lib/plugos/system.ts"; + +export class SyscallHook implements Hook { + apply(system: System): void { + this.registerSyscalls(system); + system.on({ + plugLoaded: () => { + this.registerSyscalls(system); + }, + }); + } + + registerSyscalls(system: System) { + // Register syscalls from all loaded plugs + for (const plug of system.loadedPlugs.values()) { + const syscalls: SysCallMapping = {}; + + for ( + const [name, functionDef] of Object.entries(plug.manifest!.functions) + ) { + if (!functionDef.syscall) { + continue; + } + + const syscallName = functionDef.syscall; + + console.log("Registering plug syscall", syscallName, "for", name); + // Add the syscall to our mapping + syscalls[syscallName] = (ctx, ...args) => { + // Delegate to the system to invoke the function + return system.syscall(ctx, "system.invokeFunction", [ + name, + ...args, + ]); + }; + + // Register the syscalls with no required permissions + system.registerSyscalls([], syscalls); + } + } + } + + validateManifest(manifest: Manifest): string[] { + const errors: string[] = []; + for (const [name, functionDef] of Object.entries(manifest.functions)) { + if (!functionDef.syscall) { + continue; + } + + // Validate syscall name is provided + if (!functionDef.syscall) { + errors.push(`Function ${name} has a syscall but no name`); + continue; + } + + // Validate syscall name format (should be namespaced) + if (!functionDef.syscall.includes(".")) { + errors.push( + `Function ${name} has invalid syscall name "${functionDef.syscall}" - must be in format "namespace.name"`, + ); + } + } + return errors; + } +}