chore: setup linting
This commit is contained in:
2
app/src/lib/node-registry/index.ts
Normal file
2
app/src/lib/node-registry/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './node-registry-cache';
|
||||
export * from './node-registry-client';
|
||||
56
app/src/lib/node-registry/node-registry-cache.ts
Normal file
56
app/src/lib/node-registry/node-registry-cache.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { AsyncCache } from '@nodarium/types';
|
||||
import { type IDBPDatabase, openDB } from 'idb';
|
||||
|
||||
export class IndexDBCache implements AsyncCache<unknown> {
|
||||
size: number = 100;
|
||||
|
||||
db: Promise<IDBPDatabase<unknown>>;
|
||||
private _cache = new Map<string, unknown>();
|
||||
|
||||
constructor(id: string) {
|
||||
this.db = openDB<unknown>('cache/' + id, 1, {
|
||||
upgrade(db) {
|
||||
db.createObjectStore('keyval');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T> {
|
||||
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 as T;
|
||||
}
|
||||
|
||||
async getArrayBuffer(key: string) {
|
||||
const res = await this.get(key);
|
||||
if (!res) return;
|
||||
if (res instanceof ArrayBuffer) {
|
||||
return res;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async getString(key: string) {
|
||||
const res = await this.get(key);
|
||||
if (!res) return;
|
||||
if (typeof res === 'string') {
|
||||
return res;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async set(key: string, value: unknown) {
|
||||
this._cache.set(key, value);
|
||||
const db = await this.db;
|
||||
await db.put('keyval', value, key);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.db.then(db => db.clear('keyval'));
|
||||
}
|
||||
}
|
||||
168
app/src/lib/node-registry/node-registry-client.ts
Normal file
168
app/src/lib/node-registry/node-registry-client.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import {
|
||||
type AsyncCache,
|
||||
type NodeDefinition,
|
||||
NodeDefinitionSchema,
|
||||
type NodeRegistry
|
||||
} from '@nodarium/types';
|
||||
import { createLogger, createWasmWrapper } from '@nodarium/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();
|
||||
|
||||
constructor(
|
||||
private url: string,
|
||||
public cache?: AsyncCache<ArrayBuffer | string>
|
||||
) {}
|
||||
|
||||
async fetchJson(url: string, skipCache = false) {
|
||||
const finalUrl = `${this.url}/${url}`;
|
||||
|
||||
if (!skipCache && this.cache) {
|
||||
const cachedValue = await this.cache?.get<string>(finalUrl);
|
||||
if (cachedValue) {
|
||||
// fetch again in the background, maybe implement that only refetch after a certain time
|
||||
this.fetchJson(url, true);
|
||||
return JSON.parse(cachedValue);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(finalUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
log.error(`Failed to load ${url}`, { response, url, host: this.url });
|
||||
throw new Error(`Failed to load ${url}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
this.cache?.set(finalUrl, JSON.stringify(result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchArrayBuffer(url: string, skipCache = false) {
|
||||
const finalUrl = `${this.url}/${url}`;
|
||||
|
||||
if (!skipCache && this.cache) {
|
||||
const cachedNode = await this.cache?.get<ArrayBuffer>(finalUrl);
|
||||
if (cachedNode) {
|
||||
// fetch again in the background, maybe implement that only refetch after a certain time
|
||||
this.fetchArrayBuffer(url, true);
|
||||
return cachedNode;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(finalUrl);
|
||||
if (!response.ok) {
|
||||
log.error(`Failed to load ${url}`, { response, url, host: this.url });
|
||||
throw new Error(`Failed to load ${url}`);
|
||||
}
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
this.cache?.set(finalUrl, buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
async fetchUsers() {
|
||||
return this.fetchJson(`nodes/users.json`);
|
||||
}
|
||||
|
||||
async fetchUser(userId: `${string}`) {
|
||||
return this.fetchJson(`user/${userId}.json`);
|
||||
}
|
||||
|
||||
async fetchCollection(userCollectionId: `${string}/${string}`) {
|
||||
const col = await this.fetchJson(`nodes/${userCollectionId}.json`);
|
||||
return col;
|
||||
}
|
||||
|
||||
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
|
||||
return this.fetchJson(`nodes/${nodeId}.json`);
|
||||
}
|
||||
|
||||
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
|
||||
const node = await this.fetchArrayBuffer(`nodes/${nodeId}.wasm`);
|
||||
if (!node) {
|
||||
throw new Error(`Failed to load node wasm ${nodeId}`);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
try {
|
||||
return await this.register(id, wasmBuffer);
|
||||
} catch (e) {
|
||||
console.log('Failed to register: ', id);
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
})
|
||||
)).filter(Boolean) as NodeDefinition[];
|
||||
|
||||
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(id: string, wasmBuffer: ArrayBuffer) {
|
||||
let wrapper: ReturnType<typeof createWasmWrapper> = null!;
|
||||
try {
|
||||
wrapper = createWasmWrapper(wasmBuffer);
|
||||
} catch (error) {
|
||||
console.error(`Failed to create node wrapper for node: ${id}`, error);
|
||||
}
|
||||
|
||||
const rawDefinition = wrapper.get_definition();
|
||||
const definition = NodeDefinitionSchema.safeParse(rawDefinition);
|
||||
|
||||
if (definition.error) {
|
||||
throw new Error(
|
||||
'Failed to parse node definition from node:+\n' + JSON.stringify(rawDefinition, null, 2)
|
||||
+ '\n'
|
||||
+ definition.error
|
||||
);
|
||||
}
|
||||
|
||||
if (this.cache) {
|
||||
this.cache.set(definition.data.id, wasmBuffer);
|
||||
}
|
||||
|
||||
const 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