@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,139 @@
1
+ import { parseToRef, type Ref } from "@silverbulletmd/silverbullet/lib/ref";
2
+ import type { Client } from "../../client.ts";
3
+ import type { SysCallMapping } from "../system.ts";
4
+
5
+ import type {
6
+ DocumentMeta,
7
+ FileMeta,
8
+ PageMeta,
9
+ } from "@silverbulletmd/silverbullet/type/index";
10
+
11
+ export function spaceReadSyscalls(client: Client): SysCallMapping {
12
+ return {
13
+ "space.listPages": (): Promise<PageMeta[]> => {
14
+ return client.space.fetchPageList();
15
+ },
16
+ "space.readPage": async (_ctx, name: string): Promise<string> => {
17
+ return (await client.space.readPage(name)).text;
18
+ },
19
+ "space.readPageWithMeta": (
20
+ _ctx,
21
+ name: string,
22
+ ): Promise<{ text: string; meta: PageMeta }> => {
23
+ return client.space.readPage(name);
24
+ },
25
+ "space.readRef": async (_ctx, ref: string | Ref): Promise<string> => {
26
+ if (typeof ref === "string") {
27
+ ref = parseToRef(ref)!;
28
+ if (!ref) {
29
+ throw new Error(`Invalid ref: ${ref}`);
30
+ }
31
+ }
32
+ return (await client.space.readRef(ref)).text;
33
+ },
34
+ "space.pageExists": (_ctx, name: string): boolean => {
35
+ return client.clientSystem.allKnownFiles.has(name + ".md");
36
+ },
37
+ "space.getPageMeta": (_ctx, name: string): Promise<PageMeta> => {
38
+ return client.space.getPageMeta(name);
39
+ },
40
+ "space.listPlugs": (): Promise<FileMeta[]> => {
41
+ return client.space.listPlugs();
42
+ },
43
+ "space.listDocuments": async (): Promise<DocumentMeta[]> => {
44
+ return await client.space.fetchDocumentList();
45
+ },
46
+ "space.readDocument": async (_ctx, name: string): Promise<Uint8Array> => {
47
+ return (await client.space.readDocument(name)).data;
48
+ },
49
+ "space.getDocumentMeta": async (
50
+ _ctx,
51
+ name: string,
52
+ ): Promise<DocumentMeta> => {
53
+ return await client.space.getDocumentMeta(name);
54
+ },
55
+ // DEPRECATED, please use document versions instead, left here for backwards compatibility
56
+ "space.listAttachments": async (): Promise<DocumentMeta[]> => {
57
+ return await client.space.fetchDocumentList();
58
+ },
59
+ "space.readAttachment": async (_ctx, name: string): Promise<Uint8Array> => {
60
+ return (await client.space.readDocument(name)).data;
61
+ },
62
+ "space.getAttachmentMeta": async (
63
+ _ctx,
64
+ name: string,
65
+ ): Promise<DocumentMeta> => {
66
+ return await client.space.getDocumentMeta(name);
67
+ },
68
+ // FS
69
+ "space.listFiles": (): Promise<FileMeta[]> => {
70
+ return client.space.spacePrimitives.fetchFileList();
71
+ },
72
+ "space.getFileMeta": (_ctx, name: string): Promise<FileMeta> => {
73
+ return client.space.spacePrimitives.getFileMeta(name);
74
+ },
75
+ "space.readFile": async (_ctx, name: string): Promise<Uint8Array> => {
76
+ return (await client.space.spacePrimitives.readFile(name)).data;
77
+ },
78
+ "space.readFileWithMeta": async (
79
+ _ctx,
80
+ name: string,
81
+ ): Promise<{ data: Uint8Array; meta: FileMeta }> => {
82
+ return await client.space.spacePrimitives.readFile(name);
83
+ },
84
+ "space.fileExists": async (_ctx, name: string): Promise<boolean> => {
85
+ // If a full sync has successfully completed (so we know what files exist)
86
+ // and we have a snapshot, let's use the snapshot
87
+ if (
88
+ client.fullSyncCompleted &&
89
+ !client.eventedSpacePrimitives.isSnapshotEmpty()
90
+ ) {
91
+ return !!client.eventedSpacePrimitives.getSnapshot()[name];
92
+ }
93
+ try {
94
+ await client.space.spacePrimitives.getFileMeta(name);
95
+ // If this returned the file exists
96
+ return true;
97
+ } catch {
98
+ // Assumption: any error means the file does not exist
99
+ return false;
100
+ }
101
+ },
102
+ };
103
+ }
104
+
105
+ export function spaceWriteSyscalls(editor: Client): SysCallMapping {
106
+ return {
107
+ "space.writePage": (
108
+ _ctx,
109
+ name: string,
110
+ text: string,
111
+ ): Promise<PageMeta> => {
112
+ return editor.space.writePage(name, text);
113
+ },
114
+ "space.deletePage": async (_ctx, name: string) => {
115
+ console.log("Deleting page");
116
+ await editor.space.deletePage(name);
117
+ },
118
+ "space.writeDocument": (
119
+ _ctx,
120
+ name: string,
121
+ data: Uint8Array,
122
+ ): Promise<DocumentMeta> => {
123
+ return editor.space.writeDocument(name, data);
124
+ },
125
+ "space.deleteDocument": async (_ctx, name: string) => {
126
+ await editor.space.deleteDocument(name);
127
+ },
128
+ "space.writeFile": (
129
+ _ctx,
130
+ name: string,
131
+ data: Uint8Array,
132
+ ): Promise<FileMeta> => {
133
+ return editor.space.spacePrimitives.writeFile(name, data);
134
+ },
135
+ "space.deleteFile": (_ctx, name: string) => {
136
+ return editor.space.spacePrimitives.deleteFile(name);
137
+ },
138
+ };
139
+ }
@@ -0,0 +1,77 @@
1
+ import type { SysCallMapping } from "../system.ts";
2
+ import type { Client } from "../../client.ts";
3
+
4
+ // TODO: Reimplement this
5
+ export function syncSyscalls(client: Client): SysCallMapping {
6
+ return {
7
+ "sync.hasInitialSyncCompleted": (): boolean => {
8
+ return client.fullSyncCompleted;
9
+ },
10
+ "sync.performFileSync": (_ctx, path: string): Promise<void> => {
11
+ client.postServiceWorkerMessage({ type: "perform-file-sync", path });
12
+ return waitForServiceWorkerActivation(path);
13
+ },
14
+ "sync.performSpaceSync": (): Promise<number> => {
15
+ client.postServiceWorkerMessage({ type: "perform-space-sync" });
16
+ return waitForServiceWorkerActivation();
17
+ },
18
+ };
19
+ }
20
+
21
+ function waitForServiceWorkerActivation(path?: string): Promise<any> {
22
+ return new Promise<any>((resolve, reject) => {
23
+ client.eventHook.addLocalListener(
24
+ "service-worker:file-sync-complete",
25
+ eventHandler,
26
+ );
27
+ client.eventHook.addLocalListener(
28
+ "service-worker:space-sync-complete",
29
+ eventHandler,
30
+ );
31
+ client.eventHook.addLocalListener(
32
+ "service-worker:sync-error",
33
+ errorHandler,
34
+ );
35
+ function eventHandler(data: any) {
36
+ // If data.path is set, we are notified about a specific file sync -> all good, even for an individual file sync
37
+ // If data.path is not set, we are notified about a full space sync
38
+ // If we were waiting for a specific path, ignore other paths
39
+ if (data.path && path && data.path !== path) {
40
+ // Event for other file sync
41
+ return;
42
+ }
43
+ // If we were waiting for a specific path, ignore other paths
44
+ resolve(data);
45
+
46
+ // Unsubscribe from all these events
47
+ client.eventHook.removeLocalListener(
48
+ "service-worker:file-sync-complete",
49
+ eventHandler,
50
+ );
51
+ client.eventHook.removeLocalListener(
52
+ "service-worker:space-sync-complete",
53
+ eventHandler,
54
+ );
55
+ client.eventHook.removeLocalListener(
56
+ "service-worker:sync-error",
57
+ errorHandler,
58
+ );
59
+ }
60
+ function errorHandler(e: any) {
61
+ reject(e);
62
+ // Unsubscribe from all these events
63
+ client.eventHook.removeLocalListener(
64
+ "service-worker:file-sync-complete",
65
+ eventHandler,
66
+ );
67
+ client.eventHook.removeLocalListener(
68
+ "service-worker:space-sync-complete",
69
+ eventHandler,
70
+ );
71
+ client.eventHook.removeLocalListener(
72
+ "service-worker:sync-error",
73
+ errorHandler,
74
+ );
75
+ }
76
+ });
77
+ }
@@ -0,0 +1,150 @@
1
+ import type { SysCallMapping } from "../system.ts";
2
+ import type { Client } from "../../client.ts";
3
+ import { publicVersion } from "../../../public_version.ts";
4
+ import type { CommandDef } from "@silverbulletmd/silverbullet/type/manifest";
5
+ import type { SyscallMeta } from "@silverbulletmd/silverbullet/type/index";
6
+
7
+ export function systemSyscalls(
8
+ client: Client,
9
+ readOnlyMode: boolean,
10
+ ): SysCallMapping {
11
+ const api: SysCallMapping = {
12
+ "system.invokeFunction": (
13
+ _ctx,
14
+ fullName: string, // plug.function
15
+ ...args: any[]
16
+ ) => {
17
+ const [plugName, functionName] = fullName.split(".");
18
+ if (!plugName || !functionName) {
19
+ throw Error(`Invalid function name ${fullName}`);
20
+ }
21
+ const plug = client.clientSystem.system.loadedPlugs.get(plugName);
22
+ if (!plug) {
23
+ throw Error(`Plug ${plugName} not found`);
24
+ }
25
+ const functionDef = plug.manifest!.functions[functionName];
26
+ if (!functionDef) {
27
+ throw Error(`Function ${functionName} not found`);
28
+ }
29
+ return plug.invoke(functionName, args);
30
+ },
31
+ "system.invokeFunctionOnServer": (
32
+ ctx,
33
+ fullName: string, // plug.function
34
+ ...args: any[]
35
+ ) => {
36
+ console.warn(
37
+ "Calling deprecated system.invokeFunctionOnServer, use system.invokeFunction instead",
38
+ );
39
+ return api["system.invokeFunction"](ctx, fullName, ...args);
40
+ },
41
+ "system.serverSyscall": (_ctx, name: string, ...args: any[]) => {
42
+ console.warn(
43
+ "Calling deprecated system.serverSyscall, use syscall instead",
44
+ );
45
+ return client.clientSystem.localSyscall(name, args);
46
+ },
47
+ "system.invokeCommand": (_ctx, name: string, args?: string[]) => {
48
+ console.warn("Deprecated, use editor.invokeCommand instead");
49
+ return client.runCommandByName(name, args);
50
+ },
51
+ "system.listCommands": (): { [key: string]: CommandDef } => {
52
+ const commandHook = client.clientSystem.commandHook;
53
+ const allCommands: { [key: string]: CommandDef } = {};
54
+ for (const [cmd, def] of commandHook.buildAllCommands()) {
55
+ allCommands[cmd] = {
56
+ name: def.name,
57
+ contexts: def.contexts,
58
+ priority: def.priority,
59
+ key: def.key,
60
+ mac: def.mac,
61
+ hide: def.hide,
62
+ requireMode: def.requireMode,
63
+ };
64
+ }
65
+ return allCommands;
66
+ },
67
+ "system.listSyscalls": (): SyscallMeta[] => {
68
+ const syscalls: SyscallMeta[] = [];
69
+ for (
70
+ const [name, info] of client.clientSystem.system.registeredSyscalls
71
+ ) {
72
+ syscalls.push({
73
+ name,
74
+ requiredPermissions: info.requiredPermissions,
75
+ argCount: Math.max(0, info.callback.length - 1),
76
+ });
77
+ }
78
+ return syscalls;
79
+ },
80
+ "system.reloadPlugs": () => {
81
+ if (!client) {
82
+ throw new Error("Not supported");
83
+ }
84
+ return client.loadPlugs();
85
+ },
86
+ "system.reloadConfig": (): Record<string, any> => {
87
+ console.warn("system.reloadConfig is deprecated, it's now a no-op");
88
+ return client.config.values;
89
+ },
90
+ "system.loadSpaceScripts": async () => {
91
+ console.warn("DEPRECATED: used system.loadScripts instead");
92
+ await client.clientSystem.loadLuaScripts();
93
+ },
94
+ "system.loadScripts": async () => {
95
+ await client.clientSystem.loadLuaScripts();
96
+ },
97
+ "system.loadSpaceStyles": async () => {
98
+ if (!client) {
99
+ throw new Error("Not supported on server");
100
+ }
101
+ await client.loadCustomStyles();
102
+ },
103
+ "system.wipeClient": async (_ctx, logout = false) => {
104
+ await client.wipeClient();
105
+ if (logout) {
106
+ location.href = ".logout";
107
+ } else {
108
+ alert("Client wiped, feel free to navigate elsewhere");
109
+ }
110
+ },
111
+ // DEPRECATED
112
+ "system.cleanDatabases": (): boolean => {
113
+ console.warn(
114
+ "system.cleanDatabses is deprecated, use Client: Wipe instead",
115
+ );
116
+ return false;
117
+ },
118
+ "system.getMode": () => {
119
+ return readOnlyMode ? "ro" : "rw";
120
+ },
121
+ "system.getURLPrefix": () => {
122
+ const url = new URL(document.baseURI);
123
+
124
+ return url.pathname;
125
+ },
126
+ "system.getBaseURI": () => {
127
+ return document.baseURI;
128
+ },
129
+ "system.getVersion": () => {
130
+ return publicVersion;
131
+ },
132
+ "system.getConfig": (_ctx, key: string, defaultValue: any = undefined) => {
133
+ return client.config.get(key, defaultValue);
134
+ },
135
+ // DEPRECATED
136
+ "system.getEnv": () => {
137
+ console.warn(
138
+ "system.getEnv is deprecated, you can assume the env to always be the client",
139
+ );
140
+ return null;
141
+ },
142
+ "system.getSpaceConfig": (_ctx, key, defaultValue?) => {
143
+ console.warn(
144
+ "system.getSpaceConfig is deprecated, use system.getConfig instead",
145
+ );
146
+ return client.config.get(key, defaultValue);
147
+ },
148
+ };
149
+ return api;
150
+ }
@@ -0,0 +1,201 @@
1
+ import type { Hook } from "./types.ts";
2
+ import { EventEmitter } from "./event.ts";
3
+ import type { SandboxFactory } from "./sandboxes/sandbox.ts";
4
+ import { Plug } from "./plug.ts";
5
+ import { InMemoryManifestCache, type ManifestCache } from "./manifest_cache.ts";
6
+ import {
7
+ builtinPlugNames,
8
+ builtinPlugPaths,
9
+ } from "../../plugs/builtin_plugs.ts";
10
+
11
+ export interface SysCallMapping {
12
+ [key: string]: (ctx: SyscallContext, ...args: any) => Promise<any> | any;
13
+ }
14
+
15
+ export type SystemEvents<HookT> = {
16
+ plugLoaded: (plug: Plug<HookT>) => void | Promise<void>;
17
+ plugUnloaded: (name: string) => void | Promise<void>;
18
+ };
19
+
20
+ // Passed to every syscall, allows to pass in additional context that the syscall may use
21
+ export type SyscallContext = {
22
+ // This is the plug that is invoking the syscall,
23
+ // which may be undefined where this cannot be determined (e.g. when running in a NoSandbox)
24
+ plug?: string;
25
+ };
26
+
27
+ type SyscallSignature = (
28
+ ...args: any[]
29
+ ) => Promise<any> | any;
30
+
31
+ type Syscall = {
32
+ requiredPermissions: string[];
33
+ callback: SyscallSignature;
34
+ };
35
+
36
+ export type SystemOptions = {
37
+ manifestCache?: ManifestCache<any>;
38
+ plugFlushTimeout?: number;
39
+ };
40
+
41
+ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
42
+ registeredSyscalls = new Map<string, Syscall>();
43
+ protected plugs = new Map<string, Plug<HookT>>();
44
+ protected enabledHooks = new Set<Hook<HookT>>();
45
+
46
+ /**
47
+ * @param env either an environment or undefined for hybrid mode
48
+ */
49
+ constructor(
50
+ readonly env: string | undefined = undefined,
51
+ readonly options: SystemOptions = {},
52
+ ) {
53
+ super();
54
+ if (!options.manifestCache) {
55
+ options.manifestCache = new InMemoryManifestCache();
56
+ }
57
+ }
58
+
59
+ get loadedPlugs(): Map<string, Plug<HookT>> {
60
+ return this.plugs;
61
+ }
62
+
63
+ addHook(feature: Hook<HookT>) {
64
+ this.enabledHooks.add(feature);
65
+ feature.apply(this);
66
+ }
67
+
68
+ registerSyscalls(
69
+ requiredCapabilities: string[],
70
+ ...registrationObjects: SysCallMapping[]
71
+ ) {
72
+ for (const registrationObject of registrationObjects) {
73
+ for (const [name, callback] of Object.entries(registrationObject)) {
74
+ this.registeredSyscalls.set(name, {
75
+ requiredPermissions: requiredCapabilities,
76
+ callback,
77
+ });
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Invokes a function named using the "plug.functionName" pattern, for convenience
84
+ * @param name name of the function (e.g. plug.doSomething)
85
+ * @param args an array of arguments to pass to the function
86
+ */
87
+ invokeFunction(name: string, args: any[]): Promise<any> {
88
+ // Some sanity type checks
89
+ if (typeof name !== "string") {
90
+ throw new Error(
91
+ `invokeFunction: function name should be a string, got ${typeof name}`,
92
+ );
93
+ }
94
+ if (!Array.isArray(args)) {
95
+ throw new Error(
96
+ `invokeFunction: args should be an array, got ${typeof args}`,
97
+ );
98
+ }
99
+ const [plugName, functionName] = name.split(".");
100
+ if (!functionName) {
101
+ // Sanity check
102
+ throw new Error(`Missing function name: ${name}`);
103
+ }
104
+ const plug = this.loadedPlugs.get(plugName);
105
+ if (!plug) {
106
+ throw new Error(`Plug ${plugName} not found invoking ${name}`);
107
+ }
108
+ return plug.invoke(functionName, args);
109
+ }
110
+
111
+ localSyscall(name: string, args: any[]): Promise<any> {
112
+ return this.syscall({}, name, args);
113
+ }
114
+
115
+ syscall(
116
+ ctx: SyscallContext,
117
+ name: string,
118
+ args: any[],
119
+ ): Promise<any> {
120
+ const syscall = this.registeredSyscalls.get(name);
121
+ if (!syscall) {
122
+ throw Error(`Unregistered syscall ${name}`);
123
+ }
124
+ if (ctx.plug) {
125
+ // Only when running in a plug context do we check permissions
126
+ const plug = this.loadedPlugs.get(ctx.plug);
127
+ if (!plug) {
128
+ throw new Error(
129
+ `Plug ${ctx.plug} not found while attempting to invoke ${name}}`,
130
+ );
131
+ }
132
+ for (const permission of syscall.requiredPermissions) {
133
+ if (!plug.grantedPermissions.includes(permission)) {
134
+ throw Error(`Missing permission '${permission}' for syscall ${name}`);
135
+ }
136
+ }
137
+ }
138
+ return Promise.resolve(syscall.callback(ctx, ...args));
139
+ }
140
+
141
+ /**
142
+ * @param cacheKey Used to cache the manifest of the worker. Should be equal to the filepath
143
+ * @param cacheHash Used to determine if the manifest is up to date. Should be `lastModified`
144
+ */
145
+ async loadPlug(
146
+ sandboxFactory: SandboxFactory<HookT>,
147
+ cacheKey: string,
148
+ cacheHash: number = -1,
149
+ ): Promise<Plug<HookT>> {
150
+ const plug = await Plug.createLazily(
151
+ this,
152
+ cacheKey,
153
+ cacheHash,
154
+ sandboxFactory,
155
+ );
156
+
157
+ const manifest = plug.manifest;
158
+
159
+ // This depends on cacheKey being the file path
160
+ if (
161
+ !builtinPlugPaths.includes(cacheKey) &&
162
+ builtinPlugNames.includes(manifest.name)
163
+ ) {
164
+ plug.stop();
165
+ throw new Error("Plug tried to overwrite internal plug");
166
+ }
167
+
168
+ // Validate the manifest
169
+ let errors: string[] = [];
170
+ for (const feature of this.enabledHooks) {
171
+ errors = [...errors, ...feature.validateManifest(manifest)];
172
+ }
173
+ if (errors.length > 0) {
174
+ throw new Error(`Invalid manifest: ${errors.join(", ")}`);
175
+ }
176
+ if (this.plugs.has(manifest.name)) {
177
+ this.unload(manifest.name);
178
+ }
179
+ console.log("Activated plug", manifest.name);
180
+ this.plugs.set(manifest.name, plug);
181
+
182
+ await this.emit("plugLoaded", plug);
183
+ return plug;
184
+ }
185
+
186
+ unload(name: string) {
187
+ const plug = this.plugs.get(name);
188
+ if (!plug) {
189
+ return;
190
+ }
191
+ plug.stop();
192
+ this.emit("plugUnloaded", name);
193
+ this.plugs.delete(name);
194
+ }
195
+
196
+ unloadAll(): Promise<void[]> {
197
+ return Promise.all(
198
+ Array.from(this.plugs.keys()).map(this.unload.bind(this)),
199
+ );
200
+ }
201
+ }
@@ -0,0 +1,60 @@
1
+ import type { System } from "./system.ts";
2
+ import type { AssetJson } from "../asset_bundle/bundle.ts";
3
+
4
+ /** The generic top level of a plug manifest file.
5
+ * Defines plug metadata and functions.
6
+ */
7
+ export interface Manifest<HookT> {
8
+ /** The plug's name. Typically this is the name of the manifest file, without the file extension. */
9
+ name: string;
10
+
11
+ /** A list of syscall permissions required for this plug to function.
12
+ *
13
+ * Possible values:
14
+ * - `fetch`: enables `fetch` function. (see: plug-api/plugos-syscall/fetch.ts, and plug-api/lib/fetch.ts)
15
+ * - `shell`: enables the `shell.run` syscall. (see: plug-api/plugos-syscall/shell.ts)
16
+ */
17
+ requiredPermissions?: string[];
18
+
19
+ /** A list of files or glob patterns that should be bundled with the plug.
20
+ *
21
+ * These files will be accessible through the `asset.readAsset` function.
22
+ *
23
+ * see: plug-api/plugos-syscall/asset.ts#readAsset
24
+ */
25
+ assets?: string[] | AssetJson;
26
+
27
+ /** A map of function names to definitions. Declared functions are public, and may be associated with various hooks
28
+ *
29
+ * see: common/manifest.ts#SilverBulletHooks
30
+ */
31
+ functions: Record<string, FunctionDef<HookT>>;
32
+
33
+ /**
34
+ * A map of configuration options for the plug (to be merged with the system configuration).
35
+ */
36
+ config?: any;
37
+ }
38
+
39
+ /** Associates hooks with a function. This is the generic base structure, that identifies the function. Hooks are defined by the type parameter. */
40
+ export type FunctionDef<HookT> = {
41
+ /** A function path, in the form `${relativeFilename}:${functionName}`.
42
+ *
43
+ * During compilation (see `../build_plugs.ts`) the function is read from the file and inlined into the plug bundle.
44
+ *
45
+ * This field and `FunctionDef.redirect` are mutually exclusive.
46
+ */
47
+ path?: string;
48
+
49
+ /** A function from another plug, in the form `${plugName}.${functionName}` that will be attached to the given hooks. */
50
+ redirect?: string;
51
+
52
+ /** Environments where this plug is allowed to run, current may be one of "cli", "server", or "client". */
53
+ env?: string;
54
+ } & HookT;
55
+
56
+ export interface Hook<HookT> {
57
+ validateManifest(manifest: Manifest<HookT>): string[];
58
+
59
+ apply(system: System<HookT>): void;
60
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Checks if an object is sendable across the plugos worker boundary.
3
+ *
4
+ * @param o - The object to check.
5
+ * @returns `true` if the object is sendable, `false` otherwise.
6
+ */
7
+ export function isSendable(o: any): boolean {
8
+ try {
9
+ structuredClone(o);
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }