This commit is contained in:
parent
9d4d67f086
commit
0c9a9269c4
@ -65,7 +65,7 @@ export function createNodePath({
|
||||
|
||||
export const debounce = (fn: Function, ms = 300) => {
|
||||
let timeoutId: ReturnType<typeof setTimeout>;
|
||||
return function(this: any, ...args: any[]) {
|
||||
return function (this: any, ...args: any[]) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => fn.apply(this, args), ms);
|
||||
};
|
||||
@ -131,41 +131,100 @@ export function humanizeDuration(durationInMilliseconds: number) {
|
||||
|
||||
return durationString.trim();
|
||||
}
|
||||
export function debounceAsyncFunction<T extends any[], R>(func: (...args: T) => Promise<R>): (...args: T) => Promise<R> {
|
||||
let currentPromise: Promise<R> | null = null;
|
||||
let nextArgs: T | null = null;
|
||||
let resolveNext: ((result: R) => void) | null = null;
|
||||
// export function debounceAsyncFunction<T extends any[], R>(
|
||||
// func: (...args: T) => Promise<R>
|
||||
// ): (...args: T) => Promise<R> {
|
||||
// let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
// let lastPromise: Promise<R> | null = null;
|
||||
// let lastReject: ((reason?: any) => void) | null = null;
|
||||
//
|
||||
// return (...args: T): Promise<R> => {
|
||||
// if (timeoutId) {
|
||||
// clearTimeout(timeoutId);
|
||||
// if (lastReject) {
|
||||
// lastReject(new Error("Debounced: Previous call was canceled."));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return new Promise<R>((resolve, reject) => {
|
||||
// lastReject = reject;
|
||||
// timeoutId = setTimeout(() => {
|
||||
// timeoutId = null;
|
||||
// lastReject = null;
|
||||
// lastPromise = func(...args).then(resolve, reject);
|
||||
// }, 300); // Default debounce time is 300ms; you can make this configurable.
|
||||
// });
|
||||
// };
|
||||
// }
|
||||
export function debounceAsyncFunction<T extends (...args: any[]) => Promise<any>>(asyncFn: T): T {
|
||||
let isRunning = false;
|
||||
let latestArgs: Parameters<T> | null = null;
|
||||
let resolveNext: (() => void) | null = null;
|
||||
|
||||
const debouncedFunction = async (...args: T): Promise<R> => {
|
||||
if (currentPromise) {
|
||||
// Store the latest arguments and create a new promise to resolve them later
|
||||
nextArgs = args;
|
||||
return new Promise<R>((resolve) => {
|
||||
return (async function serializedFunction(...args: Parameters<T>): Promise<ReturnType<T>> {
|
||||
latestArgs = args;
|
||||
|
||||
if (isRunning) {
|
||||
// Wait for the current execution to finish
|
||||
await new Promise<void>((resolve) => {
|
||||
resolveNext = resolve;
|
||||
});
|
||||
} else {
|
||||
// Execute the function immediately
|
||||
try {
|
||||
currentPromise = func(...args);
|
||||
const result = await currentPromise;
|
||||
return result;
|
||||
} finally {
|
||||
currentPromise = null;
|
||||
// If there are stored arguments, call the function again with the latest arguments
|
||||
if (nextArgs) {
|
||||
const argsToUse = nextArgs;
|
||||
const resolver = resolveNext;
|
||||
nextArgs = null;
|
||||
resolveNext = null;
|
||||
resolver!(await debouncedFunction(...argsToUse));
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate the function is running
|
||||
isRunning = true;
|
||||
|
||||
try {
|
||||
// Execute with the latest arguments
|
||||
const result = await asyncFn(...latestArgs!);
|
||||
return result;
|
||||
} finally {
|
||||
// Allow the next execution
|
||||
isRunning = false;
|
||||
|
||||
if (resolveNext) {
|
||||
resolveNext();
|
||||
resolveNext = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return debouncedFunction;
|
||||
}) as T;
|
||||
}
|
||||
|
||||
// export function debounceAsyncFunction<T extends any[], R>(func: (...args: T) => Promise<R>): (...args: T) => Promise<R> {
|
||||
// let currentPromise: Promise<R> | null = null;
|
||||
// let nextArgs: T | null = null;
|
||||
// let resolveNext: ((result: R) => void) | null = null;
|
||||
//
|
||||
// const debouncedFunction = async (...args: T): Promise<R> => {
|
||||
// if (currentPromise) {
|
||||
// // Store the latest arguments and create a new promise to resolve them later
|
||||
// nextArgs = args;
|
||||
// return new Promise<R>((resolve) => {
|
||||
// resolveNext = resolve;
|
||||
// });
|
||||
// } else {
|
||||
// // Execute the function immediately
|
||||
// try {
|
||||
// currentPromise = func(...args);
|
||||
// const result = await currentPromise;
|
||||
// return result;
|
||||
// } finally {
|
||||
// currentPromise = null;
|
||||
// // If there are stored arguments, call the function again with the latest arguments
|
||||
// if (nextArgs) {
|
||||
// const argsToUse = nextArgs;
|
||||
// const resolver = resolveNext;
|
||||
// nextArgs = null;
|
||||
// resolveNext = null;
|
||||
// resolver!(await debouncedFunction(...argsToUse));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// return debouncedFunction;
|
||||
// }
|
||||
|
||||
export function withArgsChangeOnly<T extends any[], R>(func: (...args: T) => R): (...args: T) => R {
|
||||
let lastArgs: T | undefined = undefined;
|
||||
let lastResult: R;
|
||||
|
@ -34,7 +34,7 @@
|
||||
let performanceStore = createPerformanceStore();
|
||||
|
||||
const registryCache = new IndexDBCache("node-registry");
|
||||
const nodeRegistry = new RemoteNodeRegistry("");
|
||||
const nodeRegistry = new RemoteNodeRegistry("http://localhost:8000/v1");
|
||||
nodeRegistry.cache = registryCache;
|
||||
const workerRuntime = new WorkerRuntimeExecutor();
|
||||
const runtimeCache = new MemoryRuntimeCache();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { type NodeRegistry, type NodeDefinition, NodeDefinitionSchema, type AsyncCache } from "@nodes/types";
|
||||
import { createWasmWrapper, createLogger } from "@nodes/utils";
|
||||
import { NodeDefinitionSchema, type AsyncCache, type NodeDefinition, type NodeRegistry } from "@nodes/types";
|
||||
import { createLogger, createWasmWrapper } from "@nodes/utils";
|
||||
|
||||
const log = createLogger("node-registry");
|
||||
log.mute();
|
||||
@ -49,18 +49,21 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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)
|
||||
]);
|
||||
|
||||
if (!res) {
|
||||
throw new Error(`Failed to load node wasm ${nodeId}`);
|
||||
}
|
||||
|
||||
return response.arrayBuffer();
|
||||
return res;
|
||||
}
|
||||
|
||||
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
||||
|
@ -52,8 +52,10 @@ async function postNode(node: Node) {
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const json = await res.text();
|
||||
console.log(json);
|
||||
console.log(`Uploaded ${node.id}`);
|
||||
} else {
|
||||
const text = await res.text();
|
||||
console.log(`Failed to upload ${node.id}: ${text}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"tasks": {
|
||||
"dev": "deno run --watch main.ts",
|
||||
"test": "deno run vitest",
|
||||
"drizzle": "docker compose exec app deno --env -A --node-modules-dir npm:drizzle-kit",
|
||||
"drizzle": "podman-compose exec app deno --env -A --node-modules-dir npm:drizzle-kit",
|
||||
"upload": "deno run --allow-read --allow-net bin/upload.ts"
|
||||
},
|
||||
"imports": {
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from "../routes/user/user.schema.ts";
|
||||
export * from "../routes/node/node.schema.ts";
|
||||
export * from "../routes/node/schemas/node.schema.ts";
|
||||
|
@ -12,14 +12,14 @@ app.use("/v1/*", cors());
|
||||
app.use(logger());
|
||||
app.route("v1", router);
|
||||
|
||||
app.doc("/doc", {
|
||||
app.doc("/openapi.json", {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
version: "1.0.0",
|
||||
title: "My API",
|
||||
title: "Nodarium API",
|
||||
},
|
||||
});
|
||||
|
||||
app.get("/ui", swaggerUI({ url: "/doc" }));
|
||||
app.get("/ui", swaggerUI({ url: "/openapi.json" }));
|
||||
|
||||
Deno.serve(app.fetch);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||
import { NodeDefinitionSchema } from "./types.ts";
|
||||
import { HTTPException } from "hono/http-exception";
|
||||
import { idRegex, NodeDefinitionSchema } from "./schemas/types.ts";
|
||||
import * as service from "./node.service.ts";
|
||||
import { bodyLimit } from "hono/body-limit";
|
||||
|
||||
@ -11,24 +12,49 @@ const SingleParam = (name: string) =>
|
||||
.min(3)
|
||||
.max(20)
|
||||
.refine(
|
||||
(value) => /^[a-z_-]+$/i.test(value),
|
||||
(value) => idRegex.test(value),
|
||||
"Name should contain only alphabets",
|
||||
)
|
||||
.openapi({ param: { name, in: "path" } });
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
userId: SingleParam("userId"),
|
||||
nodeSystemId: SingleParam("nodeSystemId"),
|
||||
user: SingleParam("user"),
|
||||
system: SingleParam("system"),
|
||||
nodeId: SingleParam("nodeId"),
|
||||
});
|
||||
|
||||
const getUserNodesRoute = createRoute({
|
||||
method: "get",
|
||||
path: "/{user}.json",
|
||||
request: {
|
||||
params: z.object({
|
||||
user: SingleParam("user"),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(NodeDefinitionSchema),
|
||||
},
|
||||
},
|
||||
description: "Retrieve a single node definition",
|
||||
},
|
||||
},
|
||||
});
|
||||
nodeRouter.openapi(getUserNodesRoute, async (c) => {
|
||||
const userId = c.req.param("user.json").replace(/\.json$/, "");
|
||||
const nodes = await service.getNodeDefinitionsByUser(userId);
|
||||
return c.json(nodes);
|
||||
});
|
||||
|
||||
const getNodeCollectionRoute = createRoute({
|
||||
method: "get",
|
||||
path: "/{userId}/{nodeSystemId}.json",
|
||||
path: "/{user}/{system}.json",
|
||||
request: {
|
||||
params: z.object({
|
||||
userId: SingleParam("userId"),
|
||||
nodeSystemId: SingleParam("nodeSystemId").optional(),
|
||||
user: SingleParam("user"),
|
||||
system: SingleParam("system").optional(),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
@ -43,17 +69,16 @@ const getNodeCollectionRoute = createRoute({
|
||||
},
|
||||
});
|
||||
nodeRouter.openapi(getNodeCollectionRoute, async (c) => {
|
||||
const { userId } = c.req.valid("param");
|
||||
const nodeSystemId = c.req.param("nodeSystemId.json").replace(/\.json$/, "");
|
||||
|
||||
const nodes = await service.getNodesBySystem(userId, nodeSystemId);
|
||||
const { user } = c.req.valid("param");
|
||||
const nodeSystemId = c.req.param("system.json").replace(/\.json$/, "");
|
||||
|
||||
const nodes = await service.getNodesBySystem(user, nodeSystemId);
|
||||
return c.json(nodes);
|
||||
});
|
||||
|
||||
const getNodeDefinitionRoute = createRoute({
|
||||
method: "get",
|
||||
path: "/{userId}/{nodeSystemId}/{nodeId}.json",
|
||||
path: "/{user}/{system}/{nodeId}{.+\\.json}",
|
||||
request: {
|
||||
params: ParamsSchema,
|
||||
},
|
||||
@ -68,15 +93,25 @@ const getNodeDefinitionRoute = createRoute({
|
||||
},
|
||||
},
|
||||
});
|
||||
nodeRouter.openapi(getNodeDefinitionRoute, (c) => {
|
||||
return c.json({
|
||||
id: "",
|
||||
});
|
||||
nodeRouter.openapi(getNodeDefinitionRoute, async (c) => {
|
||||
const { user, system, nodeId } = c.req.valid("param");
|
||||
|
||||
const node = await service.getNodeDefinitionById(
|
||||
user,
|
||||
system,
|
||||
nodeId.replace(/\.json$/, ""),
|
||||
);
|
||||
|
||||
if (!node) {
|
||||
throw new HTTPException(404);
|
||||
}
|
||||
|
||||
return c.json(node);
|
||||
});
|
||||
|
||||
const getNodeWasmRoute = createRoute({
|
||||
method: "get",
|
||||
path: "/{userId}/{nodeSystemId}/{nodeId}.wasm",
|
||||
path: "/{user}/{system}/{nodeId}{.+\\.wasm}",
|
||||
request: {
|
||||
params: ParamsSchema,
|
||||
},
|
||||
@ -91,11 +126,18 @@ const getNodeWasmRoute = createRoute({
|
||||
},
|
||||
},
|
||||
});
|
||||
nodeRouter.openapi(getNodeWasmRoute, async (c) => {
|
||||
const { user, system, nodeId } = c.req.valid("param");
|
||||
|
||||
nodeRouter.openapi(getNodeWasmRoute, (c) => {
|
||||
return c.json({
|
||||
id: "",
|
||||
});
|
||||
const wasmContent = await service.getNodeWasmById(
|
||||
user,
|
||||
system,
|
||||
nodeId.replace(/\.wasm/, ""),
|
||||
);
|
||||
|
||||
c.header("Content-Type", "application/wasm");
|
||||
|
||||
return c.body(wasmContent);
|
||||
});
|
||||
|
||||
const createNodeRoute = createRoute({
|
||||
@ -113,20 +155,17 @@ const createNodeRoute = createRoute({
|
||||
},
|
||||
middleware: [
|
||||
bodyLimit({
|
||||
maxSize: 50 * 1024, // 50kb
|
||||
maxSize: 128 * 1024, // 128kb
|
||||
onError: (c) => {
|
||||
return c.text("overflow :(", 413);
|
||||
return c.text("Node content too large", 413);
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
nodeRouter.openapi(createNodeRoute, async (c) => {
|
||||
const buffer = await c.req.arrayBuffer();
|
||||
const bytes = await (await c.req.blob()).bytes();
|
||||
|
||||
const node = await service.createNode(buffer, bytes);
|
||||
|
||||
return c.json(node);
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { db } from "../../db/db.ts";
|
||||
import { nodeTable } from "./node.schema.ts";
|
||||
import { NodeDefinition, NodeDefinitionSchema } from "./types.ts";
|
||||
import { nodeTable } from "./schemas/node.schema.ts";
|
||||
import { NodeDefinition, NodeDefinitionSchema } from "./schemas/types.ts";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { createHash } from "node:crypto";
|
||||
import { WorkerMessage } from "./worker/types.ts";
|
||||
|
||||
export type CreateNodeDTO = {
|
||||
id: string;
|
||||
@ -10,10 +12,21 @@ export type CreateNodeDTO = {
|
||||
content: ArrayBuffer;
|
||||
};
|
||||
|
||||
function getNodeHash(content: Uint8Array) {
|
||||
const hash = createHash("sha256");
|
||||
hash.update(content);
|
||||
return hash.digest("hex").slice(0, 8);
|
||||
}
|
||||
|
||||
function extractDefinition(content: ArrayBuffer): Promise<NodeDefinition> {
|
||||
const worker = new Worker(new URL("./node.worker.ts", import.meta.url).href, {
|
||||
type: "module",
|
||||
});
|
||||
const worker = new Worker(
|
||||
new URL("./worker/node.worker.ts", import.meta.url).href,
|
||||
{
|
||||
type: "module",
|
||||
},
|
||||
) as Worker & {
|
||||
postMessage: (message: WorkerMessage) => void;
|
||||
};
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
worker.postMessage({ action: "extract-definition", content });
|
||||
@ -22,12 +35,12 @@ function extractDefinition(content: ArrayBuffer): Promise<NodeDefinition> {
|
||||
rej(new Error("Worker timeout out"));
|
||||
}, 100);
|
||||
worker.onmessage = function (e) {
|
||||
console.log(e.data);
|
||||
switch (e.data.action) {
|
||||
case "result":
|
||||
res(e.data.result);
|
||||
break;
|
||||
case "error":
|
||||
console.log("Worker error", e.data.error);
|
||||
rej(e.data.result);
|
||||
break;
|
||||
default:
|
||||
@ -51,20 +64,28 @@ export async function createNode(
|
||||
systemId,
|
||||
nodeId,
|
||||
definition: def,
|
||||
hash: getNodeHash(content),
|
||||
content: content,
|
||||
};
|
||||
|
||||
await db.insert(nodeTable).values(node);
|
||||
console.log("New user created!");
|
||||
// await db.insert(users).values({ name: "Andrew" });
|
||||
console.log("new node created!");
|
||||
return def;
|
||||
} catch (error) {
|
||||
console.log({ error });
|
||||
console.log("Creation Error", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNodesByUser(userName: string) {}
|
||||
export function getNodeDefinitionsByUser(userName: string) {
|
||||
return db.select({ definition: nodeTable.definition }).from(nodeTable)
|
||||
.where(
|
||||
and(
|
||||
eq(nodeTable.userId, userName),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function getNodesBySystem(
|
||||
username: string,
|
||||
systemId: string,
|
||||
@ -84,4 +105,51 @@ export async function getNodesBySystem(
|
||||
return definitions;
|
||||
}
|
||||
|
||||
export async function getNodeById(dto: CreateNodeDTO) {}
|
||||
export async function getNodeWasmById(
|
||||
userName: string,
|
||||
systemId: string,
|
||||
nodeId: string,
|
||||
) {
|
||||
const node = await db.select({ content: nodeTable.content }).from(nodeTable)
|
||||
.where(
|
||||
and(
|
||||
eq(nodeTable.userId, userName),
|
||||
eq(nodeTable.systemId, systemId),
|
||||
eq(nodeTable.nodeId, nodeId),
|
||||
),
|
||||
).limit(1);
|
||||
|
||||
if (!node[0]) {
|
||||
throw new Error("Node not found");
|
||||
}
|
||||
|
||||
return node[0].content;
|
||||
}
|
||||
|
||||
export async function getNodeDefinitionById(
|
||||
userName: string,
|
||||
systemId: string,
|
||||
nodeId: string,
|
||||
) {
|
||||
const node = await db.select({ definition: nodeTable.definition }).from(
|
||||
nodeTable,
|
||||
).where(
|
||||
and(
|
||||
eq(nodeTable.userId, userName),
|
||||
eq(nodeTable.systemId, systemId),
|
||||
eq(nodeTable.nodeId, nodeId),
|
||||
),
|
||||
).limit(1);
|
||||
|
||||
if (!node[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const definition = NodeDefinitionSchema.safeParse(node[0]?.definition);
|
||||
|
||||
if (!definition.data) {
|
||||
throw new Error("Invalid definition");
|
||||
}
|
||||
|
||||
return definition.data;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import { router } from "../router.ts";
|
||||
Deno.test("simple test", async () => {
|
||||
const res = await router.request("/max/plants/test.json");
|
||||
const json = await res.text();
|
||||
console.log({ json });
|
||||
|
||||
expect(true).toEqual(true);
|
||||
|
||||
|
@ -1,30 +0,0 @@
|
||||
/// <reference lib="webworker" />
|
||||
|
||||
import { NodeDefinitionSchema } from "./types.ts";
|
||||
import { createWasmWrapper } from "./utils.ts";
|
||||
|
||||
function extractDefinition(wasmCode: ArrayBuffer) {
|
||||
const wasm = createWasmWrapper(wasmCode);
|
||||
|
||||
const definition = wasm.get_definition();
|
||||
|
||||
const p = NodeDefinitionSchema.safeParse(definition);
|
||||
|
||||
if (!p.success) {
|
||||
self.postMessage({ action: "error", error: p.error });
|
||||
return;
|
||||
}
|
||||
|
||||
self.postMessage({ action: "result", result: p.data });
|
||||
}
|
||||
|
||||
self.onmessage = (e) => {
|
||||
switch (e.data.action) {
|
||||
case "extract-definition":
|
||||
extractDefinition(e.data.content);
|
||||
self.close();
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknwon action", e.data.action);
|
||||
}
|
||||
};
|
@ -10,7 +10,7 @@ const DefaultOptionsSchema = z.object({
|
||||
hidden: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputFloatSchema = z.object({
|
||||
const NodeInputFloatSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("float"),
|
||||
element: z.literal("slider").optional(),
|
||||
@ -20,7 +20,7 @@ export const NodeInputFloatSchema = z.object({
|
||||
step: z.number().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputIntegerSchema = z.object({
|
||||
const NodeInputIntegerSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("integer"),
|
||||
element: z.literal("slider").optional(),
|
||||
@ -29,37 +29,37 @@ export const NodeInputIntegerSchema = z.object({
|
||||
max: z.number().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputBooleanSchema = z.object({
|
||||
const NodeInputBooleanSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("boolean"),
|
||||
value: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputSelectSchema = z.object({
|
||||
const NodeInputSelectSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("select"),
|
||||
options: z.array(z.string()).optional(),
|
||||
value: z.number().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputSeedSchema = z.object({
|
||||
const NodeInputSeedSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("seed"),
|
||||
value: z.number().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputVec3Schema = z.object({
|
||||
const NodeInputVec3Schema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("vec3"),
|
||||
value: z.array(z.number()).optional(),
|
||||
});
|
||||
|
||||
export const NodeInputGeometrySchema = z.object({
|
||||
const NodeInputGeometrySchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("geometry"),
|
||||
});
|
||||
|
||||
export const NodeInputPathSchema = z.object({
|
||||
const NodeInputPathSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("path"),
|
||||
});
|
@ -7,7 +7,7 @@ import {
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { relations } from "drizzle-orm/relations";
|
||||
import { usersTable } from "../user/user.schema.ts";
|
||||
import { usersTable } from "../../user/user.schema.ts";
|
||||
|
||||
const bytea = customType<{
|
||||
data: ArrayBuffer;
|
||||
@ -25,6 +25,7 @@ export const nodeTable = pgTable("nodes", {
|
||||
nodeId: varchar().notNull(),
|
||||
content: bytea().notNull(),
|
||||
definition: json().notNull(),
|
||||
hash: varchar({ length: 8 }).notNull(),
|
||||
previous: integer(),
|
||||
});
|
||||
|
31
store/src/routes/node/schemas/types.ts
Normal file
31
store/src/routes/node/schemas/types.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { z } from "zod";
|
||||
import { NodeInputSchema } from "./inputs.ts";
|
||||
|
||||
export type NodeId = `${string}/${string}/${string}`;
|
||||
|
||||
export const idRegex = /[a-z0-9-]+/i;
|
||||
|
||||
const idSchema = z
|
||||
.string()
|
||||
.regex(
|
||||
new RegExp(
|
||||
`^(${idRegex.source})/(${idRegex.source})/(${idRegex.source})$`,
|
||||
),
|
||||
"Invalid id format",
|
||||
);
|
||||
|
||||
export const NodeDefinitionSchema = z
|
||||
.object({
|
||||
id: idSchema,
|
||||
inputs: z.record(NodeInputSchema).optional(),
|
||||
outputs: z.array(z.string()).optional(),
|
||||
meta: z
|
||||
.object({
|
||||
description: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.openapi("NodeDefinition");
|
||||
|
||||
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema>;
|
@ -1,69 +0,0 @@
|
||||
import { z } from "zod";
|
||||
import { NodeInputSchema } from "./inputs.ts";
|
||||
|
||||
export type NodeId = `${string}/${string}/${string}`;
|
||||
|
||||
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()]),
|
||||
});
|
||||
|
||||
export type Node = z.infer<typeof NodeSchema>;
|
||||
|
||||
const partPattern = /[a-z-_]{3,32}/;
|
||||
|
||||
const idSchema = z
|
||||
.string()
|
||||
.regex(
|
||||
new RegExp(
|
||||
`^(${partPattern.source})/(${partPattern.source})/(${partPattern.source})$`,
|
||||
),
|
||||
"Invalid id format",
|
||||
);
|
||||
|
||||
export const NodeDefinitionSchema = z
|
||||
.object({
|
||||
id: idSchema,
|
||||
inputs: z.record(NodeInputSchema).optional(),
|
||||
outputs: z.array(z.string()).optional(),
|
||||
meta: z
|
||||
.object({
|
||||
description: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.openapi("NodeDefinition");
|
||||
|
||||
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema>;
|
||||
|
||||
export type Socket = {
|
||||
node: Node;
|
||||
index: number | string;
|
||||
position: [number, number];
|
||||
};
|
||||
|
||||
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(),
|
||||
nodes: z.array(NodeSchema),
|
||||
edges: z.array(z.tuple([z.number(), z.number(), z.number(), z.string()])),
|
||||
});
|
||||
|
||||
export type Graph = z.infer<typeof GraphSchema> & { nodes: Node[] };
|
40
store/src/routes/node/worker/node.worker.ts
Normal file
40
store/src/routes/node/worker/node.worker.ts
Normal file
@ -0,0 +1,40 @@
|
||||
/// <reference lib="webworker" />
|
||||
|
||||
import { NodeDefinitionSchema } from "../schemas/types.ts";
|
||||
import { WorkerMessage } from "./types.ts";
|
||||
import { createWasmWrapper } from "./utils.ts";
|
||||
|
||||
const workerSelf = self as DedicatedWorkerGlobalScope & {
|
||||
postMessage: (message: WorkerMessage) => void;
|
||||
};
|
||||
|
||||
function extractDefinition(wasmCode: ArrayBuffer) {
|
||||
try {
|
||||
const wasm = createWasmWrapper(wasmCode);
|
||||
|
||||
const definition = wasm.get_definition();
|
||||
|
||||
const p = NodeDefinitionSchema.safeParse(definition);
|
||||
|
||||
if (!p.success) {
|
||||
workerSelf.postMessage({ action: "error", error: p.error });
|
||||
return;
|
||||
}
|
||||
|
||||
workerSelf.postMessage({ action: "result", result: p.data });
|
||||
} catch (e) {
|
||||
console.log("HEEERE", e);
|
||||
workerSelf.postMessage({ action: "error", error: e });
|
||||
}
|
||||
}
|
||||
|
||||
self.onmessage = (e: MessageEvent<WorkerMessage>) => {
|
||||
switch (e.data.action) {
|
||||
case "extract-definition":
|
||||
extractDefinition(e.data.content);
|
||||
self.close();
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown action: " + e.data.action);
|
||||
}
|
||||
};
|
21
store/src/routes/node/worker/types.ts
Normal file
21
store/src/routes/node/worker/types.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { NodeDefinition } from "../schemas/types.ts";
|
||||
|
||||
type ExtractDefinitionMessage = {
|
||||
action: "extract-definition";
|
||||
content: ArrayBuffer;
|
||||
};
|
||||
|
||||
type ErrorMessage = {
|
||||
action: "error";
|
||||
error: Error;
|
||||
};
|
||||
|
||||
type ResultMessage = {
|
||||
action: "result";
|
||||
result: NodeDefinition;
|
||||
};
|
||||
|
||||
export type WorkerMessage =
|
||||
| ErrorMessage
|
||||
| ResultMessage
|
||||
| ExtractDefinitionMessage;
|
@ -1,5 +1,5 @@
|
||||
// @ts-nocheck: Nocheck
|
||||
import { NodeDefinition } from "./types.ts";
|
||||
import { NodeDefinition } from "../schemas/types.ts";
|
||||
|
||||
const cachedTextDecoder = new TextDecoder("utf-8", {
|
||||
ignoreBOM: true,
|
@ -5,6 +5,6 @@ import { userRouter } from "./user/user.controller.ts";
|
||||
const router = new OpenAPIHono();
|
||||
|
||||
router.route("nodes", nodeRouter);
|
||||
router.route("nodes", userRouter);
|
||||
router.route("users", userRouter);
|
||||
|
||||
export { router };
|
||||
|
@ -22,7 +22,7 @@ export async function findUserByName(userName: string) {
|
||||
const users = await db
|
||||
.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.name, userName));
|
||||
.where(eq(usersTable.name, userName)).limit(1);
|
||||
|
||||
return users[0];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user