feat: some shit
Some checks failed
Deploy to GitHub Pages / build_site (push) Failing after 6s

This commit is contained in:
max_richter 2024-12-18 17:26:24 +01:00
parent 9d4d67f086
commit 0c9a9269c4
20 changed files with 361 additions and 197 deletions

View File

@ -131,40 +131,99 @@ export function humanizeDuration(durationInMilliseconds: number) {
return durationString.trim(); return durationString.trim();
} }
export function debounceAsyncFunction<T extends any[], R>(func: (...args: T) => Promise<R>): (...args: T) => Promise<R> { // export function debounceAsyncFunction<T extends any[], R>(
let currentPromise: Promise<R> | null = null; // func: (...args: T) => Promise<R>
let nextArgs: T | null = null; // ): (...args: T) => Promise<R> {
let resolveNext: ((result: R) => void) | null = null; // 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> => { return (async function serializedFunction(...args: Parameters<T>): Promise<ReturnType<T>> {
if (currentPromise) { latestArgs = args;
// Store the latest arguments and create a new promise to resolve them later
nextArgs = args; if (isRunning) {
return new Promise<R>((resolve) => { // Wait for the current execution to finish
await new Promise<void>((resolve) => {
resolveNext = resolve; resolveNext = resolve;
}); });
} else { }
// Execute the function immediately
// Indicate the function is running
isRunning = true;
try { try {
currentPromise = func(...args); // Execute with the latest arguments
const result = await currentPromise; const result = await asyncFn(...latestArgs!);
return result; return result;
} finally { } finally {
currentPromise = null; // Allow the next execution
// If there are stored arguments, call the function again with the latest arguments isRunning = false;
if (nextArgs) {
const argsToUse = nextArgs;
const resolver = resolveNext;
nextArgs = null;
resolveNext = null;
resolver!(await debouncedFunction(...argsToUse));
}
}
}
};
return debouncedFunction; if (resolveNext) {
resolveNext();
resolveNext = null;
} }
}
}) 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 { export function withArgsChangeOnly<T extends any[], R>(func: (...args: T) => R): (...args: T) => R {
let lastArgs: T | undefined = undefined; let lastArgs: T | undefined = undefined;

View File

@ -34,7 +34,7 @@
let performanceStore = createPerformanceStore(); let performanceStore = createPerformanceStore();
const registryCache = new IndexDBCache("node-registry"); const registryCache = new IndexDBCache("node-registry");
const nodeRegistry = new RemoteNodeRegistry(""); const nodeRegistry = new RemoteNodeRegistry("http://localhost:8000/v1");
nodeRegistry.cache = registryCache; nodeRegistry.cache = registryCache;
const workerRuntime = new WorkerRuntimeExecutor(); const workerRuntime = new WorkerRuntimeExecutor();
const runtimeCache = new MemoryRuntimeCache(); const runtimeCache = new MemoryRuntimeCache();

View File

@ -1,5 +1,5 @@
import { type NodeRegistry, type NodeDefinition, NodeDefinitionSchema, type AsyncCache } from "@nodes/types"; import { NodeDefinitionSchema, type AsyncCache, type NodeDefinition, type NodeRegistry } from "@nodes/types";
import { createWasmWrapper, createLogger } from "@nodes/utils"; import { createLogger, createWasmWrapper } from "@nodes/utils";
const log = createLogger("node-registry"); const log = createLogger("node-registry");
log.mute(); log.mute();
@ -49,18 +49,21 @@ export class RemoteNodeRegistry implements NodeRegistry {
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) { private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
const fetchNode = async () => {
const response = await this.fetch(`${this.url}/nodes/${nodeId}.wasm`); const response = await this.fetch(`${this.url}/nodes/${nodeId}.wasm`);
if (!response.ok) { return response.arrayBuffer();
if (this.cache) {
let value = await this.cache.get(nodeId);
if (value) {
return value;
}
} }
const res = await Promise.race([
fetchNode(),
this.cache?.get(nodeId)
]);
if (!res) {
throw new Error(`Failed to load node wasm ${nodeId}`); throw new Error(`Failed to load node wasm ${nodeId}`);
} }
return response.arrayBuffer(); return res;
} }
async load(nodeIds: `${string}/${string}/${string}`[]) { async load(nodeIds: `${string}/${string}/${string}`[]) {

View File

@ -52,8 +52,10 @@ async function postNode(node: Node) {
}); });
if (res.ok) { if (res.ok) {
const json = await res.text(); console.log(`Uploaded ${node.id}`);
console.log(json); } else {
const text = await res.text();
console.log(`Failed to upload ${node.id}: ${text}`);
} }
} }

View File

@ -2,7 +2,7 @@
"tasks": { "tasks": {
"dev": "deno run --watch main.ts", "dev": "deno run --watch main.ts",
"test": "deno run vitest", "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" "upload": "deno run --allow-read --allow-net bin/upload.ts"
}, },
"imports": { "imports": {

View File

@ -1,2 +1,2 @@
export * from "../routes/user/user.schema.ts"; export * from "../routes/user/user.schema.ts";
export * from "../routes/node/node.schema.ts"; export * from "../routes/node/schemas/node.schema.ts";

View File

@ -12,14 +12,14 @@ app.use("/v1/*", cors());
app.use(logger()); app.use(logger());
app.route("v1", router); app.route("v1", router);
app.doc("/doc", { app.doc("/openapi.json", {
openapi: "3.0.0", openapi: "3.0.0",
info: { info: {
version: "1.0.0", 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); Deno.serve(app.fetch);

View File

@ -1,5 +1,6 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; 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 * as service from "./node.service.ts";
import { bodyLimit } from "hono/body-limit"; import { bodyLimit } from "hono/body-limit";
@ -11,24 +12,49 @@ const SingleParam = (name: string) =>
.min(3) .min(3)
.max(20) .max(20)
.refine( .refine(
(value) => /^[a-z_-]+$/i.test(value), (value) => idRegex.test(value),
"Name should contain only alphabets", "Name should contain only alphabets",
) )
.openapi({ param: { name, in: "path" } }); .openapi({ param: { name, in: "path" } });
const ParamsSchema = z.object({ const ParamsSchema = z.object({
userId: SingleParam("userId"), user: SingleParam("user"),
nodeSystemId: SingleParam("nodeSystemId"), system: SingleParam("system"),
nodeId: SingleParam("nodeId"), 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({ const getNodeCollectionRoute = createRoute({
method: "get", method: "get",
path: "/{userId}/{nodeSystemId}.json", path: "/{user}/{system}.json",
request: { request: {
params: z.object({ params: z.object({
userId: SingleParam("userId"), user: SingleParam("user"),
nodeSystemId: SingleParam("nodeSystemId").optional(), system: SingleParam("system").optional(),
}), }),
}, },
responses: { responses: {
@ -43,17 +69,16 @@ const getNodeCollectionRoute = createRoute({
}, },
}); });
nodeRouter.openapi(getNodeCollectionRoute, async (c) => { nodeRouter.openapi(getNodeCollectionRoute, async (c) => {
const { userId } = c.req.valid("param"); const { user } = c.req.valid("param");
const nodeSystemId = c.req.param("nodeSystemId.json").replace(/\.json$/, ""); const nodeSystemId = c.req.param("system.json").replace(/\.json$/, "");
const nodes = await service.getNodesBySystem(userId, nodeSystemId);
const nodes = await service.getNodesBySystem(user, nodeSystemId);
return c.json(nodes); return c.json(nodes);
}); });
const getNodeDefinitionRoute = createRoute({ const getNodeDefinitionRoute = createRoute({
method: "get", method: "get",
path: "/{userId}/{nodeSystemId}/{nodeId}.json", path: "/{user}/{system}/{nodeId}{.+\\.json}",
request: { request: {
params: ParamsSchema, params: ParamsSchema,
}, },
@ -68,15 +93,25 @@ const getNodeDefinitionRoute = createRoute({
}, },
}, },
}); });
nodeRouter.openapi(getNodeDefinitionRoute, (c) => { nodeRouter.openapi(getNodeDefinitionRoute, async (c) => {
return c.json({ const { user, system, nodeId } = c.req.valid("param");
id: "",
}); const node = await service.getNodeDefinitionById(
user,
system,
nodeId.replace(/\.json$/, ""),
);
if (!node) {
throw new HTTPException(404);
}
return c.json(node);
}); });
const getNodeWasmRoute = createRoute({ const getNodeWasmRoute = createRoute({
method: "get", method: "get",
path: "/{userId}/{nodeSystemId}/{nodeId}.wasm", path: "/{user}/{system}/{nodeId}{.+\\.wasm}",
request: { request: {
params: ParamsSchema, 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) => { const wasmContent = await service.getNodeWasmById(
return c.json({ user,
id: "", system,
}); nodeId.replace(/\.wasm/, ""),
);
c.header("Content-Type", "application/wasm");
return c.body(wasmContent);
}); });
const createNodeRoute = createRoute({ const createNodeRoute = createRoute({
@ -113,20 +155,17 @@ const createNodeRoute = createRoute({
}, },
middleware: [ middleware: [
bodyLimit({ bodyLimit({
maxSize: 50 * 1024, // 50kb maxSize: 128 * 1024, // 128kb
onError: (c) => { onError: (c) => {
return c.text("overflow :(", 413); return c.text("Node content too large", 413);
}, },
}), }),
], ],
}); });
nodeRouter.openapi(createNodeRoute, async (c) => { nodeRouter.openapi(createNodeRoute, async (c) => {
const buffer = await c.req.arrayBuffer(); const buffer = await c.req.arrayBuffer();
const bytes = await (await c.req.blob()).bytes(); const bytes = await (await c.req.blob()).bytes();
const node = await service.createNode(buffer, bytes); const node = await service.createNode(buffer, bytes);
return c.json(node); return c.json(node);
}); });

View File

@ -1,7 +1,9 @@
import { db } from "../../db/db.ts"; import { db } from "../../db/db.ts";
import { nodeTable } from "./node.schema.ts"; import { nodeTable } from "./schemas/node.schema.ts";
import { NodeDefinition, NodeDefinitionSchema } from "./types.ts"; import { NodeDefinition, NodeDefinitionSchema } from "./schemas/types.ts";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { createHash } from "node:crypto";
import { WorkerMessage } from "./worker/types.ts";
export type CreateNodeDTO = { export type CreateNodeDTO = {
id: string; id: string;
@ -10,10 +12,21 @@ export type CreateNodeDTO = {
content: ArrayBuffer; 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> { function extractDefinition(content: ArrayBuffer): Promise<NodeDefinition> {
const worker = new Worker(new URL("./node.worker.ts", import.meta.url).href, { const worker = new Worker(
new URL("./worker/node.worker.ts", import.meta.url).href,
{
type: "module", type: "module",
}); },
) as Worker & {
postMessage: (message: WorkerMessage) => void;
};
return new Promise((res, rej) => { return new Promise((res, rej) => {
worker.postMessage({ action: "extract-definition", content }); worker.postMessage({ action: "extract-definition", content });
@ -22,12 +35,12 @@ function extractDefinition(content: ArrayBuffer): Promise<NodeDefinition> {
rej(new Error("Worker timeout out")); rej(new Error("Worker timeout out"));
}, 100); }, 100);
worker.onmessage = function (e) { worker.onmessage = function (e) {
console.log(e.data);
switch (e.data.action) { switch (e.data.action) {
case "result": case "result":
res(e.data.result); res(e.data.result);
break; break;
case "error": case "error":
console.log("Worker error", e.data.error);
rej(e.data.result); rej(e.data.result);
break; break;
default: default:
@ -51,20 +64,28 @@ export async function createNode(
systemId, systemId,
nodeId, nodeId,
definition: def, definition: def,
hash: getNodeHash(content),
content: content, content: content,
}; };
await db.insert(nodeTable).values(node); await db.insert(nodeTable).values(node);
console.log("New user created!"); console.log("new node created!");
// await db.insert(users).values({ name: "Andrew" });
return def; return def;
} catch (error) { } catch (error) {
console.log({ error }); console.log("Creation Error", { error });
throw 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( export async function getNodesBySystem(
username: string, username: string,
systemId: string, systemId: string,
@ -84,4 +105,51 @@ export async function getNodesBySystem(
return definitions; 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;
}

View File

@ -4,7 +4,6 @@ import { router } from "../router.ts";
Deno.test("simple test", async () => { Deno.test("simple test", async () => {
const res = await router.request("/max/plants/test.json"); const res = await router.request("/max/plants/test.json");
const json = await res.text(); const json = await res.text();
console.log({ json });
expect(true).toEqual(true); expect(true).toEqual(true);

View File

@ -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);
}
};

View File

@ -10,7 +10,7 @@ const DefaultOptionsSchema = z.object({
hidden: z.boolean().optional(), hidden: z.boolean().optional(),
}); });
export const NodeInputFloatSchema = z.object({ const NodeInputFloatSchema = z.object({
...DefaultOptionsSchema.shape, ...DefaultOptionsSchema.shape,
type: z.literal("float"), type: z.literal("float"),
element: z.literal("slider").optional(), element: z.literal("slider").optional(),
@ -20,7 +20,7 @@ export const NodeInputFloatSchema = z.object({
step: z.number().optional(), step: z.number().optional(),
}); });
export const NodeInputIntegerSchema = z.object({ const NodeInputIntegerSchema = z.object({
...DefaultOptionsSchema.shape, ...DefaultOptionsSchema.shape,
type: z.literal("integer"), type: z.literal("integer"),
element: z.literal("slider").optional(), element: z.literal("slider").optional(),
@ -29,37 +29,37 @@ export const NodeInputIntegerSchema = z.object({
max: z.number().optional(), max: z.number().optional(),
}); });
export const NodeInputBooleanSchema = z.object({ const NodeInputBooleanSchema = z.object({
...DefaultOptionsSchema.shape, ...DefaultOptionsSchema.shape,
type: z.literal("boolean"), type: z.literal("boolean"),
value: z.boolean().optional(), value: z.boolean().optional(),
}); });
export const NodeInputSelectSchema = z.object({ const NodeInputSelectSchema = z.object({
...DefaultOptionsSchema.shape, ...DefaultOptionsSchema.shape,
type: z.literal("select"), type: z.literal("select"),
options: z.array(z.string()).optional(), options: z.array(z.string()).optional(),
value: z.number().optional(), value: z.number().optional(),
}); });
export const NodeInputSeedSchema = z.object({ const NodeInputSeedSchema = z.object({
...DefaultOptionsSchema.shape, ...DefaultOptionsSchema.shape,
type: z.literal("seed"), type: z.literal("seed"),
value: z.number().optional(), value: z.number().optional(),
}); });
export const NodeInputVec3Schema = z.object({ const NodeInputVec3Schema = z.object({
...DefaultOptionsSchema.shape, ...DefaultOptionsSchema.shape,
type: z.literal("vec3"), type: z.literal("vec3"),
value: z.array(z.number()).optional(), value: z.array(z.number()).optional(),
}); });
export const NodeInputGeometrySchema = z.object({ const NodeInputGeometrySchema = z.object({
...DefaultOptionsSchema.shape, ...DefaultOptionsSchema.shape,
type: z.literal("geometry"), type: z.literal("geometry"),
}); });
export const NodeInputPathSchema = z.object({ const NodeInputPathSchema = z.object({
...DefaultOptionsSchema.shape, ...DefaultOptionsSchema.shape,
type: z.literal("path"), type: z.literal("path"),
}); });

View File

@ -7,7 +7,7 @@ import {
varchar, varchar,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm/relations"; import { relations } from "drizzle-orm/relations";
import { usersTable } from "../user/user.schema.ts"; import { usersTable } from "../../user/user.schema.ts";
const bytea = customType<{ const bytea = customType<{
data: ArrayBuffer; data: ArrayBuffer;
@ -25,6 +25,7 @@ export const nodeTable = pgTable("nodes", {
nodeId: varchar().notNull(), nodeId: varchar().notNull(),
content: bytea().notNull(), content: bytea().notNull(),
definition: json().notNull(), definition: json().notNull(),
hash: varchar({ length: 8 }).notNull(),
previous: integer(), previous: integer(),
}); });

View 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>;

View File

@ -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[] };

View 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);
}
};

View 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;

View File

@ -1,5 +1,5 @@
// @ts-nocheck: Nocheck // @ts-nocheck: Nocheck
import { NodeDefinition } from "./types.ts"; import { NodeDefinition } from "../schemas/types.ts";
const cachedTextDecoder = new TextDecoder("utf-8", { const cachedTextDecoder = new TextDecoder("utf-8", {
ignoreBOM: true, ignoreBOM: true,

View File

@ -5,6 +5,6 @@ import { userRouter } from "./user/user.controller.ts";
const router = new OpenAPIHono(); const router = new OpenAPIHono();
router.route("nodes", nodeRouter); router.route("nodes", nodeRouter);
router.route("nodes", userRouter); router.route("users", userRouter);
export { router }; export { router };

View File

@ -22,7 +22,7 @@ export async function findUserByName(userName: string) {
const users = await db const users = await db
.select() .select()
.from(usersTable) .from(usersTable)
.where(eq(usersTable.name, userName)); .where(eq(usersTable.name, userName)).limit(1);
return users[0]; return users[0];
} }