This commit is contained in:
Zef Hemel
2022-04-26 19:04:36 +02:00
parent cb2d3f8652
commit 76636dd9b1
41 changed files with 500 additions and 287 deletions
+4
View File
@@ -20,6 +20,10 @@ async function bundle(
(await readFile(manifestPath)).toString()
) as Manifest<any>;
if (!manifest.name) {
throw new Error(`Missing 'name' in ${manifestPath}`);
}
for (let [name, def] of Object.entries(manifest.functions)) {
let jsFunctionName = "default",
filePath = path.join(rootPath, def.path!);
+1 -1
View File
@@ -9,8 +9,8 @@ import { System } from "../system";
test("Run a plugos endpoint server", async () => {
let system = new System<EndpointHookT>("server");
let plug = await system.load(
"test",
{
name: "test",
functions: {
testhandler: {
http: {
+21 -2
View File
@@ -1,6 +1,7 @@
import { Hook, Manifest } from "../types";
import { System } from "../system";
import { safeRun } from "../util";
import { EventEmitter } from "events";
// System events:
// - plug:load (plugName: string)
@@ -11,6 +12,14 @@ export type EventHookT = {
export class EventHook implements Hook<EventHookT> {
private system?: System<EventHookT>;
public localListeners: Map<string, ((data: any) => any)[]> = new Map();
addLocalListener(eventName: string, callback: (data: any) => any) {
if (!this.localListeners.has(eventName)) {
this.localListeners.set(eventName, []);
}
this.localListeners.get(eventName)!.push(callback);
}
async dispatchEvent(eventName: string, data?: any): Promise<any[]> {
if (!this.system) {
@@ -32,15 +41,25 @@ export class EventHook implements Hook<EventHookT> {
}
}
}
let localListeners = this.localListeners.get(eventName);
if (localListeners) {
for (let localListener of localListeners) {
let result = await Promise.resolve(localListener(data));
if (result) {
responses.push(result);
}
}
}
return responses;
}
apply(system: System<EventHookT>): void {
this.system = system;
this.system.on({
plugLoaded: (name) => {
plugLoaded: (plug) => {
safeRun(async () => {
await this.dispatchEvent("plug:load", name);
await this.dispatchEvent("plug:load", plug.name);
});
},
});
+2 -2
View File
@@ -11,10 +11,10 @@ export class NodeCronHook implements Hook<CronHookT> {
apply(system: System<CronHookT>): void {
let tasks: ScheduledTask[] = [];
system.on({
plugLoaded: (name, plug) => {
plugLoaded: () => {
reloadCrons();
},
plugUnloaded(name, plug) {
plugUnloaded() {
reloadCrons();
},
});
+7 -12
View File
@@ -3,11 +3,7 @@ import watch from "node-watch";
import path from "path";
import { createSandbox } from "./environments/node_sandbox";
import { System } from "./system";
function extractPlugName(localPath: string): string {
const baseName = path.basename(localPath);
return baseName.substring(0, baseName.length - ".plug.json".length);
}
import { Manifest } from "./types";
export class DiskPlugLoader<HookT> {
private system: System<HookT>;
@@ -27,13 +23,13 @@ export class DiskPlugLoader<HookT> {
.then(async () => {
try {
// let localPath = path.join(this.plugPath, filename);
const plugName = extractPlugName(localPath);
console.log("Change detected for", plugName);
console.log("Change detected for", localPath);
try {
await fs.stat(localPath);
} catch (e) {
// Likely removed
await this.system.unload(plugName);
console.log("Plug removed, TODO: Unload");
return;
}
const plugDef = await this.loadPlugFromFile(localPath);
} catch (e) {
@@ -47,12 +43,11 @@ export class DiskPlugLoader<HookT> {
private async loadPlugFromFile(localPath: string) {
const plug = await fs.readFile(localPath, "utf8");
const plugName = extractPlugName(localPath);
console.log("Now loading plug", plugName);
try {
const plugDef = JSON.parse(plug);
await this.system.load(plugName, plugDef, createSandbox);
const plugDef: Manifest<HookT> = JSON.parse(plug);
console.log("Now loading plug", plugDef.name);
await this.system.load(plugDef, createSandbox);
return plugDef;
} catch (e) {
console.error("Could not parse plugin file", e);
+1 -1
View File
@@ -23,8 +23,8 @@ test("Run a Node sandbox", async () => {
},
});
let plug = await system.load(
"test",
{
name: "test",
requiredPermissions: ["dangerous"],
functions: {
addTen: {
-2
View File
@@ -34,9 +34,7 @@ export function esbuildSyscalls(): SysCallMapping {
}
await writeFile(`${tmpDir}/${filename}`, code);
console.log("Dir", tmpDir);
let jsCode = await compile(`${tmpDir}/${filename}`, "", false, ["yaml"]);
// console.log("JS code", jsCode);
await rm(tmpDir, { recursive: true });
return jsCode;
},
@@ -10,8 +10,8 @@ test("Test store", async () => {
let system = new System("server");
system.registerSyscalls([], storeSyscalls("test", "test"));
let plug = await system.load(
"test",
{
name: "test",
functions: {
test1: {
code: `(() => {
@@ -17,8 +17,8 @@ test("Test store", async () => {
let system = new System("server");
system.registerSyscalls([], storeSyscalls(db, "test_table"));
let plug = await system.load(
"test",
{
name: "test",
functions: {
test1: {
code: `(() => {
+13 -11
View File
@@ -7,11 +7,11 @@ export interface SysCallMapping {
[key: string]: (ctx: SyscallContext, ...args: any) => Promise<any> | any;
}
export type SystemJSON<HookT> = { [key: string]: Manifest<HookT> };
export type SystemJSON<HookT> = Manifest<HookT>[];
export type SystemEvents<HookT> = {
plugLoaded: (name: string, plug: Plug<HookT>) => void;
plugUnloaded: (name: string, plug: Plug<HookT>) => void;
plugLoaded: (plug: Plug<HookT>) => void;
plugUnloaded: (name: string) => void;
};
export type SyscallContext = {
@@ -83,10 +83,10 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
}
async load(
name: string,
manifest: Manifest<HookT>,
sandboxFactory: SandboxFactory<HookT>
): Promise<Plug<HookT>> {
const name = manifest.name;
if (this.plugs.has(name)) {
await this.unload(name);
}
@@ -100,29 +100,31 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
}
// Ok, let's load this thing!
const plug = new Plug(this, name, sandboxFactory);
console.log("Loading", name);
await plug.load(manifest);
this.plugs.set(name, plug);
this.emit("plugLoaded", name, plug);
this.emit("plugLoaded", plug);
return plug;
}
async unload(name: string) {
console.log("Unloading", name);
const plug = this.plugs.get(name);
if (!plug) {
throw Error(`Plug ${name} not found`);
}
await plug.stop();
this.emit("plugUnloaded", name, plug);
this.emit("plugUnloaded", name);
this.plugs.delete(name);
}
toJSON(): SystemJSON<HookT> {
let plugJSON: { [key: string]: Manifest<HookT> } = {};
let plugJSON: Manifest<HookT>[] = [];
for (let [name, plug] of this.plugs) {
if (!plug.manifest) {
continue;
}
plugJSON[name] = plug.manifest;
plugJSON.push(plug.manifest);
}
return plugJSON;
}
@@ -132,9 +134,9 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
sandboxFactory: SandboxFactory<HookT>
) {
await this.unloadAll();
for (let [name, manifest] of Object.entries(json)) {
console.log("Loading plug", name);
await this.load(name, manifest, sandboxFactory);
for (let manifest of json) {
console.log("Loading plug", manifest.name);
await this.load(manifest, sandboxFactory);
}
}
+1
View File
@@ -1,6 +1,7 @@
import { System } from "./system";
export interface Manifest<HookT> {
name: string;
requiredPermissions?: string[];
functions: {
[key: string]: FunctionDef<HookT>;