WIP: CLI running of plugs
This commit is contained in:
@@ -6,8 +6,8 @@ export function createSandbox<HookT>(plug: Plug<HookT>): Sandbox<HookT> {
|
||||
return new Sandbox(plug, {
|
||||
deno: {
|
||||
permissions: {
|
||||
// Disallow network access
|
||||
net: false,
|
||||
// Allow network access
|
||||
net: true,
|
||||
// This is required for console logging to work, apparently?
|
||||
env: true,
|
||||
// No talking to native code
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { assertEquals } from "../../test_deps.ts";
|
||||
import { DenoKVStore } from "./kv_store.deno_kv.ts";
|
||||
|
||||
Deno.test("Test KV index", async () => {
|
||||
const kv = new DenoKVStore();
|
||||
await kv.init("test.db");
|
||||
|
||||
await kv.set("name", "Peter");
|
||||
assertEquals(await kv.get("name"), "Peter");
|
||||
await kv.del("name");
|
||||
assertEquals(await kv.has("name"), false);
|
||||
|
||||
await kv.batchSet([
|
||||
{ key: "page:hello", value: "Hello" },
|
||||
{ key: "page:hello2", value: "Hello 2" },
|
||||
{ key: "page:hello3", value: "Hello 3" },
|
||||
{ key: "something", value: "Something" },
|
||||
]);
|
||||
|
||||
const results = await kv.queryPrefix("page:");
|
||||
assertEquals(results.length, 3);
|
||||
|
||||
assertEquals(await kv.batchGet(["page:hello", "page:hello3"]), [
|
||||
"Hello",
|
||||
"Hello 3",
|
||||
]);
|
||||
|
||||
await kv.delete();
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
/// <reference lib="deno.unstable" />
|
||||
|
||||
import { KV, KVStore } from "./kv_store.ts";
|
||||
|
||||
export class DenoKVStore implements KVStore {
|
||||
kv!: Deno.Kv;
|
||||
path: string | undefined;
|
||||
|
||||
async init(path?: string) {
|
||||
this.path = path;
|
||||
this.kv = await Deno.openKv(path);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.kv.close();
|
||||
}
|
||||
|
||||
async delete() {
|
||||
this.kv.close();
|
||||
if (this.path) {
|
||||
await Deno.remove(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
const res = await this.kv.atomic()
|
||||
.delete([key])
|
||||
.commit();
|
||||
if (!res.ok) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
async deletePrefix(prefix: string): Promise<void> {
|
||||
for await (
|
||||
const result of this.kv.list({
|
||||
start: [prefix],
|
||||
end: [endRange(prefix)],
|
||||
})
|
||||
) {
|
||||
await this.del(result.key[0] as string);
|
||||
}
|
||||
}
|
||||
async deleteAll(): Promise<void> {
|
||||
for await (
|
||||
const result of this.kv.list({ prefix: [] })
|
||||
) {
|
||||
await this.del(result.key[0] as string);
|
||||
}
|
||||
}
|
||||
async set(key: string, value: any): Promise<void> {
|
||||
const res = await this.kv.atomic()
|
||||
.set([key], value)
|
||||
.commit();
|
||||
if (!res.ok) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
async batchSet(kvs: KV[]): Promise<void> {
|
||||
for (const { key, value } of kvs) {
|
||||
await this.set(key, value);
|
||||
}
|
||||
}
|
||||
async batchDelete(keys: string[]): Promise<void> {
|
||||
for (const key of keys) {
|
||||
await this.del(key);
|
||||
}
|
||||
}
|
||||
batchGet(keys: string[]): Promise<any[]> {
|
||||
const results: Promise<any>[] = [];
|
||||
for (const key of keys) {
|
||||
results.push(this.get(key));
|
||||
}
|
||||
return Promise.all(results);
|
||||
}
|
||||
async get(key: string): Promise<any> {
|
||||
return (await this.kv.get([key])).value;
|
||||
}
|
||||
async has(key: string): Promise<boolean> {
|
||||
return (await this.kv.get([key])).value !== null;
|
||||
}
|
||||
async queryPrefix(keyPrefix: string): Promise<{ key: string; value: any }[]> {
|
||||
const results: { key: string; value: any }[] = [];
|
||||
for await (
|
||||
const result of (this.kv).list({
|
||||
start: [keyPrefix],
|
||||
end: [endRange(keyPrefix)],
|
||||
})
|
||||
) {
|
||||
results.push({
|
||||
key: result.key[0] as string,
|
||||
value: result.value as any,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
function endRange(prefix: string) {
|
||||
const lastChar = prefix[prefix.length - 1];
|
||||
const nextLastChar = String.fromCharCode(lastChar.charCodeAt(0) + 1);
|
||||
return prefix.slice(0, -1) + nextLastChar;
|
||||
}
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { Manifest, RuntimeEnvironment } from "./types.ts";
|
||||
import { Manifest } from "./types.ts";
|
||||
import { Sandbox } from "./sandbox.ts";
|
||||
import { System } from "./system.ts";
|
||||
import { AssetBundle, AssetJson } from "./asset_bundle/bundle.ts";
|
||||
|
||||
export class Plug<HookT> {
|
||||
readonly runtimeEnv?: RuntimeEnvironment;
|
||||
readonly runtimeEnv?: string;
|
||||
|
||||
public grantedPermissions: string[] = [];
|
||||
public sandbox: Sandbox<HookT>;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { SysCallMapping } from "../../plugos/system.ts";
|
||||
import {
|
||||
ProxyFetchRequest,
|
||||
ProxyFetchResponse,
|
||||
} from "../../common/proxy_fetch.ts";
|
||||
import { base64Encode } from "../asset_bundle/base64.ts";
|
||||
|
||||
export function sandboxFetchSyscalls(): SysCallMapping {
|
||||
return {
|
||||
"sandboxFetch.fetch": async (
|
||||
_ctx,
|
||||
url: string,
|
||||
options: ProxyFetchRequest,
|
||||
): Promise<ProxyFetchResponse> => {
|
||||
// console.log("Got sandbox fetch ", url);
|
||||
const resp = await fetch(url, options);
|
||||
return {
|
||||
status: resp.status,
|
||||
ok: resp.ok,
|
||||
headers: Object.fromEntries(resp.headers.entries()),
|
||||
base64Body: base64Encode(new Uint8Array(await resp.arrayBuffer())),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { SysCallMapping } from "../system.ts";
|
||||
|
||||
export default function (cwd: string): SysCallMapping {
|
||||
export function shellSyscalls(cwd: string): SysCallMapping {
|
||||
return {
|
||||
"shell.run": async (
|
||||
_ctx,
|
||||
cmd: string,
|
||||
args: string[],
|
||||
): Promise<{ stdout: string; stderr: string }> => {
|
||||
const p = Deno.run({
|
||||
cmd: [cmd, ...args],
|
||||
cwd: cwd,
|
||||
const p = new Deno.Command(cmd, {
|
||||
args: args,
|
||||
cwd,
|
||||
stdout: "piped",
|
||||
stderr: "piped",
|
||||
});
|
||||
await p.status();
|
||||
const stdout = new TextDecoder().decode(await p.output());
|
||||
const stderr = new TextDecoder().decode(await p.stderrOutput());
|
||||
const output = await p.output();
|
||||
const stdout = new TextDecoder().decode(output.stdout);
|
||||
const stderr = new TextDecoder().decode(output.stderr);
|
||||
|
||||
return { stdout, stderr };
|
||||
},
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { SysCallMapping } from "../system.ts";
|
||||
import { DexieKVStore } from "../lib/kv_store.dexie.ts";
|
||||
import { KV } from "../lib/kv_store.ts";
|
||||
import { KV, KVStore } from "../lib/kv_store.ts";
|
||||
|
||||
export function storeSyscalls(
|
||||
db: DexieKVStore,
|
||||
db: KVStore,
|
||||
): SysCallMapping {
|
||||
return {
|
||||
"store.delete": (_ctx, key: string) => {
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { Hook, RuntimeEnvironment } from "./types.ts";
|
||||
import { Hook } from "./types.ts";
|
||||
import { EventEmitter } from "./event.ts";
|
||||
import type { SandboxFactory } from "./sandbox.ts";
|
||||
import { Plug } from "./plug.ts";
|
||||
@@ -32,7 +32,7 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
||||
protected registeredSyscalls = new Map<string, Syscall>();
|
||||
protected enabledHooks = new Set<Hook<HookT>>();
|
||||
|
||||
constructor(readonly env?: RuntimeEnvironment) {
|
||||
constructor(readonly env?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -17,11 +17,9 @@ export type FunctionDef<HookT> = {
|
||||
// Reuse an
|
||||
// Format: plugName.functionName
|
||||
redirect?: string;
|
||||
env?: RuntimeEnvironment;
|
||||
env?: string;
|
||||
} & HookT;
|
||||
|
||||
export type RuntimeEnvironment = "client" | "server";
|
||||
|
||||
export interface Hook<HookT> {
|
||||
validateManifest(manifest: Manifest<HookT>): string[];
|
||||
apply(system: System<HookT>): void;
|
||||
|
||||
Reference in New Issue
Block a user