Support plugs to register their own syscalls, using `syscall` (see the index plug for an example)

pull/1232/head
Zef Hemel 2025-02-07 15:16:57 +01:00
parent 9f8ac92464
commit 44a1ce698e
6 changed files with 87 additions and 61 deletions

View File

@ -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<any>[]) => {
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<string, any>,
) => {
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<string, any>) => {
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 (

View File

@ -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.
*

View File

@ -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

View File

@ -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);

View File

@ -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) => {

68
web/hooks/syscall.ts Normal file
View File

@ -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<SyscallHookT> {
apply(system: System<SyscallHookT>): void {
this.registerSyscalls(system);
system.on({
plugLoaded: () => {
this.registerSyscalls(system);
},
});
}
registerSyscalls(system: System<SyscallHookT>) {
// 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<SyscallHookT>): 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;
}
}