Added pageNamespace hook support

This commit is contained in:
Zef Hemel
2022-05-17 11:53:17 +02:00
parent cd89634fd6
commit 9a6a86f8b5
14 changed files with 302 additions and 15 deletions
+11 -1
View File
@@ -36,6 +36,8 @@ import {
ensureFTSTable,
fullTextSearchSyscalls,
} from "@plugos/plugos/syscalls/fulltext.knex_sqlite";
import { PlugSpacePrimitives } from "./hooks/plug_space_primitives";
import { PageNamespaceHook } from "./hooks/page_namespace";
const safeFilename = /^[a-zA-Z0-9_\-\.]+$/;
@@ -69,9 +71,14 @@ export class ExpressServer {
// Setup system
this.eventHook = new EventHook();
this.system.addHook(this.eventHook);
let namespaceHook = new PageNamespaceHook();
this.system.addHook(namespaceHook);
this.space = new Space(
new EventedSpacePrimitives(
new DiskSpacePrimitives(options.pagesPath),
new PlugSpacePrimitives(
new DiskSpacePrimitives(options.pagesPath),
namespaceHook
),
this.eventHook
),
true
@@ -227,6 +234,7 @@ export class ExpressServer {
let pageData = await this.space.readPage(pageName);
res.status(200);
res.header("Last-Modified", "" + pageData.meta.lastModified);
res.header("X-Permission", pageData.meta.perm);
res.header("Content-Type", "text/markdown");
res.send(pageData.text);
} catch (e) {
@@ -251,6 +259,7 @@ export class ExpressServer {
);
res.status(200);
res.header("Last-Modified", "" + meta.lastModified);
res.header("X-Permission", meta.perm);
res.send("OK");
} catch (err) {
res.status(500);
@@ -264,6 +273,7 @@ export class ExpressServer {
const meta = await this.space.getPageMeta(pageName);
res.status(200);
res.header("Last-Modified", "" + meta.lastModified);
res.header("X-Permission", meta.perm);
res.header("Content-Type", "text/markdown");
res.send("");
} catch (e) {
+90
View File
@@ -0,0 +1,90 @@
import { Plug } from "@plugos/plugos/plug";
import { System } from "@plugos/plugos/system";
import { Hook, Manifest } from "@plugos/plugos/types";
import { Express, NextFunction, Request, Response, Router } from "express";
export type PageNamespaceOperation =
| "readPage"
| "writePage"
| "listPages"
| "getPageMeta"
| "deletePage";
export type PageNamespaceDef = {
pattern: string;
operation: PageNamespaceOperation;
};
export type PageNamespaceHookT = {
pageNamespace?: PageNamespaceDef;
};
type SpaceFunction = {
operation: PageNamespaceOperation;
pattern: RegExp;
plug: Plug<PageNamespaceHookT>;
name: string;
};
export class PageNamespaceHook implements Hook<PageNamespaceHookT> {
spaceFunctions: SpaceFunction[] = [];
constructor() {}
apply(system: System<PageNamespaceHookT>): void {
system.on({
plugLoaded: () => {
this.updateCache(system);
},
plugUnloaded: () => {
this.updateCache(system);
},
});
}
updateCache(system: System<PageNamespaceHookT>) {
this.spaceFunctions = [];
for (let plug of system.loadedPlugs.values()) {
if (plug.manifest?.functions) {
for (let [funcName, funcDef] of Object.entries(
plug.manifest.functions
)) {
if (funcDef.pageNamespace) {
this.spaceFunctions.push({
operation: funcDef.pageNamespace.operation,
pattern: new RegExp(funcDef.pageNamespace.pattern),
plug,
name: funcName,
});
}
}
}
}
}
validateManifest(manifest: Manifest<PageNamespaceHookT>): string[] {
let errors: string[] = [];
if (!manifest.functions) {
return [];
}
for (let [funcName, funcDef] of Object.entries(manifest.functions)) {
if (funcDef.pageNamespace) {
if (!funcDef.pageNamespace.pattern) {
errors.push(`Function ${funcName} has a namespace but no pattern`);
}
if (!funcDef.pageNamespace.operation) {
errors.push(`Function ${funcName} has a namespace but no operation`);
}
if (
!["readPage", "writePage", "getPageMeta", "listPages"].includes(
funcDef.pageNamespace.operation
)
) {
errors.push(
`Function ${funcName} has an invalid operation ${funcDef.pageNamespace.operation}`
);
}
}
}
return errors;
}
}
@@ -0,0 +1,103 @@
import { Plug } from "@plugos/plugos/plug";
import { SpacePrimitives } from "@silverbulletmd/common/spaces/space_primitives";
import { PageMeta } from "@silverbulletmd/common/types";
import { PageNamespaceHook, PageNamespaceOperation } from "./page_namespace";
export class PlugSpacePrimitives implements SpacePrimitives {
constructor(
private wrapped: SpacePrimitives,
private hook: PageNamespaceHook
) {}
performOperation(
type: PageNamespaceOperation,
pageName: string,
...args: any[]
): Promise<any> | false {
for (let { operation, pattern, plug, name } of this.hook.spaceFunctions) {
if (operation === type && pageName.match(pattern)) {
return plug.invoke(name, [pageName, ...args]);
}
}
return false;
}
async fetchPageList(): Promise<{
pages: Set<PageMeta>;
nowTimestamp: number;
}> {
let allPages = new Set<PageMeta>();
for (let { plug, name, operation } of this.hook.spaceFunctions) {
if (operation === "listPages") {
for (let pm of await plug.invoke(name, [])) {
allPages.add(pm);
}
}
}
let result = await this.wrapped.fetchPageList();
for (let pm of result.pages) {
allPages.add(pm);
}
return {
nowTimestamp: result.nowTimestamp,
pages: allPages,
};
}
readPage(name: string): Promise<{ text: string; meta: PageMeta }> {
let result = this.performOperation("readPage", name);
if (result) {
return result;
}
return this.wrapped.readPage(name);
}
getPageMeta(name: string): Promise<PageMeta> {
let result = this.performOperation("getPageMeta", name);
if (result) {
return result;
}
return this.wrapped.getPageMeta(name);
}
writePage(
name: string,
text: string,
selfUpdate?: boolean,
lastModified?: number
): Promise<PageMeta> {
let result = this.performOperation(
"writePage",
name,
text,
selfUpdate,
lastModified
);
if (result) {
return result;
}
return this.wrapped.writePage(name, text, selfUpdate, lastModified);
}
deletePage(name: string): Promise<void> {
let result = this.performOperation("deletePage", name);
if (result) {
return result;
}
return this.wrapped.deletePage(name);
}
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any> {
return this.wrapped.proxySyscall(plug, name, args);
}
invokeFunction(
plug: Plug<any>,
env: string,
name: string,
args: any[]
): Promise<any> {
return this.wrapped.invokeFunction(plug, env, name, args);
}
}