feat: initial backend store prototype
Some checks failed
Deploy to GitHub Pages / build_site (push) Failing after 13s
Some checks failed
Deploy to GitHub Pages / build_site (push) Failing after 13s
This commit is contained in:
15
store/src/db/db.ts
Normal file
15
store/src/db/db.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import pg from "pg";
|
||||
import * as schema from "./schema.ts";
|
||||
|
||||
// Use pg driver.
|
||||
const { Pool } = pg;
|
||||
|
||||
// Instantiate Drizzle client with pg driver and schema.
|
||||
export const db = drizzle({
|
||||
client: new Pool({
|
||||
connectionString: Deno.env.get("DATABASE_URL"),
|
||||
}),
|
||||
schema,
|
||||
});
|
||||
|
2
store/src/db/schema.ts
Normal file
2
store/src/db/schema.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "../routes/user/user.schema.ts";
|
||||
export * from "../routes/node/node.schema.ts";
|
25
store/src/main.ts
Normal file
25
store/src/main.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
import { router } from "./routes/router.ts";
|
||||
import { createUser } from "./routes/user/user.service.ts";
|
||||
import { swaggerUI } from "@hono/swagger-ui";
|
||||
import { logger } from "hono/logger";
|
||||
import { cors } from "hono/cors";
|
||||
|
||||
await createUser("max");
|
||||
|
||||
const app = new OpenAPIHono();
|
||||
app.use("/v1/*", cors());
|
||||
app.use(logger());
|
||||
app.route("v1", router);
|
||||
|
||||
app.doc("/doc", {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
version: "1.0.0",
|
||||
title: "My API",
|
||||
},
|
||||
});
|
||||
|
||||
app.get("/ui", swaggerUI({ url: "/doc" }));
|
||||
|
||||
Deno.serve(app.fetch);
|
81
store/src/routes/node/inputs.ts
Normal file
81
store/src/routes/node/inputs.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { z } from "@hono/zod-openapi";
|
||||
|
||||
const DefaultOptionsSchema = z.object({
|
||||
internal: z.boolean().optional(),
|
||||
external: z.boolean().optional(),
|
||||
setting: z.string().optional(),
|
||||
label: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
accepts: z.array(z.string()).optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputFloatSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("float"),
|
||||
element: z.literal("slider").optional(),
|
||||
value: z.number().optional(),
|
||||
min: z.number().optional(),
|
||||
max: z.number().optional(),
|
||||
step: z.number().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputIntegerSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("integer"),
|
||||
element: z.literal("slider").optional(),
|
||||
value: z.number().optional(),
|
||||
min: z.number().optional(),
|
||||
max: z.number().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputBooleanSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("boolean"),
|
||||
value: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export 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({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("seed"),
|
||||
value: z.number().optional(),
|
||||
});
|
||||
|
||||
export const NodeInputVec3Schema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("vec3"),
|
||||
value: z.array(z.number()).optional(),
|
||||
});
|
||||
|
||||
export const NodeInputGeometrySchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("geometry"),
|
||||
});
|
||||
|
||||
export const NodeInputPathSchema = z.object({
|
||||
...DefaultOptionsSchema.shape,
|
||||
type: z.literal("path"),
|
||||
});
|
||||
|
||||
export const NodeInputSchema = z
|
||||
.union([
|
||||
NodeInputSeedSchema,
|
||||
NodeInputBooleanSchema,
|
||||
NodeInputFloatSchema,
|
||||
NodeInputIntegerSchema,
|
||||
NodeInputSelectSchema,
|
||||
NodeInputSeedSchema,
|
||||
NodeInputVec3Schema,
|
||||
NodeInputGeometrySchema,
|
||||
NodeInputPathSchema,
|
||||
])
|
||||
.openapi("NodeInput");
|
||||
|
||||
export type NodeInput = z.infer<typeof NodeInputSchema>;
|
133
store/src/routes/node/node.controller.ts
Normal file
133
store/src/routes/node/node.controller.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||
import { NodeDefinitionSchema } from "./types.ts";
|
||||
import * as service from "./node.service.ts";
|
||||
import { bodyLimit } from "hono/body-limit";
|
||||
|
||||
const nodeRouter = new OpenAPIHono();
|
||||
|
||||
const SingleParam = (name: string) =>
|
||||
z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(20)
|
||||
.refine(
|
||||
(value) => /^[a-z_-]+$/i.test(value),
|
||||
"Name should contain only alphabets",
|
||||
)
|
||||
.openapi({ param: { name, in: "path" } });
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
userId: SingleParam("userId"),
|
||||
nodeSystemId: SingleParam("nodeSystemId"),
|
||||
nodeId: SingleParam("nodeId"),
|
||||
});
|
||||
|
||||
const getNodeCollectionRoute = createRoute({
|
||||
method: "get",
|
||||
path: "/{userId}/{nodeSystemId}.json",
|
||||
request: {
|
||||
params: z.object({
|
||||
userId: SingleParam("userId"),
|
||||
nodeSystemId: SingleParam("nodeSystemId").optional(),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(NodeDefinitionSchema),
|
||||
},
|
||||
},
|
||||
description: "Retrieve a single node definition",
|
||||
},
|
||||
},
|
||||
});
|
||||
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);
|
||||
|
||||
return c.json(nodes);
|
||||
});
|
||||
|
||||
const getNodeDefinitionRoute = createRoute({
|
||||
method: "get",
|
||||
path: "/{userId}/{nodeSystemId}/{nodeId}.json",
|
||||
request: {
|
||||
params: ParamsSchema,
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: NodeDefinitionSchema,
|
||||
},
|
||||
},
|
||||
description: "Retrieve a single node definition",
|
||||
},
|
||||
},
|
||||
});
|
||||
nodeRouter.openapi(getNodeDefinitionRoute, (c) => {
|
||||
return c.json({
|
||||
id: "",
|
||||
});
|
||||
});
|
||||
|
||||
const getNodeWasmRoute = createRoute({
|
||||
method: "get",
|
||||
path: "/{userId}/{nodeSystemId}/{nodeId}.wasm",
|
||||
request: {
|
||||
params: ParamsSchema,
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/wasm": {
|
||||
schema: z.any(),
|
||||
},
|
||||
},
|
||||
description: "Retrieve a single node",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
nodeRouter.openapi(getNodeWasmRoute, (c) => {
|
||||
return c.json({
|
||||
id: "",
|
||||
});
|
||||
});
|
||||
|
||||
const createNodeRoute = createRoute({
|
||||
method: "post",
|
||||
path: "/",
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: NodeDefinitionSchema,
|
||||
},
|
||||
},
|
||||
description: "Create a single node",
|
||||
},
|
||||
},
|
||||
middleware: [
|
||||
bodyLimit({
|
||||
maxSize: 50 * 1024, // 50kb
|
||||
onError: (c) => {
|
||||
return c.text("overflow :(", 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);
|
||||
});
|
||||
|
||||
export { nodeRouter };
|
40
store/src/routes/node/node.schema.ts
Normal file
40
store/src/routes/node/node.schema.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {
|
||||
customType,
|
||||
integer,
|
||||
json,
|
||||
pgTable,
|
||||
serial,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { relations } from "drizzle-orm/relations";
|
||||
import { usersTable } from "../user/user.schema.ts";
|
||||
|
||||
const bytea = customType<{
|
||||
data: ArrayBuffer;
|
||||
default: false;
|
||||
}>({
|
||||
dataType() {
|
||||
return "bytea";
|
||||
},
|
||||
});
|
||||
|
||||
export const nodeTable = pgTable("nodes", {
|
||||
id: serial().primaryKey(),
|
||||
userId: varchar().notNull(),
|
||||
systemId: varchar().notNull(),
|
||||
nodeId: varchar().notNull(),
|
||||
content: bytea().notNull(),
|
||||
definition: json().notNull(),
|
||||
previous: integer(),
|
||||
});
|
||||
|
||||
export const nodeRelations = relations(nodeTable, ({ one }) => ({
|
||||
userId: one(usersTable, {
|
||||
fields: [nodeTable.userId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
previous: one(nodeTable, {
|
||||
fields: [nodeTable.previous],
|
||||
references: [nodeTable.id],
|
||||
}),
|
||||
}));
|
87
store/src/routes/node/node.service.ts
Normal file
87
store/src/routes/node/node.service.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { db } from "../../db/db.ts";
|
||||
import { nodeTable } from "./node.schema.ts";
|
||||
import { NodeDefinition, NodeDefinitionSchema } from "./types.ts";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
|
||||
export type CreateNodeDTO = {
|
||||
id: string;
|
||||
system: string;
|
||||
user: string;
|
||||
content: ArrayBuffer;
|
||||
};
|
||||
|
||||
function extractDefinition(content: ArrayBuffer): Promise<NodeDefinition> {
|
||||
const worker = new Worker(new URL("./node.worker.ts", import.meta.url).href, {
|
||||
type: "module",
|
||||
});
|
||||
|
||||
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) {
|
||||
console.log(e.data);
|
||||
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(
|
||||
wasmBuffer: ArrayBuffer,
|
||||
content: Uint8Array,
|
||||
): Promise<NodeDefinition> {
|
||||
try {
|
||||
const def = await extractDefinition(wasmBuffer);
|
||||
|
||||
const [userId, systemId, nodeId] = def.id.split("/");
|
||||
|
||||
const node: typeof nodeTable.$inferInsert = {
|
||||
userId,
|
||||
systemId,
|
||||
nodeId,
|
||||
definition: def,
|
||||
content: content,
|
||||
};
|
||||
|
||||
await db.insert(nodeTable).values(node);
|
||||
console.log("New user created!");
|
||||
// await db.insert(users).values({ name: "Andrew" });
|
||||
return def;
|
||||
} catch (error) {
|
||||
console.log({ error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNodesByUser(userName: string) {}
|
||||
export async function getNodesBySystem(
|
||||
username: string,
|
||||
systemId: string,
|
||||
): Promise<NodeDefinition[]> {
|
||||
const nodes = await db
|
||||
.select()
|
||||
.from(nodeTable)
|
||||
.where(
|
||||
and(eq(nodeTable.systemId, systemId), eq(nodeTable.userId, username)),
|
||||
);
|
||||
|
||||
const definitions = nodes
|
||||
.map((node) => NodeDefinitionSchema.safeParse(node.definition))
|
||||
.filter((v) => v.success)
|
||||
.map((v) => v.data);
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
export async function getNodeById(dto: CreateNodeDTO) {}
|
12
store/src/routes/node/node.test.ts
Normal file
12
store/src/routes/node/node.test.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { expect } from "jsr:@std/expect";
|
||||
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);
|
||||
|
||||
expect(json).toEqual({ hello: "world" });
|
||||
});
|
30
store/src/routes/node/node.worker.ts
Normal file
30
store/src/routes/node/node.worker.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/// <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);
|
||||
}
|
||||
};
|
69
store/src/routes/node/types.ts
Normal file
69
store/src/routes/node/types.ts
Normal file
@ -0,0 +1,69 @@
|
||||
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[] };
|
255
store/src/routes/node/utils.ts
Normal file
255
store/src/routes/node/utils.ts
Normal file
@ -0,0 +1,255 @@
|
||||
// @ts-nocheck: Nocheck
|
||||
import { NodeDefinition } from "./types.ts";
|
||||
|
||||
const cachedTextDecoder = new TextDecoder("utf-8", {
|
||||
ignoreBOM: true,
|
||||
fatal: true,
|
||||
});
|
||||
const cachedTextEncoder = new TextEncoder();
|
||||
|
||||
const encodeString = typeof cachedTextEncoder.encodeInto === "function"
|
||||
? function (arg: string, view: Uint8Array) {
|
||||
return cachedTextEncoder.encodeInto(arg, view);
|
||||
}
|
||||
: function (arg: string, view: Uint8Array) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
return {
|
||||
read: arg.length,
|
||||
written: buf.length,
|
||||
};
|
||||
};
|
||||
|
||||
function createWrapper() {
|
||||
let wasm: WebAssembly.Exports & { memory: { buffer: Iterable<number> } };
|
||||
|
||||
let cachedUint8Memory0: Uint8Array | null = null;
|
||||
let cachedInt32Memory0: Int32Array | null = null;
|
||||
let cachedUint32Memory0: Uint32Array | null = null;
|
||||
|
||||
const heap = new Array(128).fill(undefined);
|
||||
heap.push(undefined, null, true, false);
|
||||
let heap_next = heap.length;
|
||||
|
||||
function getUint8Memory0() {
|
||||
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
|
||||
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8Memory0;
|
||||
}
|
||||
|
||||
function getInt32Memory0() {
|
||||
if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
|
||||
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedInt32Memory0;
|
||||
}
|
||||
|
||||
function getUint32Memory0() {
|
||||
if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) {
|
||||
cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint32Memory0;
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr: number, len: number) {
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
function getObject(idx: number) {
|
||||
return heap[idx];
|
||||
}
|
||||
|
||||
function addHeapObject(obj: unknown) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
heap_next = heap[idx];
|
||||
heap[idx] = obj;
|
||||
return idx;
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
function passArray32ToWasm0(
|
||||
arg: ArrayLike<number>,
|
||||
malloc: (arg0: number, arg1: number) => number,
|
||||
) {
|
||||
const ptr = malloc(arg.length * 4, 4) >>> 0;
|
||||
getUint32Memory0().set(arg, ptr / 4);
|
||||
WASM_VECTOR_LEN = arg.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function getArrayI32FromWasm0(ptr: number, len: number) {
|
||||
ptr = ptr >>> 0;
|
||||
return getInt32Memory0().subarray(ptr / 4, ptr / 4 + len);
|
||||
}
|
||||
|
||||
function dropObject(idx: number) {
|
||||
if (idx < 132) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
|
||||
function takeObject(idx: number) {
|
||||
const ret = getObject(idx);
|
||||
dropObject(idx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function __wbindgen_string_new(arg0: number, arg1: number) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return addHeapObject(ret);
|
||||
}
|
||||
|
||||
// Additional methods and their internal helpers can also be refactored in a similar manner.
|
||||
function get_definition() {
|
||||
let deferred1_0: number;
|
||||
let deferred1_1: number;
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.get_definition(retptr);
|
||||
const r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
const r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
deferred1_0 = r0;
|
||||
deferred1_1 = r1;
|
||||
const rawDefinition = getStringFromWasm0(r0, r1);
|
||||
return JSON.parse(rawDefinition) as NodeDefinition;
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function execute(args: Int32Array) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
const ptr0 = passArray32ToWasm0(args, wasm.__wbindgen_malloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
wasm.execute(retptr, ptr0, len0);
|
||||
const r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
const r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
const v2 = getArrayI32FromWasm0(r0, r1).slice();
|
||||
wasm.__wbindgen_free(r0, r1 * 4, 4);
|
||||
return v2;
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
}
|
||||
}
|
||||
|
||||
function passStringToWasm0(
|
||||
arg: string,
|
||||
malloc: (arg0: number, arg1: number) => number,
|
||||
realloc:
|
||||
| ((arg0: number, arg1: number, arg2: number, arg3: number) => number)
|
||||
| undefined,
|
||||
) {
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length, 1) >>> 0;
|
||||
getUint8Memory0()
|
||||
.subarray(ptr, ptr + buf.length)
|
||||
.set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len, 1) >>> 0;
|
||||
|
||||
const mem = getUint8Memory0();
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (; offset < len; offset++) {
|
||||
const code = arg.charCodeAt(offset);
|
||||
if (code > 0x7f) break;
|
||||
mem[ptr + offset] = code;
|
||||
}
|
||||
|
||||
if (offset !== len) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
||||
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = encodeString(arg, view);
|
||||
|
||||
offset += ret.written;
|
||||
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
||||
}
|
||||
|
||||
WASM_VECTOR_LEN = offset;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function __wbg_new_abda76e883ba8a5f() {
|
||||
const ret = new Error();
|
||||
return addHeapObject(ret);
|
||||
}
|
||||
|
||||
function __wbg_stack_658279fe44541cf6(arg0: number, arg1: number) {
|
||||
const ret = getObject(arg1).stack;
|
||||
const ptr1 = passStringToWasm0(
|
||||
ret,
|
||||
wasm.__wbindgen_malloc,
|
||||
wasm.__wbindgen_realloc,
|
||||
);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||
}
|
||||
|
||||
function __wbg_error_f851667af71bcfc6(arg0: number, arg1: number) {
|
||||
let deferred0_0;
|
||||
let deferred0_1;
|
||||
try {
|
||||
deferred0_0 = arg0;
|
||||
deferred0_1 = arg1;
|
||||
console.error(getStringFromWasm0(arg0, arg1));
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function __wbindgen_object_drop_ref(arg0: number) {
|
||||
takeObject(arg0);
|
||||
}
|
||||
|
||||
function __wbg_log_5bb5f88f245d7762(arg0: number) {
|
||||
console.log(getObject(arg0));
|
||||
}
|
||||
|
||||
function __wbindgen_throw(arg0: number, arg1: number) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
}
|
||||
|
||||
return {
|
||||
setInstance(instance: WebAssembly.Instance) {
|
||||
wasm = instance.exports;
|
||||
},
|
||||
|
||||
exports: {
|
||||
// Expose other methods that interact with the wasm instance
|
||||
execute,
|
||||
get_definition,
|
||||
},
|
||||
|
||||
__wbindgen_string_new,
|
||||
__wbindgen_object_drop_ref,
|
||||
__wbg_new_abda76e883ba8a5f,
|
||||
__wbg_error_f851667af71bcfc6,
|
||||
__wbg_stack_658279fe44541cf6,
|
||||
__wbg_log_5bb5f88f245d7762,
|
||||
__wbindgen_throw,
|
||||
};
|
||||
}
|
||||
|
||||
export function createWasmWrapper(wasmBuffer: ArrayBuffer) {
|
||||
const wrapper = createWrapper();
|
||||
const module = new WebAssembly.Module(wasmBuffer);
|
||||
const instance = new WebAssembly.Instance(module, {
|
||||
["./index_bg.js"]: wrapper,
|
||||
});
|
||||
wrapper.setInstance(instance);
|
||||
return wrapper.exports;
|
||||
}
|
10
store/src/routes/router.ts
Normal file
10
store/src/routes/router.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
import { nodeRouter } from "./node/node.controller.ts";
|
||||
import { userRouter } from "./user/user.controller.ts";
|
||||
|
||||
const router = new OpenAPIHono();
|
||||
|
||||
router.route("nodes", nodeRouter);
|
||||
router.route("nodes", userRouter);
|
||||
|
||||
export { router };
|
54
store/src/routes/user/user.controller.ts
Normal file
54
store/src/routes/user/user.controller.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||
import { UserSchema, usersTable } from "./user.schema.ts";
|
||||
import { db } from "../../db/db.ts";
|
||||
import { findUserByName } from "./user.service.ts";
|
||||
|
||||
const userRouter = new OpenAPIHono();
|
||||
|
||||
const getAllUsersRoute = createRoute({
|
||||
method: "get",
|
||||
path: "/users.json",
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(UserSchema),
|
||||
},
|
||||
},
|
||||
description: "Retrieve a single node definition",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
userRouter.openapi(getAllUsersRoute, async (c) => {
|
||||
const users = await db.select().from(usersTable);
|
||||
return c.json(users);
|
||||
});
|
||||
|
||||
const getSingleUserRoute = createRoute({
|
||||
method: "get",
|
||||
path: "/{userId}.json",
|
||||
request: {
|
||||
params: z.object({ userId: z.string().optional() }),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: UserSchema,
|
||||
},
|
||||
},
|
||||
description: "Retrieve a single node definition",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
userRouter.openapi(getSingleUserRoute, async (c) => {
|
||||
const userId = c.req.param("userId.json");
|
||||
|
||||
const user = await findUserByName(userId.replace(/\.json$/, ""));
|
||||
|
||||
return c.json(user);
|
||||
});
|
||||
|
||||
export { userRouter };
|
14
store/src/routes/user/user.schema.ts
Normal file
14
store/src/routes/user/user.schema.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { pgTable, text, uuid } from "drizzle-orm/pg-core";
|
||||
import { z } from "@hono/zod-openapi";
|
||||
|
||||
export const usersTable = pgTable("users", {
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
name: text().unique().notNull(),
|
||||
});
|
||||
|
||||
export const UserSchema = z
|
||||
.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string().min(1), // Non-null text with a unique constraint (enforced at the database level)
|
||||
})
|
||||
.openapi("User");
|
28
store/src/routes/user/user.service.ts
Normal file
28
store/src/routes/user/user.service.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../../db/db.ts";
|
||||
import { usersTable } from "./user.schema.ts";
|
||||
import * as uuid from "jsr:@std/uuid";
|
||||
|
||||
export async function createUser(userName: string) {
|
||||
const user = await db
|
||||
.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.name, userName));
|
||||
|
||||
if (user.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return await db
|
||||
.insert(usersTable)
|
||||
.values({ id: uuid.v1.generate(), name: userName });
|
||||
}
|
||||
|
||||
export async function findUserByName(userName: string) {
|
||||
const users = await db
|
||||
.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.name, userName));
|
||||
|
||||
return users[0];
|
||||
}
|
Reference in New Issue
Block a user