feat: move registry and runtime into separate packages
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m32s
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m32s
This commit is contained in:
17
packages/registry/package.json
Normal file
17
packages/registry/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@nodes/registry",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nodes/types": "link:../types",
|
||||
"@nodes/utils": "link:../utils",
|
||||
"idb": "^8.0.0"
|
||||
}
|
||||
}
|
2
packages/registry/src/index.ts
Normal file
2
packages/registry/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./node-registry-cache";
|
||||
export * from "./node-registry-client";
|
38
packages/registry/src/node-registry-cache.ts
Normal file
38
packages/registry/src/node-registry-cache.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import type { AsyncCache } from '@nodes/types';
|
||||
import { openDB, type IDBPDatabase } from 'idb';
|
||||
|
||||
export class IndexDBCache implements AsyncCache<ArrayBuffer> {
|
||||
|
||||
size: number = 100;
|
||||
|
||||
db: Promise<IDBPDatabase<ArrayBuffer>>;
|
||||
private _cache = new Map<string, ArrayBuffer>();
|
||||
|
||||
constructor(id: string) {
|
||||
this.db = openDB<ArrayBuffer>('cache/' + id, 1, {
|
||||
upgrade(db) {
|
||||
db.createObjectStore('keyval');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async get(key: string) {
|
||||
let res = this._cache.get(key);
|
||||
if (!res) {
|
||||
res = await (await this.db).get('keyval', key);
|
||||
}
|
||||
if (res) {
|
||||
this._cache.set(key, res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
async set(key: string, value: ArrayBuffer) {
|
||||
this._cache.set(key, value);
|
||||
const db = await this.db;
|
||||
await db.put('keyval', value, key);
|
||||
}
|
||||
clear() {
|
||||
this.db.then(db => db.clear('keyval'));
|
||||
}
|
||||
|
||||
}
|
125
packages/registry/src/node-registry-client.ts
Normal file
125
packages/registry/src/node-registry-client.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { type NodeRegistry, type NodeDefinition, NodeDefinitionSchema, type AsyncCache } from "@nodes/types";
|
||||
import { createWasmWrapper, createLogger } from "@nodes/utils";
|
||||
|
||||
const log = createLogger("node-registry");
|
||||
log.mute();
|
||||
|
||||
export class RemoteNodeRegistry implements NodeRegistry {
|
||||
|
||||
status: "loading" | "ready" | "error" = "loading";
|
||||
private nodes: Map<string, NodeDefinition> = new Map();
|
||||
|
||||
cache?: AsyncCache<ArrayBuffer>;
|
||||
|
||||
fetch: typeof fetch = globalThis.fetch.bind(globalThis);
|
||||
|
||||
constructor(private url: string) { }
|
||||
|
||||
async fetchUsers() {
|
||||
const response = await this.fetch(`${this.url}/nodes/users.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load users`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchUser(userId: `${string}`) {
|
||||
const response = await this.fetch(`${this.url}/nodes/${userId}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load user ${userId}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchCollection(userCollectionId: `${string}/${string}`) {
|
||||
const response = await this.fetch(`${this.url}/nodes/${userCollectionId}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load collection ${userCollectionId}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
|
||||
const response = await this.fetch(`${this.url}/nodes/${nodeId}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load node definition ${nodeId}`);
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
|
||||
|
||||
const response = await this.fetch(`${this.url}/nodes/${nodeId}.wasm`);
|
||||
if (!response.ok) {
|
||||
if (this.cache) {
|
||||
let value = await this.cache.get(nodeId);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new Error(`Failed to load node wasm ${nodeId}`);
|
||||
}
|
||||
|
||||
return response.arrayBuffer();
|
||||
}
|
||||
|
||||
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
||||
const a = performance.now();
|
||||
|
||||
const nodes = await Promise.all([...new Set(nodeIds).values()].map(async id => {
|
||||
|
||||
if (this.nodes.has(id)) {
|
||||
return this.nodes.get(id)!;
|
||||
}
|
||||
|
||||
const wasmBuffer = await this.fetchNodeWasm(id);
|
||||
|
||||
return this.register(wasmBuffer);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
const duration = performance.now() - a;
|
||||
|
||||
log.group("loaded nodes in", duration, "ms");
|
||||
log.info(nodeIds);
|
||||
log.info(nodes);
|
||||
log.groupEnd();
|
||||
this.status = "ready";
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
async register(wasmBuffer: ArrayBuffer) {
|
||||
|
||||
const wrapper = createWasmWrapper(wasmBuffer);
|
||||
|
||||
const definition = NodeDefinitionSchema.safeParse(wrapper.get_definition());
|
||||
|
||||
if (definition.error) {
|
||||
console.error(definition.error);
|
||||
throw definition.error;
|
||||
}
|
||||
|
||||
if (this.cache) {
|
||||
await this.cache.set(definition.data.id, wasmBuffer);
|
||||
}
|
||||
|
||||
let node = {
|
||||
...definition.data,
|
||||
execute: wrapper.execute
|
||||
}
|
||||
|
||||
this.nodes.set(definition.data.id, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
getNode(id: string) {
|
||||
return this.nodes.get(id);
|
||||
}
|
||||
|
||||
getAllNodes() {
|
||||
return [...this.nodes.values()];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user