Optimizations
parent
f577661128
commit
b644801e7b
|
@ -1,43 +0,0 @@
|
|||
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
|
||||
import { compileManifest } from "../plugos/compile.ts";
|
||||
import { esbuild } from "../plugos/deps.ts";
|
||||
import { runPlug } from "./plug_run.ts";
|
||||
import assets from "../dist/plug_asset_bundle.json" assert {
|
||||
type: "json",
|
||||
};
|
||||
import { assertEquals } from "../test_deps.ts";
|
||||
import { path } from "../common/deps.ts";
|
||||
|
||||
Deno.test("Test plug run", {
|
||||
sanitizeResources: false,
|
||||
sanitizeOps: false,
|
||||
}, async () => {
|
||||
// const tempDir = await Deno.makeTempDir();
|
||||
const tempDbFile = await Deno.makeTempFile({ suffix: ".db" });
|
||||
|
||||
const assetBundle = new AssetBundle(assets);
|
||||
|
||||
const testFolder = path.dirname(new URL(import.meta.url).pathname);
|
||||
const testSpaceFolder = path.join(testFolder, "test_space");
|
||||
|
||||
const plugFolder = path.join(testSpaceFolder, "_plug");
|
||||
await Deno.mkdir(plugFolder, { recursive: true });
|
||||
|
||||
await compileManifest(
|
||||
path.join(testFolder, "test.plug.yaml"),
|
||||
plugFolder,
|
||||
);
|
||||
assertEquals(
|
||||
await runPlug(
|
||||
testSpaceFolder,
|
||||
"test.run",
|
||||
[],
|
||||
assetBundle,
|
||||
),
|
||||
"Hello",
|
||||
);
|
||||
|
||||
// await Deno.remove(tempDir, { recursive: true });
|
||||
esbuild.stop();
|
||||
await Deno.remove(tempDbFile);
|
||||
});
|
|
@ -1,69 +0,0 @@
|
|||
import { DiskSpacePrimitives } from "../common/spaces/disk_space_primitives.ts";
|
||||
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
|
||||
|
||||
import { Application } from "../server/deps.ts";
|
||||
import { sleep } from "$sb/lib/async.ts";
|
||||
import { ServerSystem } from "../server/server_system.ts";
|
||||
import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_space_primitives.ts";
|
||||
import { determineDatabaseBackend } from "../server/db_backend.ts";
|
||||
import { EndpointHook } from "../plugos/hooks/endpoint.ts";
|
||||
import { determineShellBackend } from "../server/shell_backend.ts";
|
||||
|
||||
export async function runPlug(
|
||||
spacePath: string,
|
||||
functionName: string | undefined,
|
||||
args: string[] = [],
|
||||
builtinAssetBundle: AssetBundle,
|
||||
httpServerPort = 3123,
|
||||
httpHostname = "127.0.0.1",
|
||||
) {
|
||||
const serverController = new AbortController();
|
||||
const app = new Application();
|
||||
|
||||
const dbBackend = await determineDatabaseBackend(spacePath);
|
||||
|
||||
if (!dbBackend) {
|
||||
console.error("Cannot run plugs in databaseless mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
const endpointHook = new EndpointHook("/_/");
|
||||
|
||||
const serverSystem = new ServerSystem(
|
||||
new AssetBundlePlugSpacePrimitives(
|
||||
new DiskSpacePrimitives(spacePath),
|
||||
builtinAssetBundle,
|
||||
),
|
||||
dbBackend,
|
||||
determineShellBackend(spacePath),
|
||||
);
|
||||
await serverSystem.init(true);
|
||||
app.use((context, next) => {
|
||||
return endpointHook.handleRequest(serverSystem.system!, context, next);
|
||||
});
|
||||
|
||||
app.listen({
|
||||
hostname: httpHostname,
|
||||
port: httpServerPort,
|
||||
signal: serverController.signal,
|
||||
});
|
||||
|
||||
if (functionName) {
|
||||
const [plugName, funcName] = functionName.split(".");
|
||||
|
||||
const plug = serverSystem.system.loadedPlugs.get(plugName);
|
||||
if (!plug) {
|
||||
throw new Error(`Plug ${plugName} not found`);
|
||||
}
|
||||
const result = await plug.invoke(funcName, args);
|
||||
await serverSystem.close();
|
||||
serverSystem.kvPrimitives.close();
|
||||
serverController.abort();
|
||||
return result;
|
||||
} else {
|
||||
console.log("Running in server mode, use Ctrl-c to stop");
|
||||
while (true) {
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import { datastore } from "$sb/syscalls.ts";
|
||||
|
||||
export async function run() {
|
||||
console.log("Hello from plug_test.ts");
|
||||
await datastore.set(["plug_test"], "Hello");
|
||||
return "Hello";
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
name: test
|
||||
requiredPermissions:
|
||||
- shell
|
||||
functions:
|
||||
run:
|
||||
path: plug_test.ts:run
|
|
@ -1,40 +0,0 @@
|
|||
import { runPlug } from "../cli/plug_run.ts";
|
||||
import { path } from "../common/deps.ts";
|
||||
import assets from "../dist/plug_asset_bundle.json" assert {
|
||||
type: "json",
|
||||
};
|
||||
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
|
||||
|
||||
export async function plugRunCommand(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
}: {
|
||||
hostname?: string;
|
||||
port?: number;
|
||||
},
|
||||
spacePath: string,
|
||||
functionName: string | undefined,
|
||||
...args: string[]
|
||||
) {
|
||||
spacePath = path.resolve(spacePath);
|
||||
console.log("Space path", spacePath);
|
||||
console.log("Function to run:", functionName, "with arguments", args);
|
||||
try {
|
||||
const result = await runPlug(
|
||||
spacePath,
|
||||
functionName,
|
||||
args,
|
||||
new AssetBundle(assets),
|
||||
port,
|
||||
hostname,
|
||||
);
|
||||
if (result) {
|
||||
console.log("Output", result);
|
||||
}
|
||||
Deno.exit(0);
|
||||
} catch (e: any) {
|
||||
console.error(e.message);
|
||||
Deno.exit(1);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { DataStore } from "../plugos/lib/datastore.ts";
|
||||
import { IDataStore } from "../plugos/lib/datastore.ts";
|
||||
import { System } from "../plugos/system.ts";
|
||||
|
||||
const indexVersionKey = ["$indexVersion"];
|
||||
|
@ -8,7 +8,7 @@ const desiredIndexVersion = 2;
|
|||
|
||||
let indexOngoing = false;
|
||||
|
||||
export async function ensureSpaceIndex(ds: DataStore, system: System<any>) {
|
||||
export async function ensureSpaceIndex(ds: IDataStore, system: System<any>) {
|
||||
const currentIndexVersion = await ds.get(indexVersionKey);
|
||||
|
||||
console.info("Current space index version", currentIndexVersion);
|
||||
|
@ -25,6 +25,6 @@ export async function ensureSpaceIndex(ds: DataStore, system: System<any>) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function markFullSpaceIndexComplete(ds: DataStore) {
|
||||
export async function markFullSpaceIndexComplete(ds: IDataStore) {
|
||||
await ds.set(indexVersionKey, desiredIndexVersion);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { System } from "../system.ts";
|
|||
import { fullQueueName } from "../lib/mq_util.ts";
|
||||
import { MQMessage } from "$sb/types.ts";
|
||||
import { MessageQueue } from "../lib/mq.ts";
|
||||
import { throttle } from "$sb/lib/async.ts";
|
||||
|
||||
type MQSubscription = {
|
||||
queue: string;
|
||||
|
@ -24,14 +25,14 @@ export class MQHook implements Hook<MQHookT> {
|
|||
this.system = system;
|
||||
system.on({
|
||||
plugLoaded: () => {
|
||||
this.reloadQueues();
|
||||
this.throttledReloadQueues();
|
||||
},
|
||||
plugUnloaded: () => {
|
||||
this.reloadQueues();
|
||||
this.throttledReloadQueues();
|
||||
},
|
||||
});
|
||||
|
||||
this.reloadQueues();
|
||||
this.throttledReloadQueues();
|
||||
}
|
||||
|
||||
stop() {
|
||||
|
@ -40,6 +41,10 @@ export class MQHook implements Hook<MQHookT> {
|
|||
this.subscriptions = [];
|
||||
}
|
||||
|
||||
throttledReloadQueues = throttle(() => {
|
||||
this.reloadQueues();
|
||||
}, 1000);
|
||||
|
||||
reloadQueues() {
|
||||
this.stop();
|
||||
for (const plug of this.system.loadedPlugs.values()) {
|
||||
|
|
|
@ -32,9 +32,10 @@ export class DataStoreMQ implements MessageQueue {
|
|||
};
|
||||
});
|
||||
|
||||
if (messages.length > 0) {
|
||||
await this.ds.batchSet(messages);
|
||||
if (messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
await this.ds.batchSet(messages);
|
||||
|
||||
// See if we can immediately process the message with a local subscription
|
||||
const localSubscriptions = this.localSubscriptions.get(queue);
|
||||
|
@ -50,6 +51,8 @@ export class DataStoreMQ implements MessageQueue {
|
|||
}
|
||||
|
||||
async poll(queue: string, maxItems: number): Promise<MQMessage[]> {
|
||||
// console.log("Polling", queue, maxItems);
|
||||
// console.trace();
|
||||
// Note: this is not happening in a transactional way, so we may get duplicate message delivery
|
||||
// Retrieve a batch of messages
|
||||
const messages = await this.ds.query<MQMessage>({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { KV, KvKey, KvQuery } from "$sb/types.ts";
|
||||
import type { DataStore } from "../lib/datastore.ts";
|
||||
import type { DataStore, IDataStore } from "../lib/datastore.ts";
|
||||
import type { SyscallContext, SysCallMapping } from "../system.ts";
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,7 @@ import type { SyscallContext, SysCallMapping } from "../system.ts";
|
|||
* @param prefix prefix to scope all keys to to which the plug name will be appended
|
||||
*/
|
||||
export function dataStoreSyscalls(
|
||||
ds: DataStore,
|
||||
ds: IDataStore,
|
||||
prefix: KvKey = ["ds"],
|
||||
): SysCallMapping {
|
||||
return {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import { SyscallContext, SysCallMapping } from "../system.ts";
|
||||
|
||||
export function proxySyscalls(
|
||||
names: string[],
|
||||
transportCall: (
|
||||
ctx: SyscallContext,
|
||||
name: string,
|
||||
...args: any[]
|
||||
) => Promise<any>,
|
||||
): SysCallMapping {
|
||||
const syscalls: SysCallMapping = {};
|
||||
|
||||
for (const name of names) {
|
||||
syscalls[name] = (ctx, ...args: any[]) => {
|
||||
return transportCall(ctx, name, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
return syscalls;
|
||||
}
|
|
@ -64,7 +64,7 @@ export async function clearIndex(): Promise<void> {
|
|||
/**
|
||||
* Indexes entities in the data store
|
||||
*/
|
||||
export async function indexObjects<T>(
|
||||
export function indexObjects<T>(
|
||||
page: string,
|
||||
objects: ObjectValue<T>[],
|
||||
): Promise<void> {
|
||||
|
@ -127,14 +127,12 @@ export async function indexObjects<T>(
|
|||
}
|
||||
}
|
||||
if (allAttributes.size > 0) {
|
||||
await indexObjects<AttributeObject>(
|
||||
page,
|
||||
[...allAttributes].map(([key, value]) => {
|
||||
const [tagName, name] = key.split(":");
|
||||
const attributeType = value.startsWith("!")
|
||||
? value.substring(1)
|
||||
: value;
|
||||
return {
|
||||
[...allAttributes].forEach(([key, value]) => {
|
||||
const [tagName, name] = key.split(":");
|
||||
const attributeType = value.startsWith("!") ? value.substring(1) : value;
|
||||
kvs.push({
|
||||
key: ["attribute", cleanKey(key, page)],
|
||||
value: {
|
||||
ref: key,
|
||||
tag: "attribute",
|
||||
tagName,
|
||||
|
@ -142,12 +140,14 @@ export async function indexObjects<T>(
|
|||
attributeType,
|
||||
readOnly: value.startsWith("!"),
|
||||
page,
|
||||
};
|
||||
}),
|
||||
);
|
||||
} as T,
|
||||
});
|
||||
});
|
||||
}
|
||||
if (kvs.length > 0) {
|
||||
return batchSet(page, kvs);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,31 +90,28 @@ export const builtins: Record<string, Record<string, string>> = {
|
|||
|
||||
export async function loadBuiltinsIntoIndex() {
|
||||
console.log("Loading builtins attributes into index");
|
||||
const allTags: ObjectValue<TagObject>[] = [];
|
||||
const allObjects: ObjectValue<any>[] = [];
|
||||
for (const [tagName, attributes] of Object.entries(builtins)) {
|
||||
allTags.push({
|
||||
allObjects.push({
|
||||
ref: tagName,
|
||||
tag: "tag",
|
||||
name: tagName,
|
||||
page: builtinPseudoPage,
|
||||
parent: "builtin",
|
||||
});
|
||||
await indexObjects<AttributeObject>(
|
||||
builtinPseudoPage,
|
||||
Object.entries(attributes).map(([name, attributeType]) => {
|
||||
return {
|
||||
ref: `${tagName}:${name}`,
|
||||
tag: "attribute",
|
||||
tagName,
|
||||
name,
|
||||
attributeType: attributeType.startsWith("!")
|
||||
? attributeType.substring(1)
|
||||
: attributeType,
|
||||
readOnly: attributeType.startsWith("!"),
|
||||
page: builtinPseudoPage,
|
||||
};
|
||||
}),
|
||||
allObjects.push(
|
||||
...Object.entries(attributes).map(([name, attributeType]) => ({
|
||||
ref: `${tagName}:${name}`,
|
||||
tag: "attribute",
|
||||
tagName,
|
||||
name,
|
||||
attributeType: attributeType.startsWith("!")
|
||||
? attributeType.substring(1)
|
||||
: attributeType,
|
||||
readOnly: attributeType.startsWith("!"),
|
||||
page: builtinPseudoPage,
|
||||
})),
|
||||
);
|
||||
}
|
||||
await indexObjects(builtinPseudoPage, allTags);
|
||||
await indexObjects(builtinPseudoPage, allObjects);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,12 @@ import {
|
|||
} from "./deps.ts";
|
||||
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
|
||||
import { FileMeta } from "$sb/types.ts";
|
||||
import { ShellRequest, SyscallRequest, SyscallResponse } from "./rpc.ts";
|
||||
import {
|
||||
handleRpc,
|
||||
ShellRequest,
|
||||
SyscallRequest,
|
||||
SyscallResponse,
|
||||
} from "./rpc.ts";
|
||||
import { determineShellBackend } from "./shell_backend.ts";
|
||||
import { SpaceServer, SpaceServerConfig } from "./instance.ts";
|
||||
import { KvPrimitives } from "../plugos/lib/kv_primitives.ts";
|
||||
|
@ -132,13 +137,6 @@ export class HttpServer {
|
|||
// Serve static files (javascript, css, html)
|
||||
this.app.use(this.serveStatic.bind(this));
|
||||
|
||||
const endpointHook = new EndpointHook("/_/");
|
||||
|
||||
this.app.use(async (context, next) => {
|
||||
const spaceServer = await this.ensureSpaceServer(context.request);
|
||||
return endpointHook.handleRequest(spaceServer.system!, context, next);
|
||||
});
|
||||
|
||||
this.addAuth(this.app);
|
||||
const fsRouter = this.addFsRoutes();
|
||||
this.app.use(fsRouter.routes());
|
||||
|
@ -395,45 +393,9 @@ export class HttpServer {
|
|||
limit: 100 * 1024 * 1024,
|
||||
}).value;
|
||||
try {
|
||||
if (operation === "shell") {
|
||||
const shellCommand: ShellRequest = body;
|
||||
const shellResponse = await spaceServer.shellBackend.handle(
|
||||
shellCommand,
|
||||
);
|
||||
response.headers.set("Content-Type", "application/json");
|
||||
response.body = JSON.stringify(shellResponse);
|
||||
return;
|
||||
} else {
|
||||
// Syscall
|
||||
if (spaceServer.syncOnly) {
|
||||
response.headers.set("Content-Type", "text/plain");
|
||||
response.status = 400;
|
||||
response.body = "Unknown operation";
|
||||
return;
|
||||
}
|
||||
const [plugName, syscall] = operation.split("/");
|
||||
const args: any[] = body;
|
||||
try {
|
||||
// console.log("Now invoking", operation, "with", args);
|
||||
const result = await spaceServer.system!.localSyscall(
|
||||
plugName,
|
||||
syscall,
|
||||
args,
|
||||
);
|
||||
response.headers.set("Content-type", "application/json");
|
||||
response.status = 200;
|
||||
response.body = JSON.stringify({
|
||||
result: result,
|
||||
} as SyscallResponse);
|
||||
} catch (e: any) {
|
||||
response.headers.set("Content-type", "application/json");
|
||||
response.status = 500;
|
||||
response.body = JSON.stringify({
|
||||
error: e.message,
|
||||
} as SyscallResponse);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const resp = await handleRpc(spaceServer, operation, body);
|
||||
response.headers.set("Content-Type", "application/json");
|
||||
response.body = JSON.stringify({ r: resp });
|
||||
} catch (e: any) {
|
||||
console.log("Error", e);
|
||||
response.status = 500;
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import { SilverBulletHooks } from "../common/manifest.ts";
|
||||
import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_space_primitives.ts";
|
||||
import { FilteredSpacePrimitives } from "../common/spaces/filtered_space_primitives.ts";
|
||||
import { SpacePrimitives } from "../common/spaces/space_primitives.ts";
|
||||
import { ensureSettingsAndIndex } from "../common/util.ts";
|
||||
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
|
||||
import { DataStore } from "../plugos/lib/datastore.ts";
|
||||
import { KvPrimitives } from "../plugos/lib/kv_primitives.ts";
|
||||
import { System } from "../plugos/system.ts";
|
||||
import { PrefixedKvPrimitives } from "../plugos/lib/prefixed_kv_primitives.ts";
|
||||
import { BuiltinSettings } from "../web/types.ts";
|
||||
import { JWTIssuer } from "./crypto.ts";
|
||||
import { gitIgnoreCompiler } from "./deps.ts";
|
||||
import { ServerSystem } from "./server_system.ts";
|
||||
import { ShellBackend } from "./shell_backend.ts";
|
||||
import { determineStorageBackend } from "./storage_backend.ts";
|
||||
|
||||
|
@ -31,16 +30,14 @@ export class SpaceServer {
|
|||
authToken?: string;
|
||||
hostname: string;
|
||||
|
||||
private settings?: BuiltinSettings;
|
||||
// private settings?: BuiltinSettings;
|
||||
spacePrimitives!: SpacePrimitives;
|
||||
|
||||
jwtIssuer: JWTIssuer;
|
||||
|
||||
// Only set when syncOnly == false
|
||||
private serverSystem?: ServerSystem;
|
||||
system?: System<SilverBulletHooks>;
|
||||
clientEncryption: boolean;
|
||||
syncOnly: boolean;
|
||||
ds: DataStore;
|
||||
|
||||
constructor(
|
||||
config: SpaceServerConfig,
|
||||
|
@ -60,38 +57,15 @@ export class SpaceServer {
|
|||
}
|
||||
|
||||
this.jwtIssuer = new JWTIssuer(kvPrimitives);
|
||||
this.ds = new DataStore(new PrefixedKvPrimitives(kvPrimitives, ["ds"]));
|
||||
}
|
||||
|
||||
async init() {
|
||||
let fileFilterFn: (s: string) => boolean = () => true;
|
||||
|
||||
this.spacePrimitives = new FilteredSpacePrimitives(
|
||||
new AssetBundlePlugSpacePrimitives(
|
||||
await determineStorageBackend(this.kvPrimitives, this.pagesPath),
|
||||
this.plugAssetBundle,
|
||||
),
|
||||
(meta) => fileFilterFn(meta.name),
|
||||
async () => {
|
||||
await this.reloadSettings();
|
||||
if (typeof this.settings?.spaceIgnore === "string") {
|
||||
fileFilterFn = gitIgnoreCompiler(this.settings.spaceIgnore).accepts;
|
||||
} else {
|
||||
fileFilterFn = () => true;
|
||||
}
|
||||
},
|
||||
this.spacePrimitives = new AssetBundlePlugSpacePrimitives(
|
||||
await determineStorageBackend(this.kvPrimitives, this.pagesPath),
|
||||
this.plugAssetBundle,
|
||||
);
|
||||
|
||||
// system = undefined in databaseless mode (no PlugOS instance on the server and no DB)
|
||||
if (!this.syncOnly) {
|
||||
// Enable server-side processing
|
||||
const serverSystem = new ServerSystem(
|
||||
this.spacePrimitives,
|
||||
this.kvPrimitives,
|
||||
this.shellBackend,
|
||||
);
|
||||
this.serverSystem = serverSystem;
|
||||
}
|
||||
|
||||
if (this.auth) {
|
||||
// Initialize JWT issuer
|
||||
await this.jwtIssuer.init(
|
||||
|
@ -99,25 +73,6 @@ export class SpaceServer {
|
|||
);
|
||||
}
|
||||
|
||||
if (this.serverSystem) {
|
||||
await this.serverSystem.init();
|
||||
this.system = this.serverSystem.system;
|
||||
// Swap in the space primitives from the server system
|
||||
this.spacePrimitives = this.serverSystem.spacePrimitives;
|
||||
}
|
||||
|
||||
await this.reloadSettings();
|
||||
console.log("Booted server with hostname", this.hostname);
|
||||
}
|
||||
|
||||
async reloadSettings() {
|
||||
if (!this.clientEncryption) {
|
||||
// Only attempt this when the space is not encrypted
|
||||
this.settings = await ensureSettingsAndIndex(this.spacePrimitives);
|
||||
} else {
|
||||
this.settings = {
|
||||
indexPage: "index",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { shell } from "$sb/syscalls.ts";
|
||||
import { KV, KvKey, KvQuery } from "$sb/types.ts";
|
||||
import { SpaceServer } from "./instance.ts";
|
||||
|
||||
export type ShellRequest = {
|
||||
cmd: string;
|
||||
args: string[];
|
||||
|
@ -19,3 +23,37 @@ export type SyscallResponse = {
|
|||
result?: any;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export async function handleRpc(
|
||||
spaceServer: SpaceServer,
|
||||
name: string,
|
||||
body: any,
|
||||
): Promise<any> {
|
||||
switch (name) {
|
||||
case "shell": {
|
||||
const shellCommand: ShellRequest = body;
|
||||
const shellResponse = await spaceServer.shellBackend.handle(
|
||||
shellCommand,
|
||||
);
|
||||
return shellResponse;
|
||||
}
|
||||
case "datastore.batchGet": {
|
||||
const [keys]: [KvKey[]] = body;
|
||||
return spaceServer.ds.batchGet(keys);
|
||||
}
|
||||
case "datastore.batchSet": {
|
||||
const [entries]: [KV[]] = body;
|
||||
return spaceServer.ds.batchSet(entries);
|
||||
}
|
||||
case "datastore.batchDelete": {
|
||||
const [keys]: [KvKey[]] = body;
|
||||
return spaceServer.ds.batchDelete(keys);
|
||||
}
|
||||
case "datastore.query": {
|
||||
const [query]: [KvQuery] = body;
|
||||
return spaceServer.ds.query(query);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown rpc ${name}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,198 +0,0 @@
|
|||
import { PlugNamespaceHook } from "../common/hooks/plug_namespace.ts";
|
||||
import { SilverBulletHooks } from "../common/manifest.ts";
|
||||
import { loadMarkdownExtensions } from "../common/markdown_parser/markdown_ext.ts";
|
||||
import buildMarkdown from "../common/markdown_parser/parser.ts";
|
||||
import { EventedSpacePrimitives } from "../common/spaces/evented_space_primitives.ts";
|
||||
import { PlugSpacePrimitives } from "../common/spaces/plug_space_primitives.ts";
|
||||
import { createSandbox } from "../plugos/environments/webworker_sandbox.ts";
|
||||
import { CronHook } from "../plugos/hooks/cron.ts";
|
||||
import { EventHook } from "../plugos/hooks/event.ts";
|
||||
import { MQHook } from "../plugos/hooks/mq.ts";
|
||||
import assetSyscalls from "../plugos/syscalls/asset.ts";
|
||||
import { eventSyscalls } from "../plugos/syscalls/event.ts";
|
||||
import { mqSyscalls } from "../plugos/syscalls/mq.ts";
|
||||
import { System } from "../plugos/system.ts";
|
||||
import { Space } from "../web/space.ts";
|
||||
import { debugSyscalls } from "../web/syscalls/debug.ts";
|
||||
import { markdownSyscalls } from "../common/syscalls/markdown.ts";
|
||||
import { spaceSyscalls } from "./syscalls/space.ts";
|
||||
import { systemSyscalls } from "../web/syscalls/system.ts";
|
||||
import { yamlSyscalls } from "../common/syscalls/yaml.ts";
|
||||
import { sandboxFetchSyscalls } from "../plugos/syscalls/fetch.ts";
|
||||
import { shellSyscalls } from "./syscalls/shell.ts";
|
||||
import { SpacePrimitives } from "../common/spaces/space_primitives.ts";
|
||||
import { base64EncodedDataUrl } from "../plugos/asset_bundle/base64.ts";
|
||||
import { Plug } from "../plugos/plug.ts";
|
||||
import { DataStore } from "../plugos/lib/datastore.ts";
|
||||
import { dataStoreSyscalls } from "../plugos/syscalls/datastore.ts";
|
||||
import { DataStoreMQ } from "../plugos/lib/mq.datastore.ts";
|
||||
import { languageSyscalls } from "../common/syscalls/language.ts";
|
||||
import { handlebarsSyscalls } from "../common/syscalls/handlebars.ts";
|
||||
import { codeWidgetSyscalls } from "../web/syscalls/code_widget.ts";
|
||||
import { CodeWidgetHook } from "../web/hooks/code_widget.ts";
|
||||
import { KVPrimitivesManifestCache } from "../plugos/manifest_cache.ts";
|
||||
import { KvPrimitives } from "../plugos/lib/kv_primitives.ts";
|
||||
import { ShellBackend } from "./shell_backend.ts";
|
||||
import { ensureSpaceIndex } from "../common/space_index.ts";
|
||||
|
||||
const fileListInterval = 30 * 1000; // 30s
|
||||
|
||||
const plugNameExtractRegex = /([^/]+)\.plug\.js$/;
|
||||
|
||||
export class ServerSystem {
|
||||
system!: System<SilverBulletHooks>;
|
||||
public spacePrimitives!: SpacePrimitives;
|
||||
// denoKv!: Deno.Kv;
|
||||
listInterval?: number;
|
||||
ds!: DataStore;
|
||||
|
||||
constructor(
|
||||
private baseSpacePrimitives: SpacePrimitives,
|
||||
readonly kvPrimitives: KvPrimitives,
|
||||
private shellBackend: ShellBackend,
|
||||
) {
|
||||
}
|
||||
|
||||
// Always needs to be invoked right after construction
|
||||
async init(awaitIndex = false) {
|
||||
this.ds = new DataStore(this.kvPrimitives);
|
||||
|
||||
this.system = new System(
|
||||
"server",
|
||||
{
|
||||
manifestCache: new KVPrimitivesManifestCache(
|
||||
this.kvPrimitives,
|
||||
"manifest",
|
||||
),
|
||||
plugFlushTimeout: 5 * 60 * 1000, // 5 minutes
|
||||
},
|
||||
);
|
||||
|
||||
// Event hook
|
||||
const eventHook = new EventHook();
|
||||
this.system.addHook(eventHook);
|
||||
|
||||
// Cron hook
|
||||
const cronHook = new CronHook(this.system);
|
||||
this.system.addHook(cronHook);
|
||||
|
||||
const mq = new DataStoreMQ(this.ds);
|
||||
|
||||
setInterval(() => {
|
||||
// Timeout after 5s, retries 3 times, otherwise drops the message (no DLQ)
|
||||
mq.requeueTimeouts(5000, 3, true).catch(console.error);
|
||||
}, 20000); // Look to requeue every 20s
|
||||
|
||||
const plugNamespaceHook = new PlugNamespaceHook();
|
||||
this.system.addHook(plugNamespaceHook);
|
||||
|
||||
this.system.addHook(new MQHook(this.system, mq));
|
||||
|
||||
const codeWidgetHook = new CodeWidgetHook();
|
||||
|
||||
this.system.addHook(codeWidgetHook);
|
||||
|
||||
this.spacePrimitives = new EventedSpacePrimitives(
|
||||
new PlugSpacePrimitives(
|
||||
this.baseSpacePrimitives,
|
||||
plugNamespaceHook,
|
||||
),
|
||||
eventHook,
|
||||
);
|
||||
const space = new Space(this.spacePrimitives, eventHook);
|
||||
|
||||
// Add syscalls
|
||||
this.system.registerSyscalls(
|
||||
[],
|
||||
eventSyscalls(eventHook),
|
||||
spaceSyscalls(space),
|
||||
assetSyscalls(this.system),
|
||||
yamlSyscalls(),
|
||||
systemSyscalls(this.system),
|
||||
mqSyscalls(mq),
|
||||
languageSyscalls(),
|
||||
handlebarsSyscalls(),
|
||||
dataStoreSyscalls(this.ds),
|
||||
debugSyscalls(),
|
||||
codeWidgetSyscalls(codeWidgetHook),
|
||||
markdownSyscalls(buildMarkdown([])), // Will later be replaced with markdown extensions
|
||||
);
|
||||
|
||||
// Syscalls that require some additional permissions
|
||||
this.system.registerSyscalls(
|
||||
["fetch"],
|
||||
sandboxFetchSyscalls(),
|
||||
);
|
||||
|
||||
this.system.registerSyscalls(
|
||||
["shell"],
|
||||
shellSyscalls(this.shellBackend),
|
||||
);
|
||||
|
||||
await this.loadPlugs();
|
||||
|
||||
// Load markdown syscalls based on all new syntax (if any)
|
||||
this.system.registerSyscalls(
|
||||
[],
|
||||
markdownSyscalls(buildMarkdown(loadMarkdownExtensions(this.system))),
|
||||
);
|
||||
|
||||
this.listInterval = setInterval(() => {
|
||||
space.updatePageList().catch(console.error);
|
||||
}, fileListInterval);
|
||||
|
||||
eventHook.addLocalListener("file:changed", (path, localChange) => {
|
||||
(async () => {
|
||||
if (!localChange && path.endsWith(".md")) {
|
||||
const pageName = path.slice(0, -3);
|
||||
const data = await this.spacePrimitives.readFile(path);
|
||||
console.log("Outside page change: reindexing", pageName);
|
||||
// Change made outside of editor, trigger reindex
|
||||
await eventHook.dispatchEvent("page:index_text", {
|
||||
name: pageName,
|
||||
text: new TextDecoder().decode(data.data),
|
||||
});
|
||||
}
|
||||
|
||||
if (path.startsWith("_plug/") && path.endsWith(".plug.js")) {
|
||||
console.log("Plug updated, reloading:", path);
|
||||
this.system.unload(path);
|
||||
await this.loadPlugFromSpace(path);
|
||||
}
|
||||
})().catch(console.error);
|
||||
});
|
||||
|
||||
// Ensure a valid index
|
||||
const indexPromise = ensureSpaceIndex(this.ds, this.system);
|
||||
if (awaitIndex) {
|
||||
await indexPromise;
|
||||
}
|
||||
|
||||
await eventHook.dispatchEvent("system:ready");
|
||||
}
|
||||
|
||||
async loadPlugs() {
|
||||
for (const { name } of await this.spacePrimitives.fetchFileList()) {
|
||||
if (plugNameExtractRegex.test(name)) {
|
||||
await this.loadPlugFromSpace(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadPlugFromSpace(path: string): Promise<Plug<SilverBulletHooks>> {
|
||||
const { meta, data } = await this.spacePrimitives.readFile(path);
|
||||
const plugName = path.match(plugNameExtractRegex)![1];
|
||||
return this.system.load(
|
||||
// Base64 encoding this to support `deno compile` mode
|
||||
new URL(base64EncodedDataUrl("application/javascript", data)),
|
||||
plugName,
|
||||
meta.lastModified,
|
||||
createSandbox,
|
||||
);
|
||||
}
|
||||
|
||||
async close() {
|
||||
clearInterval(this.listInterval);
|
||||
await this.system.unloadAll();
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ import { upgradeCommand } from "./cmd/upgrade.ts";
|
|||
import { versionCommand } from "./cmd/version.ts";
|
||||
import { serveCommand } from "./cmd/server.ts";
|
||||
import { plugCompileCommand } from "./cmd/plug_compile.ts";
|
||||
import { plugRunCommand } from "./cmd/plug_run.ts";
|
||||
import { syncCommand } from "./cmd/sync.ts";
|
||||
|
||||
await new Command()
|
||||
|
@ -72,15 +71,6 @@ await new Command()
|
|||
.option("--importmap <path:string>", "Path to import map file to use")
|
||||
.option("--runtimeUrl <url:string>", "URL to worker_runtime.ts to use")
|
||||
.action(plugCompileCommand)
|
||||
// plug:run
|
||||
.command("plug:run", "Run a PlugOS function from the CLI")
|
||||
.arguments("<spacePath> [function] [...args:string]")
|
||||
.option(
|
||||
"--hostname, -L <hostname:string>",
|
||||
"Hostname or address to listen on",
|
||||
)
|
||||
.option("-p, --port <port:number>", "Port to listen on")
|
||||
.action(plugRunCommand)
|
||||
// upgrade
|
||||
.command("upgrade", "Upgrade SilverBullet")
|
||||
.action(upgradeCommand)
|
||||
|
|
|
@ -109,12 +109,12 @@ export class Client {
|
|||
|
||||
ui!: MainUI;
|
||||
openPages!: OpenPages;
|
||||
stateDataStore!: DataStore;
|
||||
mq!: DataStoreMQ;
|
||||
|
||||
// Used by the "wiki link" highlighter to check if a page exists
|
||||
public allKnownPages = new Set<string>();
|
||||
remoteDataStore!: IDataStore;
|
||||
clientDS!: DataStore;
|
||||
mq!: DataStoreMQ;
|
||||
ds!: IDataStore;
|
||||
|
||||
constructor(
|
||||
private parent: Element,
|
||||
|
@ -140,15 +140,15 @@ export class Client {
|
|||
`${this.dbPrefix}_state`,
|
||||
);
|
||||
await stateKvPrimitives.init();
|
||||
this.stateDataStore = new DataStore(stateKvPrimitives);
|
||||
this.clientDS = new DataStore(stateKvPrimitives);
|
||||
|
||||
// Only used in online mode
|
||||
this.remoteDataStore = new RemoteDataStore(this.httpSpacePrimitives);
|
||||
// In sync mode, reuse the clientDS, otherwise talk to a remote data store (over HTTP)
|
||||
this.ds = this.syncMode
|
||||
? this.clientDS
|
||||
: new RemoteDataStore(this.httpSpacePrimitives);
|
||||
|
||||
// Setup message queue
|
||||
this.mq = new DataStoreMQ(
|
||||
this.syncMode ? this.stateDataStore : this.remoteDataStore,
|
||||
);
|
||||
this.mq = new DataStoreMQ(this.ds);
|
||||
|
||||
setInterval(() => {
|
||||
// Timeout after 5s, retries 3 times, otherwise drops the message (no DLQ)
|
||||
|
@ -162,7 +162,8 @@ export class Client {
|
|||
this.system = new ClientSystem(
|
||||
this,
|
||||
this.mq,
|
||||
this.stateDataStore,
|
||||
this.clientDS,
|
||||
this.ds,
|
||||
this.eventHook,
|
||||
);
|
||||
|
||||
|
@ -172,7 +173,7 @@ export class Client {
|
|||
? new SyncService(
|
||||
localSpacePrimitives,
|
||||
this.plugSpaceRemotePrimitives,
|
||||
this.stateDataStore,
|
||||
this.clientDS,
|
||||
this.eventHook,
|
||||
(path) => {
|
||||
// TODO: At some point we should remove the data.db exception here
|
||||
|
@ -252,6 +253,12 @@ export class Client {
|
|||
private async initSync() {
|
||||
this.syncService.start();
|
||||
|
||||
if (!this.syncMode) {
|
||||
ensureSpaceIndex(this.ds, this.system.system).catch(
|
||||
console.error,
|
||||
);
|
||||
}
|
||||
|
||||
// We're still booting, if a initial sync has already been completed we know this is the initial sync
|
||||
const initialSync = !await this.syncService.hasInitialSyncCompleted();
|
||||
|
||||
|
@ -270,12 +277,12 @@ export class Client {
|
|||
// A full sync just completed
|
||||
if (!initialSync) {
|
||||
// If this was NOT the initial sync let's check if we need to perform a space reindex
|
||||
ensureSpaceIndex(this.stateDataStore, this.system.system).catch(
|
||||
ensureSpaceIndex(this.ds, this.system.system).catch(
|
||||
console.error,
|
||||
);
|
||||
} else {
|
||||
// This was the initial sync, let's mark a full index as completed
|
||||
await markFullSpaceIndexComplete(this.stateDataStore);
|
||||
await markFullSpaceIndexComplete(this.ds);
|
||||
}
|
||||
}
|
||||
if (operations) {
|
||||
|
@ -368,14 +375,14 @@ export class Client {
|
|||
scrollIntoView: true,
|
||||
});
|
||||
}
|
||||
await this.stateDataStore.set(["client", "lastOpenedPage"], pageName);
|
||||
await this.clientDS.set(["client", "lastOpenedPage"], pageName);
|
||||
},
|
||||
);
|
||||
|
||||
if (location.hash === "#boot") {
|
||||
(async () => {
|
||||
// Cold start PWA load
|
||||
const lastPage = await this.stateDataStore.get([
|
||||
const lastPage = await this.clientDS.get([
|
||||
"client",
|
||||
"lastOpenedPage",
|
||||
]);
|
||||
|
@ -420,7 +427,7 @@ export class Client {
|
|||
}
|
||||
await encryptedSpacePrimitives.setup(password);
|
||||
// this.stateDataStore.set(["encryptionKey"], password);
|
||||
await this.stateDataStore.set(
|
||||
await this.ds.set(
|
||||
["spaceSalt"],
|
||||
encryptedSpacePrimitives.spaceSalt,
|
||||
);
|
||||
|
@ -432,7 +439,7 @@ export class Client {
|
|||
"Offline, will assume encryption space is initialized, fetching salt from data store",
|
||||
);
|
||||
await encryptedSpacePrimitives.init(
|
||||
await this.stateDataStore.get(["spaceSalt"]),
|
||||
await this.ds.get(["spaceSalt"]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -442,7 +449,7 @@ export class Client {
|
|||
await encryptedSpacePrimitives.login(
|
||||
prompt("Password")!,
|
||||
);
|
||||
await this.stateDataStore.set(
|
||||
await this.ds.set(
|
||||
["spaceSalt"],
|
||||
encryptedSpacePrimitives.spaceSalt,
|
||||
);
|
||||
|
@ -1042,7 +1049,7 @@ export class Client {
|
|||
|
||||
async loadCaches() {
|
||||
const [widgetHeightCache, widgetCache] = await this
|
||||
.stateDataStore.batchGet([[
|
||||
.clientDS.batchGet([[
|
||||
"cache",
|
||||
"widgetHeight",
|
||||
], ["cache", "widgets"]]);
|
||||
|
@ -1051,7 +1058,7 @@ export class Client {
|
|||
}
|
||||
|
||||
debouncedWidgetHeightCacheFlush = throttle(() => {
|
||||
this.stateDataStore.set(
|
||||
this.clientDS.set(
|
||||
["cache", "widgetHeight"],
|
||||
this.widgetHeightCache.toJSON(),
|
||||
)
|
||||
|
@ -1070,7 +1077,7 @@ export class Client {
|
|||
}
|
||||
|
||||
debouncedWidgetCacheFlush = throttle(() => {
|
||||
this.stateDataStore.set(["cache", "widgets"], this.widgetCache.toJSON())
|
||||
this.clientDS.set(["cache", "widgets"], this.widgetCache.toJSON())
|
||||
.catch(
|
||||
console.error,
|
||||
);
|
||||
|
|
|
@ -29,9 +29,8 @@ import {
|
|||
} from "../common/markdown_parser/markdown_ext.ts";
|
||||
import { MQHook } from "../plugos/hooks/mq.ts";
|
||||
import { mqSyscalls } from "../plugos/syscalls/mq.ts";
|
||||
import { dataStoreProxySyscalls } from "./syscalls/datastore.proxy.ts";
|
||||
import { dataStoreSyscalls } from "../plugos/syscalls/datastore.ts";
|
||||
import { DataStore } from "../plugos/lib/datastore.ts";
|
||||
import { DataStore, IDataStore } from "../plugos/lib/datastore.ts";
|
||||
import { MessageQueue } from "../plugos/lib/mq.ts";
|
||||
import { languageSyscalls } from "../common/syscalls/language.ts";
|
||||
import { handlebarsSyscalls } from "../common/syscalls/handlebars.ts";
|
||||
|
@ -56,7 +55,8 @@ export class ClientSystem {
|
|||
constructor(
|
||||
private client: Client,
|
||||
private mq: MessageQueue,
|
||||
private ds: DataStore,
|
||||
private clientDs: DataStore,
|
||||
private dataStore: IDataStore,
|
||||
private eventHook: EventHook,
|
||||
) {
|
||||
// Only set environment to "client" when running in thin client mode, otherwise we run everything locally (hybrid)
|
||||
|
@ -65,7 +65,7 @@ export class ClientSystem {
|
|||
undefined,
|
||||
{
|
||||
manifestCache: new KVPrimitivesManifestCache<SilverBulletHooks>(
|
||||
ds.kv,
|
||||
clientDs.kv,
|
||||
"manifest",
|
||||
),
|
||||
},
|
||||
|
@ -165,12 +165,10 @@ export class ClientSystem {
|
|||
clientCodeWidgetSyscalls(),
|
||||
languageSyscalls(),
|
||||
mqSyscalls(this.mq),
|
||||
this.client.syncMode
|
||||
? dataStoreSyscalls(this.ds)
|
||||
: dataStoreProxySyscalls(this.client),
|
||||
dataStoreSyscalls(this.dataStore),
|
||||
debugSyscalls(),
|
||||
syncSyscalls(this.client),
|
||||
clientStoreSyscalls(this.ds),
|
||||
clientStoreSyscalls(this.clientDs),
|
||||
);
|
||||
|
||||
// Syscalls that require some additional permissions
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { HttpSpacePrimitives } from "../common/spaces/http_space_primitives.ts";
|
||||
import { KV, KvKey, KvQuery } from "$sb/types.ts";
|
||||
import { proxySyscall } from "./syscalls/util.ts";
|
||||
import { IDataStore } from "../plugos/lib/datastore.ts";
|
||||
import { rpcCall } from "./syscalls/datastore.proxy.ts";
|
||||
|
||||
// implements DataStore "interface"
|
||||
export class RemoteDataStore implements IDataStore {
|
||||
|
@ -10,45 +10,46 @@ export class RemoteDataStore implements IDataStore {
|
|||
|
||||
private proxy(
|
||||
name: string,
|
||||
args: any[],
|
||||
...args: any[]
|
||||
) {
|
||||
return proxySyscall(
|
||||
{ plug: { name: "index" } } as any,
|
||||
// console.trace();
|
||||
return rpcCall(
|
||||
this.httpPrimitives,
|
||||
name,
|
||||
args,
|
||||
...args,
|
||||
);
|
||||
}
|
||||
|
||||
get<T = any>(key: KvKey): Promise<T | null> {
|
||||
return this.proxy("datastore.get", [key]);
|
||||
async get<T = any>(key: KvKey): Promise<T | null> {
|
||||
const results = await this.batchGet([key]);
|
||||
return results[0];
|
||||
}
|
||||
|
||||
batchGet<T = any>(keys: KvKey[]): Promise<(T | null)[]> {
|
||||
return this.proxy("datastore.batchGet", [keys]);
|
||||
return this.proxy("datastore.batchGet", keys);
|
||||
}
|
||||
|
||||
set(key: KvKey, value: any): Promise<void> {
|
||||
return this.proxy("datastore.set", [key, value]);
|
||||
return this.batchSet([{ key, value }]);
|
||||
}
|
||||
|
||||
batchSet<T = any>(entries: KV<T>[]): Promise<void> {
|
||||
return this.proxy("datastore.batchSet", [entries]);
|
||||
return this.proxy("datastore.batchSet", entries);
|
||||
}
|
||||
|
||||
delete(key: KvKey): Promise<void> {
|
||||
return this.proxy("datastore.delete", [key]);
|
||||
return this.batchDelete([key]);
|
||||
}
|
||||
|
||||
batchDelete(keys: KvKey[]): Promise<void> {
|
||||
return this.proxy("datastore.batchDelete", [keys]);
|
||||
return this.proxy("datastore.batchDelete", keys);
|
||||
}
|
||||
|
||||
query<T = any>(query: KvQuery): Promise<KV<T>[]> {
|
||||
return this.proxy("datastore.query", [query]);
|
||||
return this.proxy("datastore.query", query);
|
||||
}
|
||||
|
||||
queryDelete(query: KvQuery): Promise<void> {
|
||||
return this.proxy("datastore.queryDelete", [query]);
|
||||
return this.proxy("datastore.queryDelete", query);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,90 @@
|
|||
import type { SysCallMapping } from "../../plugos/system.ts";
|
||||
import type { Client } from "../client.ts";
|
||||
import { proxySyscalls } from "./util.ts";
|
||||
import { HttpSpacePrimitives } from "../../common/spaces/http_space_primitives.ts";
|
||||
import { KV, KvKey, KvQuery } from "$sb/types.ts";
|
||||
import type { SyscallContext, SysCallMapping } from "../../plugos/system.ts";
|
||||
|
||||
export function dataStoreProxySyscalls(client: Client): SysCallMapping {
|
||||
return proxySyscalls(client, [
|
||||
"datastore.delete",
|
||||
"datastore.set",
|
||||
"datastore.batchSet",
|
||||
"datastore.batchDelete",
|
||||
"datastore.batchGet",
|
||||
"datastore.get",
|
||||
"datastore.query",
|
||||
]);
|
||||
export function dataStoreProxySyscalls(
|
||||
httpSpacePrimitives: HttpSpacePrimitives,
|
||||
): SysCallMapping {
|
||||
return {
|
||||
"datastore.delete": (ctx, key: KvKey) => {
|
||||
return rpcCall(httpSpacePrimitives, "datastore.batchDelete", [
|
||||
addPrefix(ctx, key),
|
||||
]);
|
||||
},
|
||||
"datastore.batchDelete": (ctx, keys: KvKey[]) => {
|
||||
return rpcCall(
|
||||
httpSpacePrimitives,
|
||||
"datastore.batchDelete",
|
||||
keys.map((key) => addPrefix(ctx, key)),
|
||||
);
|
||||
},
|
||||
"datastore.set": (ctx, key: KvKey, value: any) => {
|
||||
return rpcCall(httpSpacePrimitives, "datastore.batchSet", [
|
||||
{ key: addPrefix(ctx, key), value: value },
|
||||
]);
|
||||
},
|
||||
"datastore.batchSet": (ctx, entries: KV[]) => {
|
||||
return rpcCall(
|
||||
httpSpacePrimitives,
|
||||
"datastore.batchSet",
|
||||
entries.map(({ key, value }) => ({ key: addPrefix(ctx, key), value })),
|
||||
);
|
||||
},
|
||||
"datastore.get": async (ctx, key: KvKey) => {
|
||||
const [result] = await rpcCall(
|
||||
httpSpacePrimitives,
|
||||
"datastore.batchGet",
|
||||
[
|
||||
addPrefix(ctx, key),
|
||||
],
|
||||
);
|
||||
return result;
|
||||
},
|
||||
"datastore.batchGet": (ctx, keys: KvKey[]) => {
|
||||
return rpcCall(
|
||||
httpSpacePrimitives,
|
||||
"datastore.batchGet",
|
||||
keys.map((key) => addPrefix(ctx, key)),
|
||||
);
|
||||
},
|
||||
"datastore.query": async (ctx, query: KvQuery) => {
|
||||
const results: KV[] = await rpcCall(
|
||||
httpSpacePrimitives,
|
||||
"datastore.query",
|
||||
{ ...query, prefix: addPrefix(ctx, query.prefix || []) },
|
||||
);
|
||||
return results.map(({ key, value }) => ({
|
||||
key: stripPrefix(key),
|
||||
value,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
function addPrefix(ctx: SyscallContext, key: KvKey): KvKey {
|
||||
return [ctx.plug.name, ...key];
|
||||
}
|
||||
|
||||
function stripPrefix(key: KvKey): KvKey {
|
||||
return key.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
export async function rpcCall(
|
||||
httpSpacePrimitives: HttpSpacePrimitives,
|
||||
name: string,
|
||||
...args: any[]
|
||||
): Promise<any> {
|
||||
const resp = await httpSpacePrimitives.authenticatedFetch(
|
||||
`${httpSpacePrimitives.url}/.rpc/${name}`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(args),
|
||||
},
|
||||
);
|
||||
if (!resp.ok) {
|
||||
const errorText = await resp.text();
|
||||
console.error("Remote rpc error", errorText);
|
||||
throw new Error(errorText);
|
||||
}
|
||||
return (await resp.json()).r;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import type { Plug } from "../../plugos/plug.ts";
|
|||
import { SysCallMapping, System } from "../../plugos/system.ts";
|
||||
import type { Client } from "../client.ts";
|
||||
import { CommandDef } from "../hooks/command.ts";
|
||||
import { proxySyscall } from "./util.ts";
|
||||
|
||||
export function systemSyscalls(
|
||||
system: System<any>,
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import { HttpSpacePrimitives } from "../../common/spaces/http_space_primitives.ts";
|
||||
import { SyscallContext, SysCallMapping } from "../../plugos/system.ts";
|
||||
import { SyscallResponse } from "../../server/rpc.ts";
|
||||
import { Client } from "../client.ts";
|
||||
|
||||
export function proxySyscalls(client: Client, names: string[]): SysCallMapping {
|
||||
const syscalls: SysCallMapping = {};
|
||||
for (const name of names) {
|
||||
syscalls[name] = (ctx, ...args: any[]) => {
|
||||
return proxySyscall(ctx, client.httpSpacePrimitives, name, args);
|
||||
};
|
||||
}
|
||||
return syscalls;
|
||||
}
|
||||
|
||||
export async function proxySyscall(
|
||||
ctx: SyscallContext,
|
||||
httpSpacePrimitives: HttpSpacePrimitives,
|
||||
name: string,
|
||||
args: any[],
|
||||
): Promise<any> {
|
||||
const resp = await httpSpacePrimitives.authenticatedFetch(
|
||||
`${httpSpacePrimitives.url}/.rpc/${ctx.plug.name}/${name}`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(args),
|
||||
},
|
||||
);
|
||||
const result: SyscallResponse = await resp.json();
|
||||
if (result.error) {
|
||||
console.error("Remote syscall error", result.error);
|
||||
throw new Error(result.error);
|
||||
} else {
|
||||
return result.result;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue