fixed test run/002
This commit is contained in:
parent
28691d677a
commit
9d9a1e1f08
|
@ -1,4 +1,4 @@
|
||||||
import {App} from "../../util/app";
|
import {App} from "../util/app";
|
||||||
|
|
||||||
describe('Run', () => {
|
describe('Run', () => {
|
||||||
let app: App;
|
let app: App;
|
||||||
|
@ -23,16 +23,18 @@ describe('Run', () => {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: "Peon",
|
|
||||||
id: "peon-1",
|
id: "peon-1",
|
||||||
age: 263
|
properties: {
|
||||||
|
name: "Peon",
|
||||||
|
age: 263
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
expect(data).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
|
id: "peon-1",
|
||||||
properties: {
|
properties: {
|
||||||
name: "Peon",
|
name: "Peon",
|
||||||
id: "peon-1",
|
|
||||||
age: 263
|
age: 263
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import {App} from '../../util/app';
|
import Debug from 'debug';
|
||||||
|
import {App} from '../util/app';
|
||||||
|
const debug = Debug('test:two');
|
||||||
|
|
||||||
describe('Run', () => {
|
describe('Run', () => {
|
||||||
const apps: App[] = [];
|
const apps: App[] = [];
|
||||||
|
@ -6,10 +8,14 @@ describe('Run', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
apps[0] = new App({
|
apps[0] = new App({
|
||||||
httpEnable: true,
|
httpEnable: true,
|
||||||
|
peerId: 'app0',
|
||||||
});
|
});
|
||||||
apps[1] = new App({
|
apps[1] = new App({
|
||||||
httpEnable: true,
|
httpEnable: true,
|
||||||
|
peerId: 'app1',
|
||||||
});
|
});
|
||||||
|
apps[0].config.seedPeers.push(apps[1].myRequestAddr);
|
||||||
|
apps[1].config.seedPeers.push(apps[0].myRequestAddr);
|
||||||
|
|
||||||
await Promise.all(apps.map((app) => app.start()));
|
await Promise.all(apps.map((app) => app.start()));
|
||||||
});
|
});
|
||||||
|
@ -19,40 +25,38 @@ describe('Run', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can create a record on node 0 and read it on node 1', async () => {
|
it('can create a record on node 0 and read it on node 1', async () => {
|
||||||
|
debug('apps[0].apiUrl', apps[0].apiUrl);
|
||||||
|
debug('apps[1].apiUrl', apps[1].apiUrl);
|
||||||
|
|
||||||
const res = await fetch(`${apps[0].apiUrl}/users`, {
|
const res = await fetch(`${apps[0].apiUrl}/users`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: "Peon",
|
|
||||||
id: "peon-1",
|
id: "peon-1",
|
||||||
age: 263
|
properties: {
|
||||||
|
name: "Peon",
|
||||||
|
age: 263
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
expect(data).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
|
id: "peon-1",
|
||||||
properties: {
|
properties: {
|
||||||
name: "Peon",
|
name: "Peon",
|
||||||
id: "peon-1",
|
|
||||||
age: 263
|
age: 263
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
const res2 = await fetch(`${apps[0].apiUrl}/users`, {
|
const res2 = await fetch(`${apps[1].apiUrl}/users/peon-1`);
|
||||||
method: 'PUT',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: "Peon",
|
|
||||||
id: "peon-1",
|
|
||||||
age: 263
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data2 = await res2.json();
|
const data2 = await res2.json();
|
||||||
|
debug('data2', data2);
|
||||||
expect(data2).toMatchObject({
|
expect(data2).toMatchObject({
|
||||||
|
id: "peon-1",
|
||||||
properties: {
|
properties: {
|
||||||
name: "Peon",
|
name: "Peon",
|
||||||
id: "peon-1",
|
|
||||||
age: 263
|
age: 263
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {RhizomeNode, RhizomeNodeConfig} from "../src/node";
|
import {RhizomeNode, RhizomeNodeConfig} from "../../src/node";
|
||||||
import {TypedCollection} from "../src/typed-collection";
|
import {TypedCollection} from "../../src/typed-collection";
|
||||||
|
|
||||||
type User = {
|
type User = {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -28,7 +28,8 @@ export class App extends RhizomeNode {
|
||||||
const users = new TypedCollection<User>("users");
|
const users = new TypedCollection<User>("users");
|
||||||
users.rhizomeConnect(this);
|
users.rhizomeConnect(this);
|
||||||
|
|
||||||
this.apiUrl = `http://${this.config.httpAddr}:${this.config.httpPort}`;
|
const {httpAddr, httpPort} = this.config;
|
||||||
|
this.apiUrl = `http://${httpAddr}:${httpPort}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
"level": "^9.0.0",
|
"level": "^9.0.0",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"zeromq": "^6.1.2"
|
"zeromq": "^6.1.2",
|
||||||
|
"util": "./util/"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
|
|
|
@ -66,6 +66,7 @@ export class Collection {
|
||||||
} else {
|
} else {
|
||||||
let anyChanged = false;
|
let anyChanged = false;
|
||||||
Object.entries(properties).forEach(([key, value]) => {
|
Object.entries(properties).forEach(([key, value]) => {
|
||||||
|
if (key === 'id') return;
|
||||||
let changed = false;
|
let changed = false;
|
||||||
if (entity.properties && entity.properties[key] !== value) {
|
if (entity.properties && entity.properties[key] !== value) {
|
||||||
entity.properties[key] = value;
|
entity.properties[key] = value;
|
||||||
|
|
150
src/http-api.ts
150
src/http-api.ts
|
@ -1,124 +1,26 @@
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
import express from "express";
|
import express, {Express, Router} from "express";
|
||||||
import {FSWatcher} from "fs";
|
|
||||||
import {readdirSync, readFileSync, watch} from "fs";
|
|
||||||
import {Server} from "http";
|
import {Server} from "http";
|
||||||
import path, {join} from "path";
|
|
||||||
import {Converter} from "showdown";
|
|
||||||
import {Collection} from "./collection";
|
import {Collection} from "./collection";
|
||||||
import {RhizomeNode} from "./node";
|
import {RhizomeNode} from "./node";
|
||||||
import {Delta} from "./types";
|
import {Delta} from "./types";
|
||||||
|
import {htmlDocFromMarkdown, MDFiles} from "./util/md-files";
|
||||||
const debug = Debug('http-api');
|
const debug = Debug('http-api');
|
||||||
|
|
||||||
const docConverter = new Converter({
|
|
||||||
completeHTMLDocument: true,
|
|
||||||
// simpleLineBreaks: true,
|
|
||||||
tables: true,
|
|
||||||
tasklists: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const htmlDocFromMarkdown = (md: string): string => docConverter.makeHtml(md);
|
|
||||||
|
|
||||||
type mdFileInfo = {
|
|
||||||
name: string,
|
|
||||||
md: string,
|
|
||||||
html: string
|
|
||||||
};
|
|
||||||
|
|
||||||
class MDFiles {
|
|
||||||
files = new Map<string, mdFileInfo>();
|
|
||||||
readme?: mdFileInfo;
|
|
||||||
dirWatcher?: FSWatcher;
|
|
||||||
readmeWatcher?: FSWatcher;
|
|
||||||
|
|
||||||
readFile(name: string) {
|
|
||||||
const md = readFileSync(join('./markdown', `${name}.md`)).toString();
|
|
||||||
const html = htmlDocFromMarkdown(md);
|
|
||||||
this.files.set(name, {name, md, html});
|
|
||||||
}
|
|
||||||
|
|
||||||
readReadme() {
|
|
||||||
const md = readFileSync('./README.md').toString();
|
|
||||||
const html = htmlDocFromMarkdown(md);
|
|
||||||
this.readme = {name: 'README', md, html};
|
|
||||||
}
|
|
||||||
|
|
||||||
getReadmeHTML() {
|
|
||||||
return this.readme?.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
getHtml(name: string): string | undefined {
|
|
||||||
return this.files.get(name)?.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
list(): string[] {
|
|
||||||
return Array.from(this.files.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
readDir() {
|
|
||||||
// Read list of markdown files from directory and
|
|
||||||
// render each markdown file as html
|
|
||||||
readdirSync('./markdown/')
|
|
||||||
.filter((f) => f.endsWith('.md'))
|
|
||||||
.map((name) => path.parse(name).name)
|
|
||||||
.forEach((name) => this.readFile(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
watchDir() {
|
|
||||||
this.dirWatcher = watch('./markdown', null, (eventType, filename) => {
|
|
||||||
if (!filename) return;
|
|
||||||
if (!filename.endsWith(".md")) return;
|
|
||||||
|
|
||||||
const name = path.parse(filename).name;
|
|
||||||
|
|
||||||
switch (eventType) {
|
|
||||||
case 'rename': {
|
|
||||||
debug(`file ${name} renamed`);
|
|
||||||
// Remove it from memory and re-scan everything
|
|
||||||
this.files.delete(name);
|
|
||||||
this.readDir();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'change': {
|
|
||||||
debug(`file ${name} changed`);
|
|
||||||
// Re-read this file
|
|
||||||
this.readFile(name)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watchReadme() {
|
|
||||||
this.readmeWatcher = watch('./README.md', null, (eventType, filename) => {
|
|
||||||
if (!filename) return;
|
|
||||||
|
|
||||||
switch (eventType) {
|
|
||||||
case 'change': {
|
|
||||||
debug(`README file changed`);
|
|
||||||
// Re-read this file
|
|
||||||
this.readReadme()
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.dirWatcher?.close();
|
|
||||||
this.readmeWatcher?.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HttpApi {
|
export class HttpApi {
|
||||||
rhizomeNode: RhizomeNode;
|
rhizomeNode: RhizomeNode;
|
||||||
app = express();
|
app: Express;
|
||||||
|
router: Router;
|
||||||
mdFiles = new MDFiles();
|
mdFiles = new MDFiles();
|
||||||
server?: Server;
|
server?: Server;
|
||||||
|
|
||||||
constructor(rhizomeNode: RhizomeNode) {
|
constructor(rhizomeNode: RhizomeNode) {
|
||||||
this.rhizomeNode = rhizomeNode;
|
this.rhizomeNode = rhizomeNode;
|
||||||
|
this.app = express();
|
||||||
|
this.router = Router();
|
||||||
|
|
||||||
this.app.use(express.json());
|
this.app.use(express.json());
|
||||||
|
this.app.use(this.router);
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
@ -129,13 +31,13 @@ export class HttpApi {
|
||||||
this.mdFiles.watchReadme();
|
this.mdFiles.watchReadme();
|
||||||
|
|
||||||
// Serve README
|
// Serve README
|
||||||
this.app.get('/html/README', (_req: express.Request, res: express.Response) => {
|
this.router.get('/html/README', (_req: express.Request, res: express.Response) => {
|
||||||
const html = this.mdFiles.getReadmeHTML();
|
const html = this.mdFiles.getReadmeHTML();
|
||||||
res.setHeader('content-type', 'text/html').send(html);
|
res.setHeader('content-type', 'text/html').send(html);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve markdown files as html
|
// Serve markdown files as html
|
||||||
this.app.get('/html/:name', (req: express.Request, res: express.Response) => {
|
this.router.get('/html/:name', (req: express.Request, res: express.Response) => {
|
||||||
let html = this.mdFiles.getHtml(req.params.name);
|
let html = this.mdFiles.getHtml(req.params.name);
|
||||||
if (!html) {
|
if (!html) {
|
||||||
res.status(404);
|
res.status(404);
|
||||||
|
@ -154,24 +56,24 @@ export class HttpApi {
|
||||||
}
|
}
|
||||||
const html = htmlDocFromMarkdown(md);
|
const html = htmlDocFromMarkdown(md);
|
||||||
|
|
||||||
this.app.get('/html', (_req: express.Request, res: express.Response) => {
|
this.router.get('/html', (_req: express.Request, res: express.Response) => {
|
||||||
res.setHeader('content-type', 'text/html').send(html);
|
res.setHeader('content-type', 'text/html').send(html);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve list of all deltas accepted
|
// Serve list of all deltas accepted
|
||||||
// TODO: This won't scale well
|
// TODO: This won't scale well
|
||||||
this.app.get("/deltas", (_req: express.Request, res: express.Response) => {
|
this.router.get("/deltas", (_req: express.Request, res: express.Response) => {
|
||||||
res.json(this.rhizomeNode.deltaStream.deltasAccepted);
|
res.json(this.rhizomeNode.deltaStream.deltasAccepted);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the number of deltas ingested by this node
|
// Get the number of deltas ingested by this node
|
||||||
this.app.get("/deltas/count", (_req: express.Request, res: express.Response) => {
|
this.router.get("/deltas/count", (_req: express.Request, res: express.Response) => {
|
||||||
res.json(this.rhizomeNode.deltaStream.deltasAccepted.length);
|
res.json(this.rhizomeNode.deltaStream.deltasAccepted.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the list of peers seen by this node (including itself)
|
// Get the list of peers seen by this node (including itself)
|
||||||
this.app.get("/peers", (_req: express.Request, res: express.Response) => {
|
this.router.get("/peers", (_req: express.Request, res: express.Response) => {
|
||||||
res.json(this.rhizomeNode.peers.peers.map(({reqAddr, publishAddr, isSelf, isSeedPeer}) => {
|
res.json(this.rhizomeNode.peers.peers.map(({reqAddr, publishAddr, isSelf, isSeedPeer}) => {
|
||||||
const deltasAcceptedCount = this.rhizomeNode.deltaStream.deltasAccepted
|
const deltasAcceptedCount = this.rhizomeNode.deltaStream.deltasAccepted
|
||||||
.filter((delta: Delta) => {
|
.filter((delta: Delta) => {
|
||||||
|
@ -193,12 +95,16 @@ export class HttpApi {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the number of peers seen by this node (including itself)
|
// Get the number of peers seen by this node (including itself)
|
||||||
this.app.get("/peers/count", (_req: express.Request, res: express.Response) => {
|
this.router.get("/peers/count", (_req: express.Request, res: express.Response) => {
|
||||||
res.json(this.rhizomeNode.peers.peers.length);
|
res.json(this.rhizomeNode.peers.peers.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
const {httpAddr, httpPort} = this.rhizomeNode.config;
|
const {httpAddr, httpPort} = this.rhizomeNode.config;
|
||||||
this.server = this.app.listen(httpPort, httpAddr, () => {
|
this.server = this.app.listen({
|
||||||
|
port: httpPort,
|
||||||
|
host: httpAddr,
|
||||||
|
exclusive: true
|
||||||
|
}, () => {
|
||||||
debug(`HTTP API bound to ${httpAddr}:${httpPort}`);
|
debug(`HTTP API bound to ${httpAddr}:${httpPort}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -207,27 +113,31 @@ export class HttpApi {
|
||||||
const {name} = collection;
|
const {name} = collection;
|
||||||
|
|
||||||
// Get the ID of all domain entities
|
// Get the ID of all domain entities
|
||||||
this.app.get(`/${name}/ids`, (_req: express.Request, res: express.Response) => {
|
this.router.get(`/${name}/ids`, (_req: express.Request, res: express.Response) => {
|
||||||
res.json({ids: collection.getIds()});
|
res.json({ids: collection.getIds()});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get a single domain entity by ID
|
// Get a single domain entity by ID
|
||||||
this.app.get(`/${name}/:id`, (req: express.Request, res: express.Response) => {
|
this.router.get(`/${name}/:id`, (req: express.Request, res: express.Response) => {
|
||||||
const {params: {id}} = req;
|
const {params: {id}} = req;
|
||||||
const ent = collection.get(id);
|
const ent = collection.get(id);
|
||||||
|
if (!ent) {
|
||||||
|
res.status(404).send({error: "Not Found"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
res.json(ent);
|
res.json(ent);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a new domain entity
|
// Add a new domain entity
|
||||||
// TODO: schema validation
|
// TODO: schema validation
|
||||||
this.app.put(`/${name}`, (req: express.Request, res: express.Response) => {
|
this.router.put(`/${name}`, (req: express.Request, res: express.Response) => {
|
||||||
const {body: properties} = req;
|
const {body: {id, properties}} = req;
|
||||||
const ent = collection.put(properties.id, properties);
|
const ent = collection.put(id, properties);
|
||||||
res.json(ent);
|
res.json(ent);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update a domain entity
|
// Update a domain entity
|
||||||
this.app.put(`/${name}/:id`, (req: express.Request, res: express.Response) => {
|
this.router.put(`/${name}/:id`, (req: express.Request, res: express.Response) => {
|
||||||
const {body: properties, params: {id}} = req;
|
const {body: properties, params: {id}} = req;
|
||||||
if (properties.id && properties.id !== id) {
|
if (properties.id && properties.id !== id) {
|
||||||
res.status(400).json({error: "ID Mismatch", param: id, property: properties.id});
|
res.status(400).json({error: "ID Mismatch", param: id, property: properties.id});
|
||||||
|
|
12
src/peers.ts
12
src/peers.ts
|
@ -7,7 +7,7 @@ import {PeerRequest, RequestSocket, ResponseSocket} from "./request-reply";
|
||||||
import {Delta, PeerAddress} from "./types";
|
import {Delta, PeerAddress} from "./types";
|
||||||
const debug = Debug('peers');
|
const debug = Debug('peers');
|
||||||
|
|
||||||
export enum PeerMethods {
|
export enum RequestMethods {
|
||||||
GetPublishAddress,
|
GetPublishAddress,
|
||||||
AskForDeltas
|
AskForDeltas
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class Peer {
|
||||||
this.isSeedPeer = !!SEED_PEERS.find((seedPeer) => reqAddr.isEqual(seedPeer));
|
this.isSeedPeer = !!SEED_PEERS.find((seedPeer) => reqAddr.isEqual(seedPeer));
|
||||||
}
|
}
|
||||||
|
|
||||||
async request(method: PeerMethods): Promise<Message> {
|
async request(method: RequestMethods): Promise<Message> {
|
||||||
if (!this.reqSock) {
|
if (!this.reqSock) {
|
||||||
this.reqSock = new RequestSocket(this.reqAddr);
|
this.reqSock = new RequestSocket(this.reqAddr);
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ class Peer {
|
||||||
async subscribeDeltas() {
|
async subscribeDeltas() {
|
||||||
if (!this.publishAddr) {
|
if (!this.publishAddr) {
|
||||||
debug(`requesting publish addr from peer ${this.reqAddr.toAddrString()}`);
|
debug(`requesting publish addr from peer ${this.reqAddr.toAddrString()}`);
|
||||||
const res = await this.request(PeerMethods.GetPublishAddress);
|
const res = await this.request(RequestMethods.GetPublishAddress);
|
||||||
this.publishAddr = PeerAddress.fromString(res.toString());
|
this.publishAddr = PeerAddress.fromString(res.toString());
|
||||||
debug(`received publish addr ${this.publishAddr.toAddrString()} from peer ${this.reqAddr.toAddrString()}`);
|
debug(`received publish addr ${this.publishAddr.toAddrString()} from peer ${this.reqAddr.toAddrString()}`);
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ class Peer {
|
||||||
// Third pass should find a way to reduce the number of deltas transmitted.
|
// Third pass should find a way to reduce the number of deltas transmitted.
|
||||||
|
|
||||||
// TODO: requestTimeout
|
// TODO: requestTimeout
|
||||||
const res = await this.request(PeerMethods.AskForDeltas);
|
const res = await this.request(RequestMethods.AskForDeltas);
|
||||||
const deltas = JSON.parse(res.toString());
|
const deltas = JSON.parse(res.toString());
|
||||||
return deltas;
|
return deltas;
|
||||||
}
|
}
|
||||||
|
@ -82,12 +82,12 @@ export class Peers {
|
||||||
this.rhizomeNode.requestReply.registerRequestHandler(async (req: PeerRequest, res: ResponseSocket) => {
|
this.rhizomeNode.requestReply.registerRequestHandler(async (req: PeerRequest, res: ResponseSocket) => {
|
||||||
debug('inspecting peer request');
|
debug('inspecting peer request');
|
||||||
switch (req.method) {
|
switch (req.method) {
|
||||||
case PeerMethods.GetPublishAddress: {
|
case RequestMethods.GetPublishAddress: {
|
||||||
debug('it\'s a request for our publish address');
|
debug('it\'s a request for our publish address');
|
||||||
await res.send(this.rhizomeNode.myPublishAddr.toAddrString());
|
await res.send(this.rhizomeNode.myPublishAddr.toAddrString());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PeerMethods.AskForDeltas: {
|
case RequestMethods.AskForDeltas: {
|
||||||
debug('it\'s a request for deltas');
|
debug('it\'s a request for deltas');
|
||||||
// TODO: stream these rather than
|
// TODO: stream these rather than
|
||||||
// trying to write them all in one message
|
// trying to write them all in one message
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import {Request, Reply, Message} from 'zeromq';
|
import {Request, Reply, Message} from 'zeromq';
|
||||||
import {EventEmitter} from 'node:events';
|
import {EventEmitter} from 'node:events';
|
||||||
import {PeerMethods} from './peers';
|
import {RequestMethods} from './peers';
|
||||||
import Debug from 'debug';
|
import Debug from 'debug';
|
||||||
import {RhizomeNode} from './node';
|
import {RhizomeNode} from './node';
|
||||||
import {PeerAddress} from './types';
|
import {PeerAddress} from './types';
|
||||||
const debug = Debug('request-reply');
|
const debug = Debug('request-reply');
|
||||||
|
|
||||||
export type PeerRequest = {
|
export type PeerRequest = {
|
||||||
method: PeerMethods;
|
method: RequestMethods;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RequestHandler = (req: PeerRequest, res: ResponseSocket) => void;
|
export type RequestHandler = (req: PeerRequest, res: ResponseSocket) => void;
|
||||||
|
@ -22,7 +22,7 @@ export class RequestSocket {
|
||||||
debug(`Request socket connecting to ${addrStr}`);
|
debug(`Request socket connecting to ${addrStr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async request(method: PeerMethods): Promise<Message> {
|
async request(method: RequestMethods): Promise<Message> {
|
||||||
const req: PeerRequest = {
|
const req: PeerRequest = {
|
||||||
method
|
method
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
import Debug from "debug";
|
||||||
|
import {FSWatcher, readdirSync, readFileSync, watch} from "fs";
|
||||||
|
import path, {join} from "path";
|
||||||
|
import {Converter} from "showdown";
|
||||||
|
const debug = Debug('md-files');
|
||||||
|
|
||||||
|
const docConverter = new Converter({
|
||||||
|
completeHTMLDocument: true,
|
||||||
|
// simpleLineBreaks: true,
|
||||||
|
tables: true,
|
||||||
|
tasklists: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export const htmlDocFromMarkdown = (md: string): string => docConverter.makeHtml(md);
|
||||||
|
|
||||||
|
type mdFileInfo = {
|
||||||
|
name: string,
|
||||||
|
md: string,
|
||||||
|
html: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export class MDFiles {
|
||||||
|
files = new Map<string, mdFileInfo>();
|
||||||
|
readme?: mdFileInfo;
|
||||||
|
dirWatcher?: FSWatcher;
|
||||||
|
readmeWatcher?: FSWatcher;
|
||||||
|
|
||||||
|
readFile(name: string) {
|
||||||
|
const md = readFileSync(join('./markdown', `${name}.md`)).toString();
|
||||||
|
const html = htmlDocFromMarkdown(md);
|
||||||
|
this.files.set(name, {name, md, html});
|
||||||
|
}
|
||||||
|
|
||||||
|
readReadme() {
|
||||||
|
const md = readFileSync('./README.md').toString();
|
||||||
|
const html = htmlDocFromMarkdown(md);
|
||||||
|
this.readme = {name: 'README', md, html};
|
||||||
|
}
|
||||||
|
|
||||||
|
getReadmeHTML() {
|
||||||
|
return this.readme?.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHtml(name: string): string | undefined {
|
||||||
|
return this.files.get(name)?.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
list(): string[] {
|
||||||
|
return Array.from(this.files.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
readDir() {
|
||||||
|
// Read list of markdown files from directory and
|
||||||
|
// render each markdown file as html
|
||||||
|
readdirSync('./markdown/')
|
||||||
|
.filter((f) => f.endsWith('.md'))
|
||||||
|
.map((name) => path.parse(name).name)
|
||||||
|
.forEach((name) => this.readFile(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
watchDir() {
|
||||||
|
this.dirWatcher = watch('./markdown', null, (eventType, filename) => {
|
||||||
|
if (!filename) return;
|
||||||
|
if (!filename.endsWith(".md")) return;
|
||||||
|
|
||||||
|
const name = path.parse(filename).name;
|
||||||
|
|
||||||
|
switch (eventType) {
|
||||||
|
case 'rename': {
|
||||||
|
debug(`file ${name} renamed`);
|
||||||
|
// Remove it from memory and re-scan everything
|
||||||
|
this.files.delete(name);
|
||||||
|
this.readDir();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'change': {
|
||||||
|
debug(`file ${name} changed`);
|
||||||
|
// Re-read this file
|
||||||
|
this.readFile(name)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watchReadme() {
|
||||||
|
this.readmeWatcher = watch('./README.md', null, (eventType, filename) => {
|
||||||
|
if (!filename) return;
|
||||||
|
|
||||||
|
switch (eventType) {
|
||||||
|
case 'change': {
|
||||||
|
debug(`README file changed`);
|
||||||
|
// Re-read this file
|
||||||
|
this.readReadme()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.dirWatcher?.close();
|
||||||
|
this.readmeWatcher?.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,6 @@
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
|
||||||
"@/*": ["src/*"]
|
|
||||||
},
|
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"importsNotUsedAsValues": "remove",
|
"importsNotUsedAsValues": "remove",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|
Loading…
Reference in New Issue