Compare commits

..

No commits in common. "15ff1cc52d1abdc705d69daa3ced540401e38916" and "4ca36b324bc21ebb452a1887917e8cda0df7b2a4" have entirely different histories.

10 changed files with 496 additions and 477 deletions

View File

@ -7,13 +7,12 @@ CREATE TABLE "users" (
CREATE TABLE "nodes" ( CREATE TABLE "nodes" (
"id" serial PRIMARY KEY NOT NULL, "id" serial PRIMARY KEY NOT NULL,
"userId" varchar NOT NULL, "userId" varchar NOT NULL,
"createdAt" timestamp DEFAULT now(),
"systemId" varchar NOT NULL, "systemId" varchar NOT NULL,
"nodeId" varchar NOT NULL, "nodeId" varchar NOT NULL,
"content" "bytea" NOT NULL, "content" "bytea" NOT NULL,
"definition" json NOT NULL, "definition" json NOT NULL,
"hash" varchar(16) NOT NULL, "hash" varchar(8) NOT NULL,
"previous" varchar(16), "previous" varchar(8),
CONSTRAINT "nodes_hash_unique" UNIQUE("hash") CONSTRAINT "nodes_hash_unique" UNIQUE("hash")
); );
--> statement-breakpoint --> statement-breakpoint

View File

@ -0,0 +1 @@
ALTER TABLE "nodes" ADD COLUMN "createdAt" timestamp DEFAULT now();

View File

@ -1,5 +1,5 @@
{ {
"id": "15ad729d-5756-4c06-87ed-cb8b721201f9", "id": "b5fc8bcf-82d4-4d2e-bcd1-89d5a238f5e2",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
@ -54,13 +54,6 @@
"primaryKey": false, "primaryKey": false,
"notNull": true "notNull": true
}, },
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"systemId": { "systemId": {
"name": "systemId", "name": "systemId",
"type": "varchar", "type": "varchar",
@ -87,13 +80,13 @@
}, },
"hash": { "hash": {
"name": "hash", "name": "hash",
"type": "varchar(16)", "type": "varchar(8)",
"primaryKey": false, "primaryKey": false,
"notNull": true "notNull": true
}, },
"previous": { "previous": {
"name": "previous", "name": "previous",
"type": "varchar(16)", "type": "varchar(8)",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
} }

View File

@ -0,0 +1,217 @@
{
"id": "080ee514-5516-4400-9286-295826df6f8a",
"prevId": "b5fc8bcf-82d4-4d2e-bcd1-89d5a238f5e2",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_name_unique": {
"name": "users_name_unique",
"nullsNotDistinct": false,
"columns": [
"name"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.nodes": {
"name": "nodes",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"userId": {
"name": "userId",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"systemId": {
"name": "systemId",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"nodeId": {
"name": "nodeId",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"content": {
"name": "content",
"type": "bytea",
"primaryKey": false,
"notNull": true
},
"definition": {
"name": "definition",
"type": "json",
"primaryKey": false,
"notNull": true
},
"hash": {
"name": "hash",
"type": "varchar(8)",
"primaryKey": false,
"notNull": true
},
"previous": {
"name": "previous",
"type": "varchar(8)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_id_idx": {
"name": "user_id_idx",
"columns": [
{
"expression": "userId",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"system_id_idx": {
"name": "system_id_idx",
"columns": [
{
"expression": "systemId",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"node_id_idx": {
"name": "node_id_idx",
"columns": [
{
"expression": "nodeId",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"hash_idx": {
"name": "hash_idx",
"columns": [
{
"expression": "hash",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"nodes_userId_users_name_fk": {
"name": "nodes_userId_users_name_fk",
"tableFrom": "nodes",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"name"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"node_previous_fk": {
"name": "node_previous_fk",
"tableFrom": "nodes",
"tableTo": "nodes",
"columnsFrom": [
"previous"
],
"columnsTo": [
"hash"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"nodes_hash_unique": {
"name": "nodes_hash_unique",
"nullsNotDistinct": false,
"columns": [
"hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -5,8 +5,15 @@
{ {
"idx": 0, "idx": 0,
"version": "7", "version": "7",
"when": 1734703963242, "when": 1734695353420,
"tag": "0000_known_kid_colt", "tag": "0000_steep_bromley",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1734696211359,
"tag": "0001_amazing_weapon_omega",
"breakpoints": true "breakpoints": true
} }
] ]

File diff suppressed because one or more lines are too long

View File

@ -1,33 +0,0 @@
import { StatusCode } from "hono";
export class CustomError extends Error {
constructor(public status: StatusCode, message: string) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
export class NodeNotFoundError extends CustomError {
constructor() {
super(404, "Node not found");
}
}
export class InvalidNodeDefinitionError extends CustomError {
constructor() {
super(400, "Invalid node definition");
}
}
export class WorkerTimeoutError extends CustomError {
constructor() {
super(500, "Worker timed out");
}
}
export class UnknownWorkerResponseError extends CustomError {
constructor() {
super(500, "Unknown worker response");
}
}

View File

@ -3,360 +3,238 @@ import { HTTPException } from "hono/http-exception";
import { idRegex, NodeDefinitionSchema } from "./validations/types.ts"; import { idRegex, NodeDefinitionSchema } from "./validations/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";
import { ZodSchema } from "zod";
import { CustomError } from "./errors.ts";
const nodeRouter = new OpenAPIHono(); const nodeRouter = new OpenAPIHono();
const createParamSchema = (name: string) => const SingleParam = (name: string) =>
z z
.string() .string()
.min(3) .min(3)
.max(20) .max(20)
.refine( .refine(
(value) => idRegex.test(value), (value) => idRegex.test(value),
`${name} must contain only letters, numbers, "-", or "_"`, `${name} should contain only letters, numbers, "-" or "_"`,
) )
.openapi({ param: { name, in: "path" } }); .openapi({ param: { name, in: "path" } });
const createResponseSchema = <T extends ZodSchema>( const ParamsSchema = z.object({
description: string, user: SingleParam("user"),
schema: T, system: SingleParam("system"),
) => ({ nodeId: SingleParam("nodeId"),
200: {
content: { "application/json": { schema } },
description,
},
}); });
async function getNodeByVersion( const getUserNodesRoute = createRoute({
user: string,
system: string,
nodeId: string,
hash?: string,
) {
console.log("Get Node by Version", { user, system, nodeId, hash });
if (hash) {
if (nodeId.includes("wasm")) {
return await service.getNodeVersionWasm(
user,
system,
nodeId.replace(".wasm", ""),
hash,
);
} else {
const wasmContent = await service.getNodeVersion(
user,
system,
nodeId,
hash,
);
return wasmContent;
}
} else {
if (nodeId.includes(".wasm")) {
const [id, version] = nodeId.replace(/\.wasm$/, "").split("@");
console.log({ user, system, id, version });
if (version) {
return service.getNodeVersionWasm(
user,
system,
id,
version,
);
} else {
return service.getNodeWasmById(
user,
system,
id,
);
}
} else {
const [id, version] = nodeId.replace(/\.json$/, "").split("@");
if (!version) {
return service.getNodeDefinitionById(
user,
system,
id,
);
} else {
return await service.getNodeVersion(
user,
system,
id,
version,
);
}
}
}
}
nodeRouter.openapi(
createRoute({
method: "post",
path: "/",
responses: createResponseSchema(
"Create a single node",
NodeDefinitionSchema,
),
middleware: [
bodyLimit({
maxSize: 128 * 1024, // 128 KB
onError: (c) => c.text("Node content too large", 413),
}),
],
}),
async (c) => {
const buffer = await c.req.arrayBuffer();
const bytes = new Uint8Array(buffer);
try {
const node = await service.createNode(buffer, bytes);
return c.json(node);
} catch (error) {
if (error instanceof CustomError) {
throw new HTTPException(error.status, { message: error.message });
}
throw new HTTPException(500, { message: "Internal server error" });
}
},
);
nodeRouter.openapi(
createRoute({
method: "get", method: "get",
path: "/{user}.json", path: "/{user}.json",
request: { request: {
params: z.object({ params: z.object({
user: createParamSchema("user").optional(), user: SingleParam("user"),
}), }),
}, },
responses: createResponseSchema( responses: {
"Retrieve nodes for a user", 200: {
z.array(NodeDefinitionSchema), content: {
), "application/json": {
}), schema: z.array(NodeDefinitionSchema),
async (c) => { },
const user = c.req.param("user.json").replace(/\.json$/, ""); },
try { description: "Retrieve a single node definition",
const nodes = await service.getNodeDefinitionsByUser(user); },
},
});
nodeRouter.openapi(getUserNodesRoute, async (c) => {
const userId = c.req.param("user.json").replace(/\.json$/, "");
const nodes = await service.getNodeDefinitionsByUser(userId);
return c.json(nodes); return c.json(nodes);
} catch (error) { });
if (error instanceof CustomError) {
throw new HTTPException(error.status, { message: error.message });
}
throw new HTTPException(500, { message: "Internal server error" });
}
},
);
nodeRouter.openapi( const getNodeCollectionRoute = createRoute({
createRoute({
method: "get", method: "get",
path: "/{user}/{system}.json", path: "/{user}/{system}.json",
request: { request: {
params: z.object({ params: z.object({
user: createParamSchema("user"), user: SingleParam("user"),
system: createParamSchema("system").optional(), system: SingleParam("system").optional(),
}), }),
}, },
responses: createResponseSchema( responses: {
"Retrieve nodes for a system", 200: {
z.array(NodeDefinitionSchema), content: {
), "application/json": {
}), schema: z.array(NodeDefinitionSchema),
async (c) => { },
},
description: "Retrieve a single node definition",
},
},
});
nodeRouter.openapi(getNodeCollectionRoute, async (c) => {
const { user } = c.req.valid("param"); const { user } = c.req.valid("param");
const system = c.req.param("system.json").replace(/\.json$/, ""); const nodeSystemId = c.req.param("system.json").replace(/\.json$/, "");
console.log("Get Nodes by System", { user, system });
try {
const nodes = await service.getNodesBySystem(user, system);
return c.json(nodes);
} catch (error) {
if (error instanceof CustomError) {
throw new HTTPException(error.status, { message: error.message });
}
throw new HTTPException(500, { message: "Internal server error" });
}
},
);
nodeRouter.openapi( const nodes = await service.getNodesBySystem(user, nodeSystemId);
createRoute({ return c.json(nodes);
});
const getNodeDefinitionRoute = createRoute({
method: "get", method: "get",
path: "/{user}/{system}/{nodeId}.json", path: "/{user}/{system}/{nodeId}.json",
request: { request: {
params: z.object({ params: ParamsSchema,
user: createParamSchema("user"),
system: createParamSchema("system"),
nodeId: createParamSchema("nodeId").optional(),
}),
}, },
responses: createResponseSchema( responses: {
"Retrieve a single node definition", 200: {
NodeDefinitionSchema, content: {
), "application/json": {
}), schema: NodeDefinitionSchema,
async (c) => {
const { user, system } = c.req.valid("param");
const nodeId = c.req.param("nodeId.json").replace(/\.json$/, "");
console.log("Get Node by Id", { user, system, nodeId });
try {
const node = await service.getNodeDefinitionById(user, system, nodeId);
return c.json(node);
} catch (error) {
if (error instanceof CustomError) {
throw new HTTPException(error.status, { message: error.message });
}
throw new HTTPException(500, { message: "Internal server error" });
}
}, },
);
nodeRouter.openapi(
createRoute({
method: "get",
path: "/{user}/{system}/{nodeId}@{version}.json",
request: {
params: z.object({
user: createParamSchema("user"),
system: createParamSchema("system"),
nodeId: createParamSchema("nodeId"),
version: createParamSchema("version").optional(),
}),
}, },
responses: createResponseSchema( description: "Retrieve a single node definition",
"Retrieve a single node definition", },
NodeDefinitionSchema, },
), });
}), nodeRouter.openapi(getNodeDefinitionRoute, async (c) => {
async (c) => {
const { user, system, nodeId } = c.req.valid("param"); const { user, system, nodeId } = c.req.valid("param");
const hash = c.req.param("version.json");
try { const node = await service.getNodeDefinitionById(
const res = await getNodeByVersion(user, system, nodeId, hash); user,
if (res instanceof ArrayBuffer) { system,
c.header("Content-Type", "application/wasm"); nodeId.replace(/\.json$/, ""),
return c.body(res);
} else {
return c.json(res);
}
} catch (error) {
if (error instanceof CustomError) {
throw new HTTPException(error.status, { message: error.message });
}
throw new HTTPException(500, { message: "Internal server error" });
}
},
); );
nodeRouter.openapi( if (!node) {
createRoute({ throw new HTTPException(404);
}
return c.json(node);
});
const getNodeWasmRoute = createRoute({
method: "get",
path: "/{user}/{system}/{nodeId}.wasm",
request: {
params: ParamsSchema,
},
responses: {
200: {
content: {
"application/wasm": {
schema: z.any(),
},
},
description: "Retrieve a single node",
},
},
});
nodeRouter.openapi(getNodeWasmRoute, async (c) => {
const { user, system, nodeId } = c.req.valid("param");
const wasmContent = await service.getNodeWasmById(
user,
system,
nodeId.replace(/\.wasm/, ""),
);
c.header("Content-Type", "application/wasm");
return c.body(wasmContent);
});
const getNodeVersionRoute = createRoute({
method: "get",
path: "/{user}/{system}/{nodeId}@{hash}.json",
request: {
params: ParamsSchema,
},
responses: {
200: {
content: {
"application/json": {
schema: NodeDefinitionSchema,
},
},
description: "Create a single node",
},
},
});
nodeRouter.openapi(getNodeVersionRoute, async (c) => {
const { user, system, nodeId } = c.req.valid("param");
const nodes = await service.getNodeVersions(user, system, nodeId);
return c.json(nodes);
});
const getNodeVersionsRoute = createRoute({
method: "get", method: "get",
path: "/{user}/{system}/{nodeId}/versions.json", path: "/{user}/{system}/{nodeId}/versions.json",
request: { request: {
params: z.object({ params: z.object({
user: createParamSchema("user"), user: SingleParam("user"),
system: createParamSchema("system"), system: SingleParam("system"),
nodeId: createParamSchema("nodeId"), nodeId: SingleParam("nodeId"),
hash: SingleParam("hash"),
}), }),
}, },
responses: createResponseSchema( responses: {
"Retrieve a single node definition", 200: {
z.array(NodeDefinitionSchema), content: {
), "application/json": {
}), schema: NodeDefinitionSchema,
async (c) => { },
const { user, system, nodeId } = c.req.valid("param"); },
description: "Create a single node",
},
},
});
try { nodeRouter.openapi(getNodeVersionsRoute, async (c) => {
const node = await service.getNodeVersions(user, system, nodeId); const { user, system, nodeId, hash } = c.req.valid("param");
const node = await service.getNodeVersion(user, system, nodeId, hash);
return c.json(node); return c.json(node);
} catch (error) { });
if (error instanceof CustomError) {
throw new HTTPException(error.status, { message: error.message });
}
throw new HTTPException(500, { message: "Internal server error" });
}
},
);
nodeRouter.openapi( const createNodeRoute = createRoute({
createRoute({ method: "post",
method: "get", path: "/",
path: "/{user}/{system}/{nodeId}.wasm",
request: {
params: z.object({
user: createParamSchema("user"),
system: createParamSchema("system"),
nodeId: createParamSchema("nodeId").optional(),
}),
},
responses: { responses: {
200: { 200: {
content: { "application/wasm": { schema: z.any() } }, content: {
description: "Retrieve a node's WASM file", "application/json": {
schema: NodeDefinitionSchema,
}, },
}, },
description: "Create a single node",
},
},
middleware: [
bodyLimit({
maxSize: 128 * 1024, // 128kb
onError: (c) => {
return c.text("Node content too large", 413);
},
}), }),
async (c) => { ],
const { user, system } = c.req.valid("param"); });
const nodeId = c.req.param("nodeId.wasm"); nodeRouter.openapi(createNodeRoute, async (c) => {
console.log("Get NodeWasm by Id", { user, system, nodeId }); const buffer = await c.req.arrayBuffer();
try { const bytes = await (await c.req.blob()).bytes();
const res = await getNodeByVersion(user, system, nodeId);
if (res instanceof ArrayBuffer) {
c.header("Content-Type", "application/wasm");
return c.body(res);
} else {
return c.json(res);
}
} catch (error) {
if (error instanceof CustomError) {
throw new HTTPException(error.status, { message: error.message });
}
throw new HTTPException(500, { message: "Internal server error" });
}
},
);
nodeRouter.openapi(
createRoute({
method: "get",
path: "/{user}/{system}/{nodeId}@{version}.wasm",
request: {
params: z.object({
user: createParamSchema("user"),
system: createParamSchema("system"),
nodeId: createParamSchema("nodeId"),
version: createParamSchema("version").optional(),
}),
},
responses: {
200: {
content: { "application/wasm": { schema: z.any() } },
description: "Retrieve a node's WASM file",
},
},
}),
async (c) => {
const { user, system, nodeId } = c.req.valid("param");
const hash = c.req.param("version.wasm");
try { try {
const res = await getNodeByVersion(user, system, nodeId, hash); const node = await service.createNode(buffer, bytes);
if (res instanceof ArrayBuffer) { return c.json(node);
c.header("Content-Type", "application/wasm");
return c.body(res);
} else {
return c.json(res);
}
} catch (error) { } catch (error) {
if (error instanceof CustomError) { if (error instanceof Error && "code" in error) {
throw new HTTPException(error.status, { message: error.message }); switch (error.code) {
case "23505":
throw new HTTPException(409, { message: "node already exists" });
} }
throw new HTTPException(500, { message: "Internal server error" });
} }
}, }
); throw new HTTPException(500);
});
export { nodeRouter }; export { nodeRouter };

View File

@ -3,8 +3,7 @@ import { nodeTable } from "./node.schema.ts";
import { NodeDefinition, NodeDefinitionSchema } from "./validations/types.ts"; import { NodeDefinition, NodeDefinitionSchema } from "./validations/types.ts";
import { and, asc, eq } from "drizzle-orm"; import { and, asc, eq } from "drizzle-orm";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import { extractDefinition } from "./worker/index.ts"; import { WorkerMessage } from "./worker/messages.ts";
import { InvalidNodeDefinitionError, NodeNotFoundError } from "./errors.ts";
export type CreateNodeDTO = { export type CreateNodeDTO = {
id: string; id: string;
@ -19,6 +18,37 @@ function getNodeHash(content: Uint8Array) {
return hash.digest("hex").slice(0, 16); return hash.digest("hex").slice(0, 16);
} }
function extractDefinition(content: ArrayBuffer): Promise<NodeDefinition> {
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 });
setTimeout(() => {
worker.terminate();
rej(new Error("Worker timeout out"));
}, 100);
worker.onmessage = function (e) {
switch (e.data.action) {
case "result":
res(e.data.result);
break;
case "error":
rej(e.data.result);
break;
default:
rej(new Error("Unknown worker response"));
}
};
});
}
export async function createNode( export async function createNode(
wasmBuffer: ArrayBuffer, wasmBuffer: ArrayBuffer,
content: Uint8Array, content: Uint8Array,
@ -53,10 +83,7 @@ export async function createNode(
} }
export async function getNodeDefinitionsByUser(userName: string) { export async function getNodeDefinitionsByUser(userName: string) {
const nodes = await db.select({ const nodes = await db.select({ definition: nodeTable.definition }).from(
definition: nodeTable.definition,
hash: nodeTable.hash,
}).from(
nodeTable, nodeTable,
) )
.where( .where(
@ -65,10 +92,7 @@ export async function getNodeDefinitionsByUser(userName: string) {
), ),
); );
return nodes.map((n) => ({ return nodes.map((n) => n.definition);
...n.definition,
id: n.definition.id + "@" + n.hash,
}));
} }
export async function getNodesBySystem( export async function getNodesBySystem(
@ -78,7 +102,7 @@ export async function getNodesBySystem(
const nodes = await db const nodes = await db
.selectDistinctOn( .selectDistinctOn(
[nodeTable.userId, nodeTable.systemId, nodeTable.nodeId], [nodeTable.userId, nodeTable.systemId, nodeTable.nodeId],
{ definition: nodeTable.definition, hash: nodeTable.hash }, { definition: nodeTable.definition },
) )
.from(nodeTable) .from(nodeTable)
.where( .where(
@ -86,14 +110,9 @@ export async function getNodesBySystem(
).orderBy(nodeTable.userId, nodeTable.systemId, nodeTable.nodeId); ).orderBy(nodeTable.userId, nodeTable.systemId, nodeTable.nodeId);
const definitions = nodes const definitions = nodes
.map((node) => .map((node) => NodeDefinitionSchema.safeParse(node.definition))
[NodeDefinitionSchema.safeParse(node.definition), node.hash] as const .filter((v) => v.success)
) .map((v) => v.data);
.filter(([v]) => v.success)
.map(([v, hash]) => ({
...v.data,
id: v?.data?.id + "@" + hash,
}));
return definitions; return definitions;
} }
@ -103,6 +122,7 @@ export async function getNodeWasmById(
systemId: string, systemId: string,
nodeId: string, nodeId: string,
) { ) {
const a = performance.now();
const node = await db.select({ content: nodeTable.content }).from(nodeTable) const node = await db.select({ content: nodeTable.content }).from(nodeTable)
.where( .where(
and( and(
@ -113,9 +133,10 @@ export async function getNodeWasmById(
) )
.orderBy(asc(nodeTable.createdAt)) .orderBy(asc(nodeTable.createdAt))
.limit(1); .limit(1);
console.log("Time to load wasm", performance.now() - a);
if (!node[0]) { if (!node[0]) {
throw new NodeNotFoundError(); throw new Error("Node not found");
} }
return node[0].content; return node[0].content;
@ -126,10 +147,7 @@ export async function getNodeDefinitionById(
systemId: string, systemId: string,
nodeId: string, nodeId: string,
) { ) {
const node = await db.select({ const node = await db.select({ definition: nodeTable.definition }).from(
definition: nodeTable.definition,
hash: nodeTable.hash,
}).from(
nodeTable, nodeTable,
).where( ).where(
and( and(
@ -142,16 +160,16 @@ export async function getNodeDefinitionById(
.limit(1); .limit(1);
if (!node[0]) { if (!node[0]) {
throw new NodeNotFoundError(); return;
} }
const definition = NodeDefinitionSchema.safeParse(node[0]?.definition); const definition = NodeDefinitionSchema.safeParse(node[0]?.definition);
if (!definition.success) { if (!definition.data) {
throw new InvalidNodeDefinitionError(); throw new Error("Invalid definition");
} }
return { ...definition.data, id: definition.data.id + "@" + node[0].hash }; return definition.data;
} }
export async function getNodeVersions( export async function getNodeVersions(
@ -187,6 +205,7 @@ export async function getNodeVersion(
) { ) {
const nodes = await db.select({ const nodes = await db.select({
definition: nodeTable.definition, definition: nodeTable.definition,
hash: nodeTable.hash,
}).from( }).from(
nodeTable, nodeTable,
).where( ).where(
@ -199,34 +218,8 @@ export async function getNodeVersion(
).limit(1); ).limit(1);
if (nodes.length === 0) { if (nodes.length === 0) {
throw new NodeNotFoundError(); throw new Error("Node not found");
} }
return nodes[0].definition; return nodes[0].definition;
} }
export async function getNodeVersionWasm(
user: string,
system: string,
nodeId: string,
hash: string,
) {
const node = await db.select({
content: nodeTable.content,
}).from(
nodeTable,
).where(
and(
eq(nodeTable.userId, user),
eq(nodeTable.systemId, system),
eq(nodeTable.nodeId, nodeId),
eq(nodeTable.hash, hash),
),
).limit(1);
if (node.length === 0) {
throw new NodeNotFoundError();
}
return node[0].content;
}

View File

@ -1,36 +0,0 @@
import { UnknownWorkerResponseError, WorkerTimeoutError } from "../errors.ts";
import { NodeDefinition } from "../validations/types.ts";
import { WorkerMessage } from "./messages.ts";
export function extractDefinition(
content: ArrayBuffer,
): Promise<NodeDefinition> {
const worker = new Worker(
new URL("./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 });
setTimeout(() => {
worker.terminate();
rej(new WorkerTimeoutError());
}, 100);
worker.onmessage = function (e) {
switch (e.data.action) {
case "result":
res(e.data.result);
break;
case "error":
rej(e.data.error);
break;
default:
rej(new UnknownWorkerResponseError());
}
};
});
}