@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.
- package/LICENSE.md +18 -0
- package/README.md +98 -0
- package/client/asset_bundle/bundle.ts +95 -0
- package/client/data/datastore.ts +85 -0
- package/client/data/kv_primitives.ts +25 -0
- package/client/markdown_parser/constants.ts +13 -0
- package/client/plugos/event.ts +36 -0
- package/client/plugos/eventhook.ts +8 -0
- package/client/plugos/hooks/code_widget.ts +59 -0
- package/client/plugos/hooks/command.ts +104 -0
- package/client/plugos/hooks/document_editor.ts +77 -0
- package/client/plugos/hooks/event.ts +187 -0
- package/client/plugos/hooks/mq.ts +154 -0
- package/client/plugos/hooks/plug_namespace.ts +85 -0
- package/client/plugos/hooks/slash_command.ts +192 -0
- package/client/plugos/hooks/syscall.ts +66 -0
- package/client/plugos/manifest_cache.ts +67 -0
- package/client/plugos/plug.ts +99 -0
- package/client/plugos/plug_compile.ts +202 -0
- package/client/plugos/protocol.ts +40 -0
- package/client/plugos/proxy_fetch.ts +53 -0
- package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
- package/client/plugos/sandboxes/sandbox.ts +14 -0
- package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
- package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
- package/client/plugos/syscalls/asset.ts +35 -0
- package/client/plugos/syscalls/clientStore.ts +21 -0
- package/client/plugos/syscalls/client_code_widget.ts +12 -0
- package/client/plugos/syscalls/code_widget.ts +24 -0
- package/client/plugos/syscalls/config.ts +46 -0
- package/client/plugos/syscalls/datastore.ts +89 -0
- package/client/plugos/syscalls/editor.ts +673 -0
- package/client/plugos/syscalls/event.ts +36 -0
- package/client/plugos/syscalls/fetch.ts +128 -0
- package/client/plugos/syscalls/index.ts +102 -0
- package/client/plugos/syscalls/jsonschema.ts +69 -0
- package/client/plugos/syscalls/language.ts +23 -0
- package/client/plugos/syscalls/lua.ts +58 -0
- package/client/plugos/syscalls/markdown.ts +84 -0
- package/client/plugos/syscalls/mq.ts +52 -0
- package/client/plugos/syscalls/service_registry.ts +43 -0
- package/client/plugos/syscalls/shell.ts +39 -0
- package/client/plugos/syscalls/space.ts +139 -0
- package/client/plugos/syscalls/sync.ts +77 -0
- package/client/plugos/syscalls/system.ts +150 -0
- package/client/plugos/system.ts +201 -0
- package/client/plugos/types.ts +60 -0
- package/client/plugos/util.ts +14 -0
- package/client/plugos/worker_runtime.ts +195 -0
- package/client/space_lua/ast.ts +328 -0
- package/client/space_lua/ast_narrow.ts +81 -0
- package/client/space_lua/eval.ts +2478 -0
- package/client/space_lua/labels.ts +416 -0
- package/client/space_lua/numeric.ts +240 -0
- package/client/space_lua/parse.ts +1522 -0
- package/client/space_lua/query_collection.ts +232 -0
- package/client/space_lua/rp.ts +27 -0
- package/client/space_lua/runtime.ts +1702 -0
- package/client/space_lua/stdlib/crypto.ts +10 -0
- package/client/space_lua/stdlib/encoding.ts +19 -0
- package/client/space_lua/stdlib/format.ts +770 -0
- package/client/space_lua/stdlib/js.ts +73 -0
- package/client/space_lua/stdlib/load.ts +52 -0
- package/client/space_lua/stdlib/math.ts +193 -0
- package/client/space_lua/stdlib/net.ts +113 -0
- package/client/space_lua/stdlib/os.ts +368 -0
- package/client/space_lua/stdlib/space_lua.ts +153 -0
- package/client/space_lua/stdlib/string.ts +286 -0
- package/client/space_lua/stdlib/table.ts +401 -0
- package/client/space_lua/stdlib.ts +489 -0
- package/client/space_lua/tonumber.ts +501 -0
- package/client/space_lua/util.ts +96 -0
- package/dist/plug-compile.js +1513 -0
- package/package.json +120 -0
- package/plug-api/constants.ts +42 -0
- package/plug-api/lib/async.ts +162 -0
- package/plug-api/lib/crypto.ts +202 -0
- package/plug-api/lib/dates.ts +13 -0
- package/plug-api/lib/json.ts +136 -0
- package/plug-api/lib/limited_map.ts +72 -0
- package/plug-api/lib/memory_cache.ts +21 -0
- package/plug-api/lib/native_fetch.ts +6 -0
- package/plug-api/lib/ref.ts +275 -0
- package/plug-api/lib/resolve.ts +90 -0
- package/plug-api/lib/tags.ts +15 -0
- package/plug-api/lib/transclusion.ts +122 -0
- package/plug-api/lib/tree.ts +232 -0
- package/plug-api/lib/yaml.ts +284 -0
- package/plug-api/syscall.ts +15 -0
- package/plug-api/syscalls/asset.ts +36 -0
- package/plug-api/syscalls/client_store.ts +33 -0
- package/plug-api/syscalls/code_widget.ts +8 -0
- package/plug-api/syscalls/config.ts +58 -0
- package/plug-api/syscalls/datastore.ts +96 -0
- package/plug-api/syscalls/editor.ts +517 -0
- package/plug-api/syscalls/event.ts +47 -0
- package/plug-api/syscalls/index.ts +77 -0
- package/plug-api/syscalls/jsonschema.ts +25 -0
- package/plug-api/syscalls/language.ts +23 -0
- package/plug-api/syscalls/lua.ts +20 -0
- package/plug-api/syscalls/markdown.ts +38 -0
- package/plug-api/syscalls/mq.ts +79 -0
- package/plug-api/syscalls/shell.ts +14 -0
- package/plug-api/syscalls/space.ts +212 -0
- package/plug-api/syscalls/sync.ts +28 -0
- package/plug-api/syscalls/system.ts +102 -0
- package/plug-api/syscalls/yaml.ts +28 -0
- package/plug-api/syscalls.ts +21 -0
- package/plug-api/system_mock.ts +89 -0
- package/plug-api/types/client.ts +116 -0
- package/plug-api/types/config.ts +22 -0
- package/plug-api/types/datastore.ts +28 -0
- package/plug-api/types/event.ts +27 -0
- package/plug-api/types/index.ts +56 -0
- package/plug-api/types/manifest.ts +98 -0
- package/plug-api/types/namespace.ts +6 -0
- package/plugs/builtin_plugs.ts +14 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { KvPrimitives } from "../data/kv_primitives.ts";
|
|
2
|
+
import type { Plug } from "./plug.ts";
|
|
3
|
+
import type { Manifest } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
export interface ManifestCache<T> {
|
|
6
|
+
getManifest(
|
|
7
|
+
plug: Plug<T>,
|
|
8
|
+
cacheKey: string,
|
|
9
|
+
cacheHash: number,
|
|
10
|
+
): Promise<Manifest<T>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class KVPrimitivesManifestCache<T> implements ManifestCache<T> {
|
|
14
|
+
constructor(private kv: KvPrimitives, private manifestPrefix: string) {
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async getManifest(
|
|
18
|
+
plug: Plug<T>,
|
|
19
|
+
cacheKey: string,
|
|
20
|
+
cacheHash: number,
|
|
21
|
+
): Promise<Manifest<T>> {
|
|
22
|
+
const [cached] = await this.kv.batchGet([[
|
|
23
|
+
this.manifestPrefix,
|
|
24
|
+
cacheKey,
|
|
25
|
+
]]);
|
|
26
|
+
if (cached && cached.hash === cacheHash) {
|
|
27
|
+
// console.log("Using KV cached manifest for", plug.name);
|
|
28
|
+
return cached.manifest;
|
|
29
|
+
}
|
|
30
|
+
await plug.sandbox.init();
|
|
31
|
+
const manifest = plug.sandbox.manifest!;
|
|
32
|
+
await this.kv.batchSet([{
|
|
33
|
+
key: [this.manifestPrefix, cacheKey],
|
|
34
|
+
// Deliverately removing the assets from the manifest to preserve space, will be re-added upon load of actual worker
|
|
35
|
+
value: { manifest: { ...manifest, assets: undefined }, hash: cacheHash },
|
|
36
|
+
}]);
|
|
37
|
+
return manifest;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class InMemoryManifestCache<T> implements ManifestCache<T> {
|
|
42
|
+
private cache = new Map<string, {
|
|
43
|
+
manifest: Manifest<T>;
|
|
44
|
+
hash: number;
|
|
45
|
+
}>();
|
|
46
|
+
|
|
47
|
+
async getManifest(
|
|
48
|
+
plug: Plug<T>,
|
|
49
|
+
cacheKey: string,
|
|
50
|
+
cacheHash: number,
|
|
51
|
+
): Promise<Manifest<T>> {
|
|
52
|
+
const cached = this.cache.get(cacheKey);
|
|
53
|
+
if (cached && cached.hash === cacheHash) {
|
|
54
|
+
// console.log("Using memory cached manifest for", plug.name);
|
|
55
|
+
return cached.manifest;
|
|
56
|
+
}
|
|
57
|
+
await plug.sandbox.init();
|
|
58
|
+
const manifest = plug.sandbox.manifest!;
|
|
59
|
+
|
|
60
|
+
// Deliverately removing the assets from the manifest to preserve space, will be re-added upon load of actual worker
|
|
61
|
+
this.cache.set(cacheKey, {
|
|
62
|
+
manifest: { ...manifest, assets: undefined },
|
|
63
|
+
hash: cacheHash,
|
|
64
|
+
});
|
|
65
|
+
return manifest;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Manifest } from "./types.ts";
|
|
2
|
+
import type { System } from "./system.ts";
|
|
3
|
+
import type { AssetBundle } from "../asset_bundle/bundle.ts";
|
|
4
|
+
import type { Sandbox, SandboxFactory } from "./sandboxes/sandbox.ts";
|
|
5
|
+
|
|
6
|
+
export class Plug<HookT> {
|
|
7
|
+
readonly runtimeEnv?: string;
|
|
8
|
+
|
|
9
|
+
public grantedPermissions: string[] = [];
|
|
10
|
+
public sandbox: Sandbox<HookT>;
|
|
11
|
+
|
|
12
|
+
public manifest!: Manifest<HookT>;
|
|
13
|
+
public assets?: AssetBundle;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
readonly system: System<HookT>,
|
|
17
|
+
sandboxFactory: SandboxFactory<HookT>,
|
|
18
|
+
) {
|
|
19
|
+
this.runtimeEnv = system.env;
|
|
20
|
+
this.sandbox = sandboxFactory(this);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static async createLazily<HookT>(
|
|
24
|
+
system: System<HookT>,
|
|
25
|
+
cacheKey: string,
|
|
26
|
+
cacheHash: number,
|
|
27
|
+
sandboxFactory: SandboxFactory<HookT>,
|
|
28
|
+
): Promise<Plug<HookT>> {
|
|
29
|
+
const plug = new Plug(
|
|
30
|
+
system,
|
|
31
|
+
sandboxFactory,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Retrieve the manifest, which may either come from a cache or be loaded from the worker
|
|
35
|
+
plug.manifest = await system.options.manifestCache!.getManifest(
|
|
36
|
+
plug,
|
|
37
|
+
cacheKey,
|
|
38
|
+
cacheHash,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// TODO: These need to be explicitly granted, not just taken
|
|
42
|
+
plug.grantedPermissions = plug.manifest.requiredPermissions || [];
|
|
43
|
+
|
|
44
|
+
return plug;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Invoke a syscall
|
|
48
|
+
syscall(name: string, args: any[]): Promise<any> {
|
|
49
|
+
return this.system.syscall({ plug: this.manifest.name }, name, args);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if a function can be invoked (it may be restricted on its execution environment)
|
|
54
|
+
*/
|
|
55
|
+
canInvoke(name: string) {
|
|
56
|
+
const funDef = this.manifest!.functions[name];
|
|
57
|
+
if (!funDef) {
|
|
58
|
+
throw new Error(`Function ${name} not found in manifest`);
|
|
59
|
+
}
|
|
60
|
+
return !funDef.env || !this.runtimeEnv || funDef.env === this.runtimeEnv;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Invoke a function
|
|
64
|
+
async invoke(name: string, args: any[]): Promise<any> {
|
|
65
|
+
// Before we access the manifest
|
|
66
|
+
const funDef = this.manifest!.functions[name];
|
|
67
|
+
if (!funDef) {
|
|
68
|
+
throw new Error(`Function ${name} not found in manifest`);
|
|
69
|
+
}
|
|
70
|
+
const sandbox = this.sandbox!;
|
|
71
|
+
if (funDef.redirect) {
|
|
72
|
+
// Function redirect, look up
|
|
73
|
+
// deno-lint-ignore no-this-alias
|
|
74
|
+
let plug: Plug<HookT> | undefined = this;
|
|
75
|
+
if (funDef.redirect.indexOf(".") !== -1) {
|
|
76
|
+
const [plugName, functionName] = funDef.redirect.split(".");
|
|
77
|
+
plug = this.system.loadedPlugs.get(plugName);
|
|
78
|
+
if (!plug) {
|
|
79
|
+
throw Error(`Plug ${plugName} redirected to not found`);
|
|
80
|
+
}
|
|
81
|
+
name = functionName;
|
|
82
|
+
} else {
|
|
83
|
+
name = funDef.redirect;
|
|
84
|
+
}
|
|
85
|
+
return plug.invoke(name, args);
|
|
86
|
+
}
|
|
87
|
+
if (!await this.canInvoke(name)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Function ${name} is not available in ${this.runtimeEnv}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return sandbox.invoke(name, args);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
stop() {
|
|
96
|
+
console.log("Stopping sandbox for", this.manifest.name);
|
|
97
|
+
this.sandbox.stop();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { readFile, writeFile, mkdtemp, rm, mkdir } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import * as YAML from "js-yaml";
|
|
5
|
+
|
|
6
|
+
import { denoPlugin, esbuild } from "../../build_deps.ts";
|
|
7
|
+
import { bundleAssets } from "../asset_bundle/builder.ts";
|
|
8
|
+
import type { Manifest } from "./types.ts";
|
|
9
|
+
import { version } from "../../version.ts";
|
|
10
|
+
|
|
11
|
+
// const workerRuntimeUrl = new URL(
|
|
12
|
+
// "../lib/plugos/worker_runtime.ts",
|
|
13
|
+
// import.meta.url,
|
|
14
|
+
// );
|
|
15
|
+
const workerRuntimeUrl =
|
|
16
|
+
`https://deno.land/x/silverbullet@${version}/client/plugos/worker_runtime.ts`;
|
|
17
|
+
|
|
18
|
+
export type CompileOptions = {
|
|
19
|
+
debug?: boolean;
|
|
20
|
+
runtimeUrl?: string;
|
|
21
|
+
// path to config file
|
|
22
|
+
configPath?: string;
|
|
23
|
+
// Print info on bundle size
|
|
24
|
+
info?: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function compileManifest(
|
|
28
|
+
manifestPath: string,
|
|
29
|
+
destPath: string,
|
|
30
|
+
options: CompileOptions = {},
|
|
31
|
+
): Promise<string> {
|
|
32
|
+
const rootPath = path.dirname(manifestPath);
|
|
33
|
+
const manifestContent = await readFile(manifestPath, "utf-8");
|
|
34
|
+
const manifest = YAML.load(manifestContent) as Manifest<any>;
|
|
35
|
+
|
|
36
|
+
if (!manifest.name) {
|
|
37
|
+
throw new Error(`Missing 'name' in ${manifestPath}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Assets
|
|
41
|
+
const assetsBundle = await bundleAssets(
|
|
42
|
+
path.resolve(rootPath),
|
|
43
|
+
manifest.assets as string[] || [],
|
|
44
|
+
);
|
|
45
|
+
manifest.assets = assetsBundle.toJSON();
|
|
46
|
+
|
|
47
|
+
// Normalize the edge case of a plug with no functions
|
|
48
|
+
if (!manifest.functions) {
|
|
49
|
+
manifest.functions = {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const jsFile = `
|
|
53
|
+
import { setupMessageListener } from "${
|
|
54
|
+
options.runtimeUrl || workerRuntimeUrl
|
|
55
|
+
}";
|
|
56
|
+
|
|
57
|
+
// Imports
|
|
58
|
+
${
|
|
59
|
+
Object.entries(manifest.functions).map(([funcName, def]) => {
|
|
60
|
+
if (!def.path) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
let [filePath, jsFunctionName] = def.path.split(":");
|
|
64
|
+
// Resolve path
|
|
65
|
+
filePath = path.join(rootPath, filePath);
|
|
66
|
+
|
|
67
|
+
return `import {${jsFunctionName} as ${funcName}} from "file://${
|
|
68
|
+
// Replacing \ with / for Windows
|
|
69
|
+
path.resolve(filePath).replaceAll(
|
|
70
|
+
"\\",
|
|
71
|
+
"\\\\",
|
|
72
|
+
)}";\n`;
|
|
73
|
+
}).join("")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Function mapping
|
|
77
|
+
const functionMapping = {
|
|
78
|
+
${
|
|
79
|
+
Object.entries(manifest.functions).map(([funcName, def]) => {
|
|
80
|
+
if (!def.path) {
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
return ` ${funcName}: ${funcName},\n`;
|
|
84
|
+
}).join("")
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Manifest
|
|
89
|
+
const manifest = ${JSON.stringify(manifest, null, 2)};
|
|
90
|
+
|
|
91
|
+
export const plug = {manifest, functionMapping};
|
|
92
|
+
|
|
93
|
+
setupMessageListener(functionMapping, manifest, self.postMessage);
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
// console.log("Code:", jsFile);
|
|
97
|
+
|
|
98
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), "plug-compile-"));
|
|
99
|
+
const inFile = path.join(tempDir, "input.js");
|
|
100
|
+
const outFile = `${destPath}/${manifest.name}.plug.js`;
|
|
101
|
+
await writeFile(inFile, jsFile, "utf-8");
|
|
102
|
+
|
|
103
|
+
const result = await esbuild.build({
|
|
104
|
+
entryPoints: [inFile],
|
|
105
|
+
bundle: true,
|
|
106
|
+
format: "esm",
|
|
107
|
+
globalName: "mod",
|
|
108
|
+
platform: "browser",
|
|
109
|
+
sourcemap: "linked",
|
|
110
|
+
minify: !options.debug,
|
|
111
|
+
outfile: outFile,
|
|
112
|
+
metafile: options.info,
|
|
113
|
+
treeShaking: true,
|
|
114
|
+
plugins: [
|
|
115
|
+
denoPlugin({
|
|
116
|
+
configPath: options.configPath &&
|
|
117
|
+
path.resolve(process.cwd(), options.configPath),
|
|
118
|
+
}),
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (options.info) {
|
|
123
|
+
const text = await esbuild.analyzeMetafile(result.metafile!);
|
|
124
|
+
console.log("Bundle info for", manifestPath, text);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let jsCode = await readFile(outFile, "utf-8");
|
|
128
|
+
jsCode = patchDenoLibJS(jsCode);
|
|
129
|
+
await writeFile(outFile, jsCode, "utf-8");
|
|
130
|
+
|
|
131
|
+
// Clean up temp directory
|
|
132
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
133
|
+
|
|
134
|
+
console.log(`Plug ${manifest.name} written to ${outFile}.`);
|
|
135
|
+
return outFile;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function compileManifests(
|
|
139
|
+
manifestFiles: string[],
|
|
140
|
+
dist: string,
|
|
141
|
+
options: CompileOptions = {},
|
|
142
|
+
) {
|
|
143
|
+
let building = false;
|
|
144
|
+
dist = path.resolve(dist);
|
|
145
|
+
|
|
146
|
+
async function buildAll() {
|
|
147
|
+
if (building) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log("Building", manifestFiles);
|
|
151
|
+
building = true;
|
|
152
|
+
await mkdir(dist, { recursive: true });
|
|
153
|
+
const startTime = Date.now();
|
|
154
|
+
// Build all plugs in parallel
|
|
155
|
+
await Promise.all(manifestFiles.map(async (plugManifestPath) => {
|
|
156
|
+
const manifestPath = plugManifestPath as string;
|
|
157
|
+
try {
|
|
158
|
+
await compileManifest(
|
|
159
|
+
manifestPath,
|
|
160
|
+
dist,
|
|
161
|
+
options,
|
|
162
|
+
);
|
|
163
|
+
} catch (e: any) {
|
|
164
|
+
console.error(`Error building ${manifestPath}:`, e.message);
|
|
165
|
+
throw e;
|
|
166
|
+
}
|
|
167
|
+
}));
|
|
168
|
+
console.log(`Done building plugs in ${Date.now() - startTime}ms`);
|
|
169
|
+
building = false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await buildAll();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function patchDenoLibJS(code: string): string {
|
|
176
|
+
// The Deno std lib has one occurence of a regex that Webkit JS doesn't (yet parse), we'll strip it because it's likely never invoked anyway, YOLO
|
|
177
|
+
return code.replaceAll("/(?<=\\n)/", "/()/");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function plugCompileCommand(
|
|
181
|
+
{ dist, debug, info, config, runtimeUrl }: {
|
|
182
|
+
dist: string;
|
|
183
|
+
debug: boolean;
|
|
184
|
+
info: boolean;
|
|
185
|
+
config?: string;
|
|
186
|
+
runtimeUrl?: string;
|
|
187
|
+
},
|
|
188
|
+
...manifestPaths: string[]
|
|
189
|
+
) {
|
|
190
|
+
await compileManifests(
|
|
191
|
+
manifestPaths,
|
|
192
|
+
dist,
|
|
193
|
+
{
|
|
194
|
+
debug: debug,
|
|
195
|
+
info: info,
|
|
196
|
+
runtimeUrl,
|
|
197
|
+
configPath: config,
|
|
198
|
+
},
|
|
199
|
+
);
|
|
200
|
+
esbuild.stop();
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Manifest } from "@silverbulletmd/silverbullet/type/manifest";
|
|
2
|
+
|
|
3
|
+
// Messages received from the worker
|
|
4
|
+
export type ControllerMessage =
|
|
5
|
+
| {
|
|
6
|
+
// Parsed manifest when worker is initialized
|
|
7
|
+
type: "manifest";
|
|
8
|
+
manifest: Manifest;
|
|
9
|
+
}
|
|
10
|
+
| {
|
|
11
|
+
// Function invocation result
|
|
12
|
+
type: "invr";
|
|
13
|
+
id: number;
|
|
14
|
+
error?: string;
|
|
15
|
+
result?: any;
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
// Syscall
|
|
19
|
+
type: "sys";
|
|
20
|
+
id: number;
|
|
21
|
+
name: string;
|
|
22
|
+
args: any[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Messages received inside the worker
|
|
26
|
+
export type WorkerMessage =
|
|
27
|
+
| {
|
|
28
|
+
// Function invocation
|
|
29
|
+
type: "inv";
|
|
30
|
+
id: number;
|
|
31
|
+
name: string;
|
|
32
|
+
args: any[];
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
// Syscall result
|
|
36
|
+
type: "sysr";
|
|
37
|
+
id: number;
|
|
38
|
+
result?: any;
|
|
39
|
+
error?: any;
|
|
40
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { base64Decode, base64Encode } from "../../plug-api/lib/crypto.ts";
|
|
2
|
+
|
|
3
|
+
export type ProxyFetchRequest64 = {
|
|
4
|
+
method?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
base64Body?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type ProxyFetchResponse64 = {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
status: number;
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
// We base64 encode the body because the body can be binary data that we have to push through the worker boundary
|
|
14
|
+
base64Body: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ProxyFetchRequest = {
|
|
18
|
+
method?: string;
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
responseEncoding?: string;
|
|
21
|
+
body?: Uint8Array | string | any;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ProxyFetchResponse = {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
status: number;
|
|
27
|
+
headers: Record<string, string>;
|
|
28
|
+
// We base64 encode the body because the body can be binary data that we have to push through the worker boundary
|
|
29
|
+
body: Uint8Array | string | any;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export async function performLocalFetch(
|
|
33
|
+
url: string,
|
|
34
|
+
req: ProxyFetchRequest64,
|
|
35
|
+
): Promise<ProxyFetchResponse64> {
|
|
36
|
+
const result = await fetch(
|
|
37
|
+
url,
|
|
38
|
+
req && {
|
|
39
|
+
method: req.method,
|
|
40
|
+
headers: req.headers,
|
|
41
|
+
body: req.base64Body && base64Decode(req.base64Body),
|
|
42
|
+
// Casting to "any" for now, since of weird Deno typing
|
|
43
|
+
} as any,
|
|
44
|
+
);
|
|
45
|
+
return {
|
|
46
|
+
ok: result.ok,
|
|
47
|
+
status: result.status,
|
|
48
|
+
headers: Object.fromEntries(result.headers.entries()),
|
|
49
|
+
base64Body: base64Encode(
|
|
50
|
+
new Uint8Array(await (await result.blob()).arrayBuffer()),
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Plug } from "../plug.ts";
|
|
2
|
+
import type { Manifest } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
export type SandboxFactory<HookT> = (plug: Plug<HookT>) => Sandbox<HookT>;
|
|
5
|
+
|
|
6
|
+
export interface Sandbox<HookT> {
|
|
7
|
+
manifest?: Manifest<HookT>;
|
|
8
|
+
|
|
9
|
+
init(): Promise<void>;
|
|
10
|
+
|
|
11
|
+
invoke(name: string, args: any[]): Promise<any>;
|
|
12
|
+
|
|
13
|
+
stop(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { WorkerSandbox } from "./worker_sandbox.ts";
|
|
2
|
+
import type { Plug } from "../plug.ts";
|
|
3
|
+
import type { SandboxFactory } from "./sandbox.ts";
|
|
4
|
+
import { fsEndpoint } from "../../spaces/constants.ts";
|
|
5
|
+
|
|
6
|
+
export function createWorkerSandboxFromLocalPath<HookT>(
|
|
7
|
+
name: string,
|
|
8
|
+
): SandboxFactory<HookT> {
|
|
9
|
+
return (plug: Plug<HookT>) =>
|
|
10
|
+
new WorkerSandbox(
|
|
11
|
+
plug,
|
|
12
|
+
new URL(
|
|
13
|
+
name,
|
|
14
|
+
document.baseURI.slice(0, -1) + fsEndpoint + "/", // We're NOT striping trailing '/', this used to be `location.origin`
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Manifest } from "../types.ts";
|
|
2
|
+
import type { ControllerMessage, WorkerMessage } from "../protocol.ts";
|
|
3
|
+
import type { Plug } from "../plug.ts";
|
|
4
|
+
import { AssetBundle, type AssetJson } from "../../asset_bundle/bundle.ts";
|
|
5
|
+
import type { Sandbox } from "./sandbox.ts";
|
|
6
|
+
import { race, timeout } from "@silverbulletmd/silverbullet/lib/async";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents a "safe" execution environment for plug code
|
|
10
|
+
* Effectively this wraps a web worker, the reason to have this split from Plugs is to allow plugs to manage multiple sandboxes, e.g. for performance in the future
|
|
11
|
+
*/
|
|
12
|
+
export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
13
|
+
public manifest?: Manifest<HookT>;
|
|
14
|
+
private worker?: Worker;
|
|
15
|
+
private reqId = 0;
|
|
16
|
+
private outstandingInvocations = new Map<
|
|
17
|
+
number,
|
|
18
|
+
{ resolve: (result: any) => void; reject: (e: any) => void }
|
|
19
|
+
>();
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
readonly plug: Plug<HookT>,
|
|
23
|
+
public workerUrl: URL,
|
|
24
|
+
private workerOptions = {},
|
|
25
|
+
) {
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Should only invoked lazily (either by invoke, or by a ManifestCache to load the manifest)
|
|
30
|
+
*/
|
|
31
|
+
init(): Promise<void> {
|
|
32
|
+
console.log("Booting up worker from", this.workerUrl.pathname);
|
|
33
|
+
if (this.worker) {
|
|
34
|
+
// Race condition
|
|
35
|
+
console.warn("Double init of sandbox, ignoring");
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
}
|
|
38
|
+
this.worker = new Worker(this.workerUrl, {
|
|
39
|
+
...this.workerOptions,
|
|
40
|
+
type: "module",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return race([
|
|
44
|
+
// We're adding a timeout of 5s here to handle the case where a plug blows up during initialization
|
|
45
|
+
timeout(5000).catch((_) =>
|
|
46
|
+
Promise.reject(new Error("Plug timed out during creation"))
|
|
47
|
+
),
|
|
48
|
+
new Promise((resolve) => {
|
|
49
|
+
this.worker!.onmessage = (ev) => {
|
|
50
|
+
if (ev.data.type === "manifest") {
|
|
51
|
+
this.manifest = ev.data.manifest;
|
|
52
|
+
// Set manifest in the plug
|
|
53
|
+
this.plug.manifest = this.manifest!;
|
|
54
|
+
|
|
55
|
+
// Set assets in the plug
|
|
56
|
+
this.plug.assets = new AssetBundle(
|
|
57
|
+
this.manifest?.assets ? this.manifest.assets as AssetJson : {},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return resolve();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.onMessage(ev.data);
|
|
64
|
+
};
|
|
65
|
+
}),
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async onMessage(data: ControllerMessage) {
|
|
70
|
+
if (!this.worker) {
|
|
71
|
+
console.warn("Received message for terminated worker, ignoring");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
switch (data.type) {
|
|
75
|
+
case "sys":
|
|
76
|
+
try {
|
|
77
|
+
const result = await this.plug.syscall(data.name!, data.args!);
|
|
78
|
+
|
|
79
|
+
this.worker && this.worker!.postMessage({
|
|
80
|
+
type: "sysr",
|
|
81
|
+
id: data.id,
|
|
82
|
+
result: result,
|
|
83
|
+
} as WorkerMessage);
|
|
84
|
+
} catch (e: any) {
|
|
85
|
+
// console.error("Syscall fail", e);
|
|
86
|
+
this.worker && this.worker!.postMessage({
|
|
87
|
+
type: "sysr",
|
|
88
|
+
id: data.id,
|
|
89
|
+
error: e.message,
|
|
90
|
+
} as WorkerMessage);
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case "invr": {
|
|
94
|
+
const resultCbs = this.outstandingInvocations.get(data.id!);
|
|
95
|
+
this.outstandingInvocations.delete(data.id!);
|
|
96
|
+
if (data.error) {
|
|
97
|
+
resultCbs &&
|
|
98
|
+
resultCbs.reject(new Error(data.error));
|
|
99
|
+
} else {
|
|
100
|
+
resultCbs && resultCbs.resolve(data.result);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
default:
|
|
105
|
+
console.error("Unknown message type", data);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async invoke(name: string, args: any[]): Promise<any> {
|
|
110
|
+
if (!this.worker) {
|
|
111
|
+
// Lazy initialization
|
|
112
|
+
await this.init();
|
|
113
|
+
}
|
|
114
|
+
this.reqId++;
|
|
115
|
+
this.worker!.postMessage({
|
|
116
|
+
type: "inv",
|
|
117
|
+
id: this.reqId,
|
|
118
|
+
name,
|
|
119
|
+
args,
|
|
120
|
+
} as WorkerMessage);
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
this.outstandingInvocations.set(this.reqId, { resolve, reject });
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
stop() {
|
|
127
|
+
if (this.worker) {
|
|
128
|
+
this.worker.terminate();
|
|
129
|
+
this.worker = undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SysCallMapping, System } from "../system.ts";
|
|
2
|
+
import type { FileMeta } from "../../../plug-api/types/index.ts";
|
|
3
|
+
|
|
4
|
+
export default function assetSyscalls(system: System<any>): SysCallMapping {
|
|
5
|
+
return {
|
|
6
|
+
"asset.readAsset": (_ctx, plugName: string, name: string): string => {
|
|
7
|
+
return system.loadedPlugs.get(plugName)!.assets!.readFileAsDataUrl(
|
|
8
|
+
name,
|
|
9
|
+
);
|
|
10
|
+
},
|
|
11
|
+
"asset.listFiles": (_ctx, plugName: string): FileMeta[] => {
|
|
12
|
+
const assets = system.loadedPlugs.get(plugName)!.assets!;
|
|
13
|
+
const fileNames = assets.listFiles();
|
|
14
|
+
return fileNames.map((name) => ({
|
|
15
|
+
name,
|
|
16
|
+
contentType: assets.getMimeType(name),
|
|
17
|
+
created: assets.getMtime(name),
|
|
18
|
+
lastModified: assets.getMtime(name),
|
|
19
|
+
size: -1,
|
|
20
|
+
perm: "ro",
|
|
21
|
+
}));
|
|
22
|
+
},
|
|
23
|
+
"asset.getFileMeta": (_ctx, plugName: string, name: string): FileMeta => {
|
|
24
|
+
const assets = system.loadedPlugs.get(plugName)!.assets!;
|
|
25
|
+
return {
|
|
26
|
+
name,
|
|
27
|
+
contentType: assets.getMimeType(name),
|
|
28
|
+
created: assets.getMtime(name),
|
|
29
|
+
lastModified: assets.getMtime(name),
|
|
30
|
+
size: -1,
|
|
31
|
+
perm: "ro",
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SysCallMapping } from "../../plugos/system.ts";
|
|
2
|
+
import type { DataStore } from "../../data/datastore.ts";
|
|
3
|
+
|
|
4
|
+
import type { KvKey } from "@silverbulletmd/silverbullet/type/datastore";
|
|
5
|
+
|
|
6
|
+
export function clientStoreSyscalls(
|
|
7
|
+
ds: DataStore,
|
|
8
|
+
prefix: KvKey = ["client"],
|
|
9
|
+
): SysCallMapping {
|
|
10
|
+
return {
|
|
11
|
+
"clientStore.get": (_ctx, key: string): Promise<any> => {
|
|
12
|
+
return ds.get([...prefix, key]);
|
|
13
|
+
},
|
|
14
|
+
"clientStore.set": (_ctx, key: string, val: any): Promise<void> => {
|
|
15
|
+
return ds.set([...prefix, key], val);
|
|
16
|
+
},
|
|
17
|
+
"clientStore.delete": (_ctx, key: string): Promise<void> => {
|
|
18
|
+
return ds.delete([...prefix, key]);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|