chore: make some things a bit more typesafe
This commit is contained in:
@@ -1,19 +1,24 @@
|
||||
import { NodeDefinitionSchema, type AsyncCache, type NodeDefinition, type NodeRegistry } from "@nodes/types";
|
||||
import {
|
||||
NodeDefinitionSchema,
|
||||
type AsyncCache,
|
||||
type NodeDefinition,
|
||||
type NodeRegistry,
|
||||
} from "@nodes/types";
|
||||
import { createLogger, createWasmWrapper } 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, private cache?: AsyncCache<ArrayBuffer>) { }
|
||||
constructor(
|
||||
private url: string,
|
||||
private cache?: AsyncCache<ArrayBuffer>,
|
||||
) {}
|
||||
|
||||
async fetchUsers() {
|
||||
const response = await this.fetch(`${this.url}/nodes/users.json`);
|
||||
@@ -32,7 +37,9 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
||||
}
|
||||
|
||||
async fetchCollection(userCollectionId: `${string}/${string}`) {
|
||||
const response = await this.fetch(`${this.url}/nodes/${userCollectionId}.json`);
|
||||
const response = await this.fetch(
|
||||
`${this.url}/nodes/${userCollectionId}.json`,
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load collection ${userCollectionId}`);
|
||||
}
|
||||
@@ -44,20 +51,16 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load node definition ${nodeId}`);
|
||||
}
|
||||
return response.json()
|
||||
return response.json();
|
||||
}
|
||||
|
||||
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
|
||||
|
||||
const fetchNode = async () => {
|
||||
const response = await this.fetch(`${this.url}/nodes/${nodeId}.wasm`);
|
||||
return response.arrayBuffer();
|
||||
}
|
||||
};
|
||||
|
||||
const res = await Promise.race([
|
||||
fetchNode(),
|
||||
this.cache?.get(nodeId)
|
||||
]);
|
||||
const res = await Promise.race([fetchNode(), this.cache?.get(nodeId)]);
|
||||
|
||||
if (!res) {
|
||||
throw new Error(`Failed to load node wasm ${nodeId}`);
|
||||
@@ -69,18 +72,17 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
||||
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
||||
const a = performance.now();
|
||||
|
||||
const nodes = await Promise.all([...new Set(nodeIds).values()].map(async id => {
|
||||
const nodes = await Promise.all(
|
||||
[...new Set(nodeIds).values()].map(async (id) => {
|
||||
if (this.nodes.has(id)) {
|
||||
return this.nodes.get(id)!;
|
||||
}
|
||||
|
||||
if (this.nodes.has(id)) {
|
||||
return this.nodes.get(id)!;
|
||||
}
|
||||
|
||||
const wasmBuffer = await this.fetchNodeWasm(id);
|
||||
|
||||
return this.register(wasmBuffer);
|
||||
|
||||
}));
|
||||
const wasmBuffer = await this.fetchNodeWasm(id);
|
||||
|
||||
return this.register(wasmBuffer);
|
||||
}),
|
||||
);
|
||||
|
||||
const duration = performance.now() - a;
|
||||
|
||||
@@ -90,11 +92,10 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
||||
log.groupEnd();
|
||||
this.status = "ready";
|
||||
|
||||
return nodes
|
||||
return nodes;
|
||||
}
|
||||
|
||||
async register(wasmBuffer: ArrayBuffer) {
|
||||
|
||||
const wrapper = createWasmWrapper(wasmBuffer);
|
||||
|
||||
const definition = NodeDefinitionSchema.safeParse(wrapper.get_definition());
|
||||
@@ -110,8 +111,8 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
||||
|
||||
let node = {
|
||||
...definition.data,
|
||||
execute: wrapper.execute
|
||||
}
|
||||
execute: wrapper.execute,
|
||||
};
|
||||
|
||||
this.nodes.set(definition.data.id, node);
|
||||
|
||||
|
||||
@@ -2,17 +2,17 @@ import { Graph, NodeDefinition, NodeId } from "./types";
|
||||
|
||||
export interface NodeRegistry {
|
||||
/**
|
||||
* The status of the node registry
|
||||
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
|
||||
*/
|
||||
* The status of the node registry
|
||||
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
|
||||
*/
|
||||
status: "loading" | "ready" | "error";
|
||||
/**
|
||||
* Load the nodes with the given ids
|
||||
* @param nodeIds - The ids of the nodes to load
|
||||
* @returns A promise that resolves when the nodes are loaded
|
||||
* @throws An error if the nodes could not be loaded
|
||||
* @remarks This method should be called before calling getNode or getAllNodes
|
||||
*/
|
||||
* @param nodeIds - The ids of the nodes to load
|
||||
* @returns A promise that resolves when the nodes are loaded
|
||||
* @throws An error if the nodes could not be loaded
|
||||
* @remarks This method should be called before calling getNode or getAllNodes
|
||||
*/
|
||||
load: (nodeIds: NodeId[]) => Promise<NodeDefinition[]>;
|
||||
/**
|
||||
* Get a node by id
|
||||
@@ -27,30 +27,30 @@ export interface NodeRegistry {
|
||||
getAllNodes: () => NodeDefinition[];
|
||||
|
||||
/**
|
||||
* Register a new node
|
||||
* @param wasmBuffer - The WebAssembly buffer for the node
|
||||
* @returns The node definition
|
||||
* Register a new node
|
||||
* @param wasmBuffer - The WebAssembly buffer for the node
|
||||
* @returns The node definition
|
||||
*/
|
||||
register: (wasmBuffer: ArrayBuffer) => Promise<NodeDefinition>;
|
||||
|
||||
|
||||
cache?: AsyncCache<ArrayBuffer>;
|
||||
}
|
||||
|
||||
export interface RuntimeExecutor {
|
||||
/**
|
||||
* Execute the given graph
|
||||
* @param graph - The graph to execute
|
||||
* @returns The result of the execution
|
||||
*/
|
||||
execute: (graph: Graph, settings: Record<string, unknown>) => Promise<Int32Array>;
|
||||
* Execute the given graph
|
||||
* @param graph - The graph to execute
|
||||
* @returns The result of the execution
|
||||
*/
|
||||
execute: (
|
||||
graph: Graph,
|
||||
settings: Record<string, unknown>,
|
||||
) => Promise<Int32Array>;
|
||||
}
|
||||
|
||||
export interface SyncCache<T = unknown> {
|
||||
/**
|
||||
* The maximum number of items that can be stored in the cache
|
||||
* @remarks When the cache size exceeds this value, the oldest items should be removed
|
||||
*/
|
||||
* The maximum number of items that can be stored in the cache
|
||||
* @remarks When the cache size exceeds this value, the oldest items should be removed
|
||||
*/
|
||||
size: number;
|
||||
|
||||
/**
|
||||
@@ -69,14 +69,13 @@ export interface SyncCache<T = unknown> {
|
||||
* Clear the cache
|
||||
*/
|
||||
clear: () => void;
|
||||
|
||||
}
|
||||
|
||||
export interface AsyncCache<T = unknown> {
|
||||
/**
|
||||
* The maximum number of items that can be stored in the cache
|
||||
* @remarks When the cache size exceeds this value, the oldest items should be removed
|
||||
*/
|
||||
* The maximum number of items that can be stored in the cache
|
||||
* @remarks When the cache size exceeds this value, the oldest items should be removed
|
||||
*/
|
||||
size: number;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
import { z } from "zod";
|
||||
import { NodeInputSchema } from "./inputs";
|
||||
|
||||
export type NodeId = `${string}/${string}/${string}`;
|
||||
export const NodeTypeSchema = z
|
||||
.string()
|
||||
.regex(/^[^/]+\/[^/]+\/[^/]+$/, "Invalid NodeId format")
|
||||
.transform((value) => value as `${string}/${string}/${string}`);
|
||||
|
||||
export type NodeType = z.infer<typeof NodeTypeSchema>;
|
||||
|
||||
export const NodeSchema = z.object({
|
||||
id: z.number(),
|
||||
type: z.string(),
|
||||
props: z.record(z.union([z.number(), z.array(z.number())])).optional(),
|
||||
meta: z.object({
|
||||
title: z.string().optional(),
|
||||
lastModified: z.string().optional(),
|
||||
}).optional(),
|
||||
position: z.tuple([z.number(), z.number()])
|
||||
type: NodeTypeSchema,
|
||||
tmp: z.any().optional(),
|
||||
props: z
|
||||
.record(z.string(), z.union([z.number(), z.array(z.number())]))
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
lastModified: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
position: z.tuple([z.number(), z.number()]),
|
||||
});
|
||||
|
||||
export type Node = {
|
||||
@@ -19,9 +29,9 @@ export type Node = {
|
||||
depth?: number;
|
||||
mesh?: any;
|
||||
random?: number;
|
||||
parents?: Node[],
|
||||
children?: Node[],
|
||||
inputNodes?: Record<string, Node>
|
||||
parents?: Node[];
|
||||
children?: Node[];
|
||||
inputNodes?: Record<string, Node>;
|
||||
type?: NodeDefinition;
|
||||
downX?: number;
|
||||
downY?: number;
|
||||
@@ -30,17 +40,19 @@ export type Node = {
|
||||
ref?: HTMLElement;
|
||||
visible?: boolean;
|
||||
isMoving?: boolean;
|
||||
}
|
||||
};
|
||||
} & z.infer<typeof NodeSchema>;
|
||||
|
||||
export const NodeDefinitionSchema = z.object({
|
||||
id: z.string(),
|
||||
inputs: z.record(NodeInputSchema).optional(),
|
||||
inputs: z.record(z.string(), NodeInputSchema).optional(),
|
||||
outputs: z.array(z.string()).optional(),
|
||||
meta: z.object({
|
||||
description: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
}).optional(),
|
||||
meta: z
|
||||
.object({
|
||||
description: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema> & {
|
||||
@@ -56,12 +68,14 @@ export type Socket = {
|
||||
export type Edge = [Node, number, Node, string];
|
||||
|
||||
export const GraphSchema = z.object({
|
||||
id: z.number().optional(),
|
||||
meta: z.object({
|
||||
title: z.string().optional(),
|
||||
lastModified: z.string().optional(),
|
||||
}).optional(),
|
||||
settings: z.record(z.any()).optional(),
|
||||
id: z.number(),
|
||||
meta: z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
lastModified: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
settings: z.record(z.string(), z.any()).optional(),
|
||||
nodes: z.array(NodeSchema),
|
||||
edges: z.array(z.tuple([z.number(), z.number(), z.number(), z.string()])),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user