From 9d4d67f0862ebc5615796baf427da9d1e87bb698 Mon Sep 17 00:00:00 2001 From: Max Richter Date: Tue, 17 Dec 2024 18:15:21 +0100 Subject: [PATCH] feat: initial backend store prototype --- app/src/lib/graph-interface/types.ts | 20 + app/src/lib/node-store/DraggableNode.svelte | 1 - app/src/lib/types.ts | 8 + app/src/routes/+page.svelte | 5 +- nodes/max/plantarium/gravity/src/input.json | 4 +- package.json | 7 + pnpm-lock.yaml | 79 +- store/.env | 1 + store/bin/upload.ts | 62 ++ store/compose.yml | 29 + store/deno.json | 20 + store/deno.lock | 881 ++++++++++++++++++++ store/drizzle.config.ts | 11 + store/drizzle/0000_dark_squirrel_girl.sql | 10 + store/drizzle/meta/0000_snapshot.json | 75 ++ store/drizzle/meta/_journal.json | 13 + store/src/db/db.ts | 15 + store/src/db/schema.ts | 2 + store/src/main.ts | 25 + store/src/routes/node/inputs.ts | 81 ++ store/src/routes/node/node.controller.ts | 133 +++ store/src/routes/node/node.schema.ts | 40 + store/src/routes/node/node.service.ts | 87 ++ store/src/routes/node/node.test.ts | 12 + store/src/routes/node/node.worker.ts | 30 + store/src/routes/node/types.ts | 69 ++ store/src/routes/node/utils.ts | 255 ++++++ store/src/routes/router.ts | 10 + store/src/routes/user/user.controller.ts | 54 ++ store/src/routes/user/user.schema.ts | 14 + store/src/routes/user/user.service.ts | 28 + store/vitest.config.ts | 7 + 32 files changed, 2012 insertions(+), 76 deletions(-) create mode 100644 app/src/lib/graph-interface/types.ts create mode 100644 app/src/lib/types.ts create mode 100644 store/.env create mode 100644 store/bin/upload.ts create mode 100644 store/compose.yml create mode 100644 store/deno.json create mode 100644 store/deno.lock create mode 100644 store/drizzle.config.ts create mode 100644 store/drizzle/0000_dark_squirrel_girl.sql create mode 100644 store/drizzle/meta/0000_snapshot.json create mode 100644 store/drizzle/meta/_journal.json create mode 100644 store/src/db/db.ts create mode 100644 store/src/db/schema.ts create mode 100644 store/src/main.ts create mode 100644 store/src/routes/node/inputs.ts create mode 100644 store/src/routes/node/node.controller.ts create mode 100644 store/src/routes/node/node.schema.ts create mode 100644 store/src/routes/node/node.service.ts create mode 100644 store/src/routes/node/node.test.ts create mode 100644 store/src/routes/node/node.worker.ts create mode 100644 store/src/routes/node/types.ts create mode 100644 store/src/routes/node/utils.ts create mode 100644 store/src/routes/router.ts create mode 100644 store/src/routes/user/user.controller.ts create mode 100644 store/src/routes/user/user.schema.ts create mode 100644 store/src/routes/user/user.service.ts create mode 100644 store/vitest.config.ts diff --git a/app/src/lib/graph-interface/types.ts b/app/src/lib/graph-interface/types.ts new file mode 100644 index 0000000..071f7ef --- /dev/null +++ b/app/src/lib/graph-interface/types.ts @@ -0,0 +1,20 @@ +import type { Node, NodeDefinition } from "@nodes/types"; + +export type GraphNode = Node & { + tmp?: { + depth?: number; + mesh?: any; + random?: number; + parents?: Node[]; + children?: Node[]; + inputNodes?: Record; + type?: NodeDefinition; + downX?: number; + downY?: number; + x?: number; + y?: number; + ref?: HTMLElement; + visible?: boolean; + isMoving?: boolean; + }; +}; diff --git a/app/src/lib/node-store/DraggableNode.svelte b/app/src/lib/node-store/DraggableNode.svelte index 4f2dce8..82fa680 100644 --- a/app/src/lib/node-store/DraggableNode.svelte +++ b/app/src/lib/node-store/DraggableNode.svelte @@ -3,7 +3,6 @@ import type { NodeDefinition } from "@nodes/types"; export let node: NodeDefinition; - console.log(node); let dragging = false; diff --git a/app/src/lib/types.ts b/app/src/lib/types.ts new file mode 100644 index 0000000..2f211f8 --- /dev/null +++ b/app/src/lib/types.ts @@ -0,0 +1,8 @@ +import type { + Graph, + Node as NodeType, + NodeDefinition, + NodeInput, + RuntimeExecutor, +} from "@nodes/types"; +export type { Graph, NodeDefinition, NodeInput }; diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index 0fa1b5d..3fbc8fd 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -6,7 +6,10 @@ import Viewer from "$lib/result-viewer/Viewer.svelte"; import Settings from "$lib/settings/Settings.svelte"; import { AppSettingTypes, AppSettings } from "$lib/settings/app-settings"; - import { appSettings as _appSettings, AppSettingTypes as _AppSettingTypes} from "$lib/settings/app-settings.svelte"; + import { + appSettings as _appSettings, + AppSettingTypes as _AppSettingTypes, + } from "$lib/settings/app-settings.svelte"; import { writable } from "svelte/store"; import Keymap from "$lib/settings/panels/Keymap.svelte"; import { createKeyMap } from "$lib/helpers/createKeyMap"; diff --git a/nodes/max/plantarium/gravity/src/input.json b/nodes/max/plantarium/gravity/src/input.json index df6b806..833f810 100644 --- a/nodes/max/plantarium/gravity/src/input.json +++ b/nodes/max/plantarium/gravity/src/input.json @@ -10,8 +10,8 @@ "strength": { "type": "float", "min": 0, - "max": 1 - "value": 1, + "max": 1, + "value": 1 }, "curviness": { "type": "float", diff --git a/package.json b/package.json index 611bd83..1149a27 100644 --- a/package.json +++ b/package.json @@ -6,5 +6,12 @@ "dev:nodes": "pnpm -r --parallel --filter './nodes/**' dev", "build:deploy": "pnpm build && cp -r ./packages/ui/storybook-static ./app/build/ui", "dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev" + }, + "packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b", + "dependencies": { + "@types/pg": "^8.11.10", + "drizzle-kit": "^0.30.1", + "drizzle-orm": "^0.38.2", + "pg": "^8.13.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c02926e..c3b6e8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,9 +13,6 @@ importers: '@nodes/registry': specifier: link:../packages/registry version: link:../packages/registry - '@nodes/runtime': - specifier: link:../packages/runtime - version: link:../packages/runtime '@nodes/ui': specifier: link:../packages/ui version: link:../packages/ui @@ -143,25 +140,6 @@ importers: specifier: ^8.0.0 version: 8.0.0 - packages/runtime: - dependencies: - '@nodes/registry': - specifier: link:../registry - version: link:../registry - '@nodes/types': - specifier: link:../types - version: link:../types - '@nodes/utils': - specifier: link:../utils - version: link:../utils - devDependencies: - comlink: - specifier: ^4.4.1 - version: 4.4.1 - vite-plugin-comlink: - specifier: ^5.1.0 - version: 5.1.0(comlink@4.4.1)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)) - packages/types: dependencies: zod: @@ -194,7 +172,7 @@ importers: version: 8.4.1(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9) '@storybook/sveltekit': specifier: ^8.4.1 - version: 8.4.1(@babel/core@7.26.0)(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)))(less@4.2.0)(postcss-load-config@6.0.1(jiti@2.4.0)(postcss@8.4.47)(tsx@4.19.2)(yaml@2.6.0))(postcss@8.4.47)(sass@1.80.6)(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3) + version: 8.4.1(@babel/core@7.26.0)(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)))(less@4.2.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.8.6)(typescript@5.6.3)))(postcss@8.4.47)(sass@1.80.6)(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3) '@sveltejs/adapter-static': specifier: ^3.0.6 version: 3.0.6(@sveltejs/kit@2.7.4(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)))(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))) @@ -2430,10 +2408,6 @@ packages: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} - engines: {node: '>=14'} - load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2697,24 +2671,6 @@ packages: ts-node: optional: true - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - postcss-safe-parser@6.0.0: resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} engines: {node: '>=12.0'} @@ -3406,11 +3362,6 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.6.0: - resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} - engines: {node: '>= 14'} - hasBin: true - yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -4221,7 +4172,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.4.1(prettier@3.3.3) - '@storybook/svelte-vite@8.4.1(@babel/core@7.26.0)(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)))(less@4.2.0)(postcss-load-config@6.0.1(jiti@2.4.0)(postcss@8.4.47)(tsx@4.19.2)(yaml@2.6.0))(postcss@8.4.47)(sass@1.80.6)(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3)': + '@storybook/svelte-vite@8.4.1(@babel/core@7.26.0)(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)))(less@4.2.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.8.6)(typescript@5.6.3)))(postcss@8.4.47)(sass@1.80.6)(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3)': dependencies: '@storybook/builder-vite': 8.4.1(storybook@8.4.1(prettier@3.3.3))(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3) '@storybook/svelte': 8.4.1(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9) @@ -4229,7 +4180,7 @@ snapshots: magic-string: 0.30.12 storybook: 8.4.1(prettier@3.3.3) svelte: 5.1.9 - svelte-preprocess: 5.1.4(@babel/core@7.26.0)(less@4.2.0)(postcss-load-config@6.0.1(jiti@2.4.0)(postcss@8.4.47)(tsx@4.19.2)(yaml@2.6.0))(postcss@8.4.47)(sass@1.80.6)(svelte@5.1.9)(typescript@5.6.3) + svelte-preprocess: 5.1.4(@babel/core@7.26.0)(less@4.2.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.8.6)(typescript@5.6.3)))(postcss@8.4.47)(sass@1.80.6)(svelte@5.1.9)(typescript@5.6.3) svelte2tsx: 0.7.22(svelte@5.1.9)(typescript@5.6.3) sveltedoc-parser: 4.2.1 ts-dedent: 2.2.0 @@ -4263,12 +4214,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@storybook/sveltekit@8.4.1(@babel/core@7.26.0)(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)))(less@4.2.0)(postcss-load-config@6.0.1(jiti@2.4.0)(postcss@8.4.47)(tsx@4.19.2)(yaml@2.6.0))(postcss@8.4.47)(sass@1.80.6)(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3)': + '@storybook/sveltekit@8.4.1(@babel/core@7.26.0)(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)))(less@4.2.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.8.6)(typescript@5.6.3)))(postcss@8.4.47)(sass@1.80.6)(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3)': dependencies: '@storybook/addon-actions': 8.4.1(storybook@8.4.1(prettier@3.3.3)) '@storybook/builder-vite': 8.4.1(storybook@8.4.1(prettier@3.3.3))(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3) '@storybook/svelte': 8.4.1(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9) - '@storybook/svelte-vite': 8.4.1(@babel/core@7.26.0)(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)))(less@4.2.0)(postcss-load-config@6.0.1(jiti@2.4.0)(postcss@8.4.47)(tsx@4.19.2)(yaml@2.6.0))(postcss@8.4.47)(sass@1.80.6)(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3) + '@storybook/svelte-vite': 8.4.1(@babel/core@7.26.0)(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0)))(less@4.2.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.8.6)(typescript@5.6.3)))(postcss@8.4.47)(sass@1.80.6)(storybook@8.4.1(prettier@3.3.3))(svelte@5.1.9)(vite@5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0))(webpack-sources@3.2.3) storybook: 8.4.1(prettier@3.3.3) svelte: 5.1.9 vite: 5.4.10(@types/node@22.8.6)(less@4.2.0)(sass@1.80.6)(terser@5.36.0) @@ -5774,9 +5725,6 @@ snapshots: lilconfig@2.1.0: {} - lilconfig@3.1.2: - optional: true - load-tsconfig@0.2.5: {} local-pkg@0.5.0: @@ -6022,16 +5970,6 @@ snapshots: postcss: 8.4.47 ts-node: 10.9.2(@types/node@22.8.6)(typescript@5.6.3) - postcss-load-config@6.0.1(jiti@2.4.0)(postcss@8.4.47)(tsx@4.19.2)(yaml@2.6.0): - dependencies: - lilconfig: 3.1.2 - optionalDependencies: - jiti: 2.4.0 - postcss: 8.4.47 - tsx: 4.19.2 - yaml: 2.6.0 - optional: true - postcss-safe-parser@6.0.0(postcss@8.4.47): dependencies: postcss: 8.4.47 @@ -6297,7 +6235,7 @@ snapshots: optionalDependencies: svelte: 5.1.9 - svelte-preprocess@5.1.4(@babel/core@7.26.0)(less@4.2.0)(postcss-load-config@6.0.1(jiti@2.4.0)(postcss@8.4.47)(tsx@4.19.2)(yaml@2.6.0))(postcss@8.4.47)(sass@1.80.6)(svelte@5.1.9)(typescript@5.6.3): + svelte-preprocess@5.1.4(@babel/core@7.26.0)(less@4.2.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.8.6)(typescript@5.6.3)))(postcss@8.4.47)(sass@1.80.6)(svelte@5.1.9)(typescript@5.6.3): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -6309,7 +6247,7 @@ snapshots: '@babel/core': 7.26.0 less: 4.2.0 postcss: 8.4.47 - postcss-load-config: 6.0.1(jiti@2.4.0)(postcss@8.4.47)(tsx@4.19.2)(yaml@2.6.0) + postcss-load-config: 3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.8.6)(typescript@5.6.3)) sass: 1.80.6 typescript: 5.6.3 @@ -6737,9 +6675,6 @@ snapshots: yaml@1.10.2: {} - yaml@2.6.0: - optional: true - yn@3.1.1: optional: true diff --git a/store/.env b/store/.env new file mode 100644 index 0000000..a183732 --- /dev/null +++ b/store/.env @@ -0,0 +1 @@ +DATABASE_URL=postgres://nodarium:nodarium@postgres-db:5432/nodarium diff --git a/store/bin/upload.ts b/store/bin/upload.ts new file mode 100644 index 0000000..45b174c --- /dev/null +++ b/store/bin/upload.ts @@ -0,0 +1,62 @@ +import * as path from "jsr:@std/path"; +const arg = Deno.args[0]; + +const base = arg.startsWith("/") ? arg : path.join(Deno.cwd(), arg); + +const dirs = Deno.readDir(base); + +type Node = { + user: string; + system: string; + id: string; + path: string; +}; + +const nodes: Node[] = []; + +for await (const dir of dirs) { + if (dir.isDirectory) { + const userDir = path.join(base, dir.name); + for await (const userName of Deno.readDir(userDir)) { + if (userName.isDirectory) { + const nodeSystemDir = path.join(userDir, userName.name); + for await (const nodeDir of Deno.readDir(nodeSystemDir)) { + if (nodeDir.isDirectory && !nodeDir.name.startsWith(".")) { + const wasmFilePath = path.join( + nodeSystemDir, + nodeDir.name, + "pkg", + "index_bg.wasm", + ); + nodes.push({ + user: dir.name, + system: userName.name, + id: nodeDir.name, + path: wasmFilePath, + }); + } + } + } + } + } +} + +async function postNode(node: Node) { + const wasmContent = await Deno.readFile(node.path); + + const url = `http://localhost:8000/v1/nodes`; + + const res = await fetch(url, { + method: "POST", + body: wasmContent, + }); + + if (res.ok) { + const json = await res.text(); + console.log(json); + } +} + +for (const node of nodes) { + await postNode(node); +} diff --git a/store/compose.yml b/store/compose.yml new file mode 100644 index 0000000..c9192d5 --- /dev/null +++ b/store/compose.yml @@ -0,0 +1,29 @@ +services: + app: + image: denoland/deno:latest + working_dir: /app + ports: + - 8000:8000 + environment: + DATABASE_URL: postgres://nodarium:nodarium@db:5432/nodarium + volumes: + - .:/app + - deno-cache:/deno-dir/ + command: run --allow-net --allow-env --allow-read --watch src/main.ts + depends_on: + - db + + db: + image: postgres:latest + environment: + POSTGRES_USER: nodarium + POSTGRES_PASSWORD: nodarium + POSTGRES_DB: nodarium + ports: + - "5432:5432" + volumes: + - postgres-data:/var/lib/postgresql/data + +volumes: + postgres-data: + deno-cache: diff --git a/store/deno.json b/store/deno.json new file mode 100644 index 0000000..2de581b --- /dev/null +++ b/store/deno.json @@ -0,0 +1,20 @@ +{ + "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", + "upload": "deno run --allow-read --allow-net bin/upload.ts" + }, + "imports": { + "@asteasolutions/zod-to-openapi": "npm:@asteasolutions/zod-to-openapi@^7.3.0", + "@hono/swagger-ui": "npm:@hono/swagger-ui@^0.5.0", + "@hono/zod-openapi": "npm:@hono/zod-openapi@^0.18.3", + "@std/assert": "jsr:@std/assert@1", + "@types/pg": "npm:@types/pg@^8.11.10", + "drizzle-orm": "npm:drizzle-orm@^0.38.2", + "hono": "npm:hono@^4.6.14", + "pg": "npm:pg@^8.13.1", + "vitest": "npm:vitest@^2.1.8", + "zod": "npm:zod@^3.24.1" + } +} diff --git a/store/deno.lock b/store/deno.lock new file mode 100644 index 0000000..47a0517 --- /dev/null +++ b/store/deno.lock @@ -0,0 +1,881 @@ +{ + "version": "4", + "specifiers": { + "jsr:@std/assert@1": "1.0.9", + "jsr:@std/assert@^1.0.9": "1.0.9", + "jsr:@std/bytes@^1.0.2": "1.0.4", + "jsr:@std/crypto@^1.0.3": "1.0.3", + "jsr:@std/expect@*": "1.0.9", + "jsr:@std/internal@^1.0.5": "1.0.5", + "jsr:@std/path@*": "1.0.8", + "jsr:@std/uuid@*": "1.0.4", + "npm:@asteasolutions/zod-to-openapi@^7.3.0": "7.3.0_zod@3.24.1", + "npm:@hono/swagger-ui@0.5": "0.5.0_hono@4.6.14", + "npm:@hono/zod-openapi@~0.18.3": "0.18.3_hono@4.6.14_zod@3.24.1", + "npm:@types/node@*": "22.5.4", + "npm:@types/pg@^8.11.10": "8.11.10", + "npm:drizzle-kit@*": "0.30.1_esbuild@0.19.12", + "npm:drizzle-orm@~0.38.2": "0.38.2_@types+pg@8.11.10_pg@8.13.1", + "npm:hono@^4.6.14": "4.6.14", + "npm:pg@^8.13.1": "8.13.1", + "npm:vitest@^2.1.8": "2.1.8_vite@5.4.11", + "npm:zod@^3.24.1": "3.24.1" + }, + "jsr": { + "@std/assert@1.0.9": { + "integrity": "a9f0c611a869cc791b26f523eec54c7e187aab7932c2c8e8bea0622d13680dcd", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/bytes@1.0.4": { + "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" + }, + "@std/crypto@1.0.3": { + "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" + }, + "@std/expect@1.0.9": { + "integrity": "108bb428f17492ac40439479e1dc55fbaae581530e905a8603f97305842a5a01", + "dependencies": [ + "jsr:@std/assert@^1.0.9", + "jsr:@std/internal" + ] + }, + "@std/internal@1.0.5": { + "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + }, + "@std/path@1.0.8": { + "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" + }, + "@std/uuid@1.0.4": { + "integrity": "f4233149cc8b4753cc3763fd83a7c4101699491f55c7be78dc7b30281946d7a0", + "dependencies": [ + "jsr:@std/bytes", + "jsr:@std/crypto" + ] + } + }, + "npm": { + "@asteasolutions/zod-to-openapi@7.3.0_zod@3.24.1": { + "integrity": "sha512-7tE/r1gXwMIvGnXVUdIqUhCU1RevEFC4Jk6Bussa0fk1ecbnnINkZzj1EOAJyE/M3AI25DnHT/zKQL1/FPFi8Q==", + "dependencies": [ + "openapi3-ts", + "zod" + ] + }, + "@drizzle-team/brocli@0.10.2": { + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==" + }, + "@esbuild-kit/core-utils@3.3.2": { + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "dependencies": [ + "esbuild@0.18.20", + "source-map-support" + ] + }, + "@esbuild-kit/esm-loader@2.6.5": { + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "dependencies": [ + "@esbuild-kit/core-utils", + "get-tsconfig" + ] + }, + "@esbuild/aix-ppc64@0.19.12": { + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==" + }, + "@esbuild/aix-ppc64@0.21.5": { + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==" + }, + "@esbuild/android-arm64@0.18.20": { + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==" + }, + "@esbuild/android-arm64@0.19.12": { + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==" + }, + "@esbuild/android-arm64@0.21.5": { + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==" + }, + "@esbuild/android-arm@0.18.20": { + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==" + }, + "@esbuild/android-arm@0.19.12": { + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==" + }, + "@esbuild/android-arm@0.21.5": { + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==" + }, + "@esbuild/android-x64@0.18.20": { + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==" + }, + "@esbuild/android-x64@0.19.12": { + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==" + }, + "@esbuild/android-x64@0.21.5": { + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==" + }, + "@esbuild/darwin-arm64@0.18.20": { + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==" + }, + "@esbuild/darwin-arm64@0.19.12": { + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==" + }, + "@esbuild/darwin-arm64@0.21.5": { + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==" + }, + "@esbuild/darwin-x64@0.18.20": { + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==" + }, + "@esbuild/darwin-x64@0.19.12": { + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==" + }, + "@esbuild/darwin-x64@0.21.5": { + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==" + }, + "@esbuild/freebsd-arm64@0.18.20": { + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==" + }, + "@esbuild/freebsd-arm64@0.19.12": { + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==" + }, + "@esbuild/freebsd-arm64@0.21.5": { + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==" + }, + "@esbuild/freebsd-x64@0.18.20": { + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==" + }, + "@esbuild/freebsd-x64@0.19.12": { + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==" + }, + "@esbuild/freebsd-x64@0.21.5": { + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==" + }, + "@esbuild/linux-arm64@0.18.20": { + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==" + }, + "@esbuild/linux-arm64@0.19.12": { + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==" + }, + "@esbuild/linux-arm64@0.21.5": { + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==" + }, + "@esbuild/linux-arm@0.18.20": { + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==" + }, + "@esbuild/linux-arm@0.19.12": { + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==" + }, + "@esbuild/linux-arm@0.21.5": { + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==" + }, + "@esbuild/linux-ia32@0.18.20": { + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==" + }, + "@esbuild/linux-ia32@0.19.12": { + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==" + }, + "@esbuild/linux-ia32@0.21.5": { + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==" + }, + "@esbuild/linux-loong64@0.18.20": { + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==" + }, + "@esbuild/linux-loong64@0.19.12": { + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==" + }, + "@esbuild/linux-loong64@0.21.5": { + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==" + }, + "@esbuild/linux-mips64el@0.18.20": { + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==" + }, + "@esbuild/linux-mips64el@0.19.12": { + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==" + }, + "@esbuild/linux-mips64el@0.21.5": { + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==" + }, + "@esbuild/linux-ppc64@0.18.20": { + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==" + }, + "@esbuild/linux-ppc64@0.19.12": { + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==" + }, + "@esbuild/linux-ppc64@0.21.5": { + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==" + }, + "@esbuild/linux-riscv64@0.18.20": { + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==" + }, + "@esbuild/linux-riscv64@0.19.12": { + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==" + }, + "@esbuild/linux-riscv64@0.21.5": { + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==" + }, + "@esbuild/linux-s390x@0.18.20": { + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==" + }, + "@esbuild/linux-s390x@0.19.12": { + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==" + }, + "@esbuild/linux-s390x@0.21.5": { + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==" + }, + "@esbuild/linux-x64@0.18.20": { + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==" + }, + "@esbuild/linux-x64@0.19.12": { + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==" + }, + "@esbuild/linux-x64@0.21.5": { + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==" + }, + "@esbuild/netbsd-x64@0.18.20": { + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==" + }, + "@esbuild/netbsd-x64@0.19.12": { + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==" + }, + "@esbuild/netbsd-x64@0.21.5": { + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==" + }, + "@esbuild/openbsd-x64@0.18.20": { + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==" + }, + "@esbuild/openbsd-x64@0.19.12": { + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==" + }, + "@esbuild/openbsd-x64@0.21.5": { + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==" + }, + "@esbuild/sunos-x64@0.18.20": { + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==" + }, + "@esbuild/sunos-x64@0.19.12": { + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==" + }, + "@esbuild/sunos-x64@0.21.5": { + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==" + }, + "@esbuild/win32-arm64@0.18.20": { + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==" + }, + "@esbuild/win32-arm64@0.19.12": { + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==" + }, + "@esbuild/win32-arm64@0.21.5": { + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==" + }, + "@esbuild/win32-ia32@0.18.20": { + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==" + }, + "@esbuild/win32-ia32@0.19.12": { + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==" + }, + "@esbuild/win32-ia32@0.21.5": { + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==" + }, + "@esbuild/win32-x64@0.18.20": { + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==" + }, + "@esbuild/win32-x64@0.19.12": { + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==" + }, + "@esbuild/win32-x64@0.21.5": { + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==" + }, + "@hono/swagger-ui@0.5.0_hono@4.6.14": { + "integrity": "sha512-MWYYSv9kC8IwFBLZdwgZZMT9zUq2C/4/ekuyEYOkHEgUMqu+FG3eebtBZ4ofMh60xYRxRR2BgQGoNIILys/PFg==", + "dependencies": [ + "hono" + ] + }, + "@hono/zod-openapi@0.18.3_hono@4.6.14_zod@3.24.1": { + "integrity": "sha512-bNlRDODnp7P9Fs13ZPajEOt13G0XwXKfKRHMEFCphQsFiD1Y+twzHaglpNAhNcflzR1DQwHY92ZS06b4LTPbIQ==", + "dependencies": [ + "@asteasolutions/zod-to-openapi", + "@hono/zod-validator", + "hono", + "zod" + ] + }, + "@hono/zod-validator@0.4.2_hono@4.6.14_zod@3.24.1": { + "integrity": "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g==", + "dependencies": [ + "hono", + "zod" + ] + }, + "@jridgewell/sourcemap-codec@1.5.0": { + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "@rollup/rollup-android-arm-eabi@4.28.1": { + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==" + }, + "@rollup/rollup-android-arm64@4.28.1": { + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==" + }, + "@rollup/rollup-darwin-arm64@4.28.1": { + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==" + }, + "@rollup/rollup-darwin-x64@4.28.1": { + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==" + }, + "@rollup/rollup-freebsd-arm64@4.28.1": { + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==" + }, + "@rollup/rollup-freebsd-x64@4.28.1": { + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==" + }, + "@rollup/rollup-linux-arm-gnueabihf@4.28.1": { + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==" + }, + "@rollup/rollup-linux-arm-musleabihf@4.28.1": { + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==" + }, + "@rollup/rollup-linux-arm64-gnu@4.28.1": { + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==" + }, + "@rollup/rollup-linux-arm64-musl@4.28.1": { + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==" + }, + "@rollup/rollup-linux-loongarch64-gnu@4.28.1": { + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==" + }, + "@rollup/rollup-linux-powerpc64le-gnu@4.28.1": { + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==" + }, + "@rollup/rollup-linux-riscv64-gnu@4.28.1": { + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==" + }, + "@rollup/rollup-linux-s390x-gnu@4.28.1": { + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==" + }, + "@rollup/rollup-linux-x64-gnu@4.28.1": { + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==" + }, + "@rollup/rollup-linux-x64-musl@4.28.1": { + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==" + }, + "@rollup/rollup-win32-arm64-msvc@4.28.1": { + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==" + }, + "@rollup/rollup-win32-ia32-msvc@4.28.1": { + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==" + }, + "@rollup/rollup-win32-x64-msvc@4.28.1": { + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==" + }, + "@types/estree@1.0.6": { + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "@types/node@22.5.4": { + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dependencies": [ + "undici-types" + ] + }, + "@types/pg@8.11.10": { + "integrity": "sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==", + "dependencies": [ + "@types/node", + "pg-protocol", + "pg-types@4.0.2" + ] + }, + "@vitest/expect@2.1.8": { + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", + "dependencies": [ + "@vitest/spy", + "@vitest/utils", + "chai", + "tinyrainbow" + ] + }, + "@vitest/mocker@2.1.8_vite@5.4.11": { + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "dependencies": [ + "@vitest/spy", + "estree-walker", + "magic-string", + "vite" + ] + }, + "@vitest/pretty-format@2.1.8": { + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "dependencies": [ + "tinyrainbow" + ] + }, + "@vitest/runner@2.1.8": { + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", + "dependencies": [ + "@vitest/utils", + "pathe" + ] + }, + "@vitest/snapshot@2.1.8": { + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", + "dependencies": [ + "@vitest/pretty-format", + "magic-string", + "pathe" + ] + }, + "@vitest/spy@2.1.8": { + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", + "dependencies": [ + "tinyspy" + ] + }, + "@vitest/utils@2.1.8": { + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", + "dependencies": [ + "@vitest/pretty-format", + "loupe", + "tinyrainbow" + ] + }, + "assertion-error@2.0.1": { + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==" + }, + "buffer-from@1.1.2": { + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "cac@6.7.14": { + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==" + }, + "chai@5.1.2": { + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dependencies": [ + "assertion-error", + "check-error", + "deep-eql", + "loupe", + "pathval" + ] + }, + "check-error@2.1.1": { + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==" + }, + "debug@4.4.0": { + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": [ + "ms" + ] + }, + "deep-eql@5.0.2": { + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==" + }, + "drizzle-kit@0.30.1_esbuild@0.19.12": { + "integrity": "sha512-HmA/NeewvHywhJ2ENXD3KvOuM/+K2dGLJfxVfIHsGwaqKICJnS+Ke2L6UcSrSrtMJLJaT0Im1Qv4TFXfaZShyw==", + "dependencies": [ + "@drizzle-team/brocli", + "@esbuild-kit/esm-loader", + "esbuild@0.19.12", + "esbuild-register" + ] + }, + "drizzle-orm@0.38.2_@types+pg@8.11.10_pg@8.13.1": { + "integrity": "sha512-eCE3yPRAskLo1WpM9OHpFaM70tBEDsWhwR/0M3CKyztAXKR9Qs3asZlcJOEliIcUSg8GuwrlY0dmYDgmm6y5GQ==", + "dependencies": [ + "@types/pg", + "pg" + ] + }, + "es-module-lexer@1.5.4": { + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==" + }, + "esbuild-register@3.6.0_esbuild@0.19.12": { + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dependencies": [ + "debug", + "esbuild@0.19.12" + ] + }, + "esbuild@0.18.20": { + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dependencies": [ + "@esbuild/android-arm@0.18.20", + "@esbuild/android-arm64@0.18.20", + "@esbuild/android-x64@0.18.20", + "@esbuild/darwin-arm64@0.18.20", + "@esbuild/darwin-x64@0.18.20", + "@esbuild/freebsd-arm64@0.18.20", + "@esbuild/freebsd-x64@0.18.20", + "@esbuild/linux-arm@0.18.20", + "@esbuild/linux-arm64@0.18.20", + "@esbuild/linux-ia32@0.18.20", + "@esbuild/linux-loong64@0.18.20", + "@esbuild/linux-mips64el@0.18.20", + "@esbuild/linux-ppc64@0.18.20", + "@esbuild/linux-riscv64@0.18.20", + "@esbuild/linux-s390x@0.18.20", + "@esbuild/linux-x64@0.18.20", + "@esbuild/netbsd-x64@0.18.20", + "@esbuild/openbsd-x64@0.18.20", + "@esbuild/sunos-x64@0.18.20", + "@esbuild/win32-arm64@0.18.20", + "@esbuild/win32-ia32@0.18.20", + "@esbuild/win32-x64@0.18.20" + ] + }, + "esbuild@0.19.12": { + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dependencies": [ + "@esbuild/aix-ppc64@0.19.12", + "@esbuild/android-arm@0.19.12", + "@esbuild/android-arm64@0.19.12", + "@esbuild/android-x64@0.19.12", + "@esbuild/darwin-arm64@0.19.12", + "@esbuild/darwin-x64@0.19.12", + "@esbuild/freebsd-arm64@0.19.12", + "@esbuild/freebsd-x64@0.19.12", + "@esbuild/linux-arm@0.19.12", + "@esbuild/linux-arm64@0.19.12", + "@esbuild/linux-ia32@0.19.12", + "@esbuild/linux-loong64@0.19.12", + "@esbuild/linux-mips64el@0.19.12", + "@esbuild/linux-ppc64@0.19.12", + "@esbuild/linux-riscv64@0.19.12", + "@esbuild/linux-s390x@0.19.12", + "@esbuild/linux-x64@0.19.12", + "@esbuild/netbsd-x64@0.19.12", + "@esbuild/openbsd-x64@0.19.12", + "@esbuild/sunos-x64@0.19.12", + "@esbuild/win32-arm64@0.19.12", + "@esbuild/win32-ia32@0.19.12", + "@esbuild/win32-x64@0.19.12" + ] + }, + "esbuild@0.21.5": { + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dependencies": [ + "@esbuild/aix-ppc64@0.21.5", + "@esbuild/android-arm@0.21.5", + "@esbuild/android-arm64@0.21.5", + "@esbuild/android-x64@0.21.5", + "@esbuild/darwin-arm64@0.21.5", + "@esbuild/darwin-x64@0.21.5", + "@esbuild/freebsd-arm64@0.21.5", + "@esbuild/freebsd-x64@0.21.5", + "@esbuild/linux-arm@0.21.5", + "@esbuild/linux-arm64@0.21.5", + "@esbuild/linux-ia32@0.21.5", + "@esbuild/linux-loong64@0.21.5", + "@esbuild/linux-mips64el@0.21.5", + "@esbuild/linux-ppc64@0.21.5", + "@esbuild/linux-riscv64@0.21.5", + "@esbuild/linux-s390x@0.21.5", + "@esbuild/linux-x64@0.21.5", + "@esbuild/netbsd-x64@0.21.5", + "@esbuild/openbsd-x64@0.21.5", + "@esbuild/sunos-x64@0.21.5", + "@esbuild/win32-arm64@0.21.5", + "@esbuild/win32-ia32@0.21.5", + "@esbuild/win32-x64@0.21.5" + ] + }, + "estree-walker@3.0.3": { + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": [ + "@types/estree" + ] + }, + "expect-type@1.1.0": { + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==" + }, + "fsevents@2.3.3": { + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==" + }, + "get-tsconfig@4.8.1": { + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dependencies": [ + "resolve-pkg-maps" + ] + }, + "hono@4.6.14": { + "integrity": "sha512-j4VkyUp2xazGJ8eCCLN1Vm/bxdvm/j5ZuU9AIjLu9vapn2M44p9L3Ktr9Vnb2RN2QtcR/wVjZVMlT5k7GJQgPw==" + }, + "loupe@3.1.2": { + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==" + }, + "magic-string@0.30.17": { + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nanoid@3.3.8": { + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==" + }, + "obuf@1.1.2": { + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "openapi3-ts@4.4.0": { + "integrity": "sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==", + "dependencies": [ + "yaml" + ] + }, + "pathe@1.1.2": { + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" + }, + "pathval@2.0.0": { + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==" + }, + "pg-cloudflare@1.1.1": { + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==" + }, + "pg-connection-string@2.7.0": { + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==" + }, + "pg-int8@1.0.1": { + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-numeric@1.0.2": { + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==" + }, + "pg-pool@3.7.0_pg@8.13.1": { + "integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==", + "dependencies": [ + "pg" + ] + }, + "pg-protocol@1.7.0": { + "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==" + }, + "pg-types@2.2.0": { + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": [ + "pg-int8", + "postgres-array@2.0.0", + "postgres-bytea@1.0.0", + "postgres-date@1.0.7", + "postgres-interval@1.2.0" + ] + }, + "pg-types@4.0.2": { + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "dependencies": [ + "pg-int8", + "pg-numeric", + "postgres-array@3.0.2", + "postgres-bytea@3.0.0", + "postgres-date@2.1.0", + "postgres-interval@3.0.0", + "postgres-range" + ] + }, + "pg@8.13.1": { + "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==", + "dependencies": [ + "pg-cloudflare", + "pg-connection-string", + "pg-pool", + "pg-protocol", + "pg-types@2.2.0", + "pgpass" + ] + }, + "pgpass@1.0.5": { + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": [ + "split2" + ] + }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "postcss@8.4.49": { + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dependencies": [ + "nanoid", + "picocolors", + "source-map-js" + ] + }, + "postgres-array@2.0.0": { + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-array@3.0.2": { + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==" + }, + "postgres-bytea@1.0.0": { + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-bytea@3.0.0": { + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dependencies": [ + "obuf" + ] + }, + "postgres-date@1.0.7": { + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-date@2.1.0": { + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==" + }, + "postgres-interval@1.2.0": { + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": [ + "xtend" + ] + }, + "postgres-interval@3.0.0": { + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==" + }, + "postgres-range@1.1.4": { + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==" + }, + "resolve-pkg-maps@1.0.0": { + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" + }, + "rollup@4.28.1": { + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", + "dependencies": [ + "@rollup/rollup-android-arm-eabi", + "@rollup/rollup-android-arm64", + "@rollup/rollup-darwin-arm64", + "@rollup/rollup-darwin-x64", + "@rollup/rollup-freebsd-arm64", + "@rollup/rollup-freebsd-x64", + "@rollup/rollup-linux-arm-gnueabihf", + "@rollup/rollup-linux-arm-musleabihf", + "@rollup/rollup-linux-arm64-gnu", + "@rollup/rollup-linux-arm64-musl", + "@rollup/rollup-linux-loongarch64-gnu", + "@rollup/rollup-linux-powerpc64le-gnu", + "@rollup/rollup-linux-riscv64-gnu", + "@rollup/rollup-linux-s390x-gnu", + "@rollup/rollup-linux-x64-gnu", + "@rollup/rollup-linux-x64-musl", + "@rollup/rollup-win32-arm64-msvc", + "@rollup/rollup-win32-ia32-msvc", + "@rollup/rollup-win32-x64-msvc", + "@types/estree", + "fsevents" + ] + }, + "siginfo@2.0.0": { + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" + }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "source-map-support@0.5.21": { + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": [ + "buffer-from", + "source-map" + ] + }, + "source-map@0.6.1": { + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "split2@4.2.0": { + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, + "stackback@0.0.2": { + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" + }, + "std-env@3.8.0": { + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==" + }, + "tinybench@2.9.0": { + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==" + }, + "tinyexec@0.3.1": { + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==" + }, + "tinypool@1.0.2": { + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==" + }, + "tinyrainbow@1.2.0": { + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==" + }, + "tinyspy@3.0.2": { + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==" + }, + "undici-types@6.19.8": { + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "vite-node@2.1.8": { + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", + "dependencies": [ + "cac", + "debug", + "es-module-lexer", + "pathe", + "vite" + ] + }, + "vite@5.4.11": { + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dependencies": [ + "esbuild@0.21.5", + "fsevents", + "postcss", + "rollup" + ] + }, + "vitest@2.1.8_vite@5.4.11": { + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "dependencies": [ + "@vitest/expect", + "@vitest/mocker", + "@vitest/pretty-format", + "@vitest/runner", + "@vitest/snapshot", + "@vitest/spy", + "@vitest/utils", + "chai", + "debug", + "expect-type", + "magic-string", + "pathe", + "std-env", + "tinybench", + "tinyexec", + "tinypool", + "tinyrainbow", + "vite", + "vite-node", + "why-is-node-running" + ] + }, + "why-is-node-running@2.3.0": { + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dependencies": [ + "siginfo", + "stackback" + ] + }, + "xtend@4.0.2": { + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yaml@2.6.1": { + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==" + }, + "zod@3.24.1": { + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==" + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/assert@1", + "npm:@asteasolutions/zod-to-openapi@^7.3.0", + "npm:@hono/swagger-ui@0.5", + "npm:@hono/zod-openapi@~0.18.3", + "npm:@types/pg@^8.11.10", + "npm:drizzle-orm@~0.38.2", + "npm:hono@^4.6.14", + "npm:pg@^8.13.1", + "npm:vitest@^2.1.8", + "npm:zod@^3.24.1" + ] + } +} diff --git a/store/drizzle.config.ts b/store/drizzle.config.ts new file mode 100644 index 0000000..9281193 --- /dev/null +++ b/store/drizzle.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + out: "./drizzle", + schema: "./src/db/schema.ts", + dialect: "postgresql", + dbCredentials: { + url: Deno.env.get("DATABASE_URL")!, + }, +}); + diff --git a/store/drizzle/0000_dark_squirrel_girl.sql b/store/drizzle/0000_dark_squirrel_girl.sql new file mode 100644 index 0000000..6b3b31b --- /dev/null +++ b/store/drizzle/0000_dark_squirrel_girl.sql @@ -0,0 +1,10 @@ +CREATE TABLE "nodes" ( + "id" serial NOT NULL, + "content" "bytea" NOT NULL, + "definition" json NOT NULL +); +--> statement-breakpoint +CREATE TABLE "users" ( + "id" serial PRIMARY KEY NOT NULL, + "name" text +); diff --git a/store/drizzle/meta/0000_snapshot.json b/store/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..e584fd9 --- /dev/null +++ b/store/drizzle/meta/0000_snapshot.json @@ -0,0 +1,75 @@ +{ + "id": "53dea8d7-01be-4983-ac75-9de9c9a7f592", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.nodes": { + "name": "nodes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "json", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/store/drizzle/meta/_journal.json b/store/drizzle/meta/_journal.json new file mode 100644 index 0000000..cae9842 --- /dev/null +++ b/store/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1734446124519, + "tag": "0000_dark_squirrel_girl", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/store/src/db/db.ts b/store/src/db/db.ts new file mode 100644 index 0000000..cef4ebf --- /dev/null +++ b/store/src/db/db.ts @@ -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, +}); + diff --git a/store/src/db/schema.ts b/store/src/db/schema.ts new file mode 100644 index 0000000..b1953cc --- /dev/null +++ b/store/src/db/schema.ts @@ -0,0 +1,2 @@ +export * from "../routes/user/user.schema.ts"; +export * from "../routes/node/node.schema.ts"; diff --git a/store/src/main.ts b/store/src/main.ts new file mode 100644 index 0000000..acdb02e --- /dev/null +++ b/store/src/main.ts @@ -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); diff --git a/store/src/routes/node/inputs.ts b/store/src/routes/node/inputs.ts new file mode 100644 index 0000000..25601f8 --- /dev/null +++ b/store/src/routes/node/inputs.ts @@ -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; diff --git a/store/src/routes/node/node.controller.ts b/store/src/routes/node/node.controller.ts new file mode 100644 index 0000000..bf8b11f --- /dev/null +++ b/store/src/routes/node/node.controller.ts @@ -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 }; diff --git a/store/src/routes/node/node.schema.ts b/store/src/routes/node/node.schema.ts new file mode 100644 index 0000000..971ef63 --- /dev/null +++ b/store/src/routes/node/node.schema.ts @@ -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], + }), +})); diff --git a/store/src/routes/node/node.service.ts b/store/src/routes/node/node.service.ts new file mode 100644 index 0000000..beaf224 --- /dev/null +++ b/store/src/routes/node/node.service.ts @@ -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 { + 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 { + 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 { + 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) {} diff --git a/store/src/routes/node/node.test.ts b/store/src/routes/node/node.test.ts new file mode 100644 index 0000000..bb83f5e --- /dev/null +++ b/store/src/routes/node/node.test.ts @@ -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" }); +}); diff --git a/store/src/routes/node/node.worker.ts b/store/src/routes/node/node.worker.ts new file mode 100644 index 0000000..d88894b --- /dev/null +++ b/store/src/routes/node/node.worker.ts @@ -0,0 +1,30 @@ +/// + +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); + } +}; diff --git a/store/src/routes/node/types.ts b/store/src/routes/node/types.ts new file mode 100644 index 0000000..3274cfc --- /dev/null +++ b/store/src/routes/node/types.ts @@ -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; + +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; + +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 & { nodes: Node[] }; diff --git a/store/src/routes/node/utils.ts b/store/src/routes/node/utils.ts new file mode 100644 index 0000000..65876dd --- /dev/null +++ b/store/src/routes/node/utils.ts @@ -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 } }; + + 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, + 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; +} diff --git a/store/src/routes/router.ts b/store/src/routes/router.ts new file mode 100644 index 0000000..86f392a --- /dev/null +++ b/store/src/routes/router.ts @@ -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 }; diff --git a/store/src/routes/user/user.controller.ts b/store/src/routes/user/user.controller.ts new file mode 100644 index 0000000..864e603 --- /dev/null +++ b/store/src/routes/user/user.controller.ts @@ -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 }; diff --git a/store/src/routes/user/user.schema.ts b/store/src/routes/user/user.schema.ts new file mode 100644 index 0000000..d6f5b35 --- /dev/null +++ b/store/src/routes/user/user.schema.ts @@ -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"); diff --git a/store/src/routes/user/user.service.ts b/store/src/routes/user/user.service.ts new file mode 100644 index 0000000..094cfee --- /dev/null +++ b/store/src/routes/user/user.service.ts @@ -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]; +} diff --git a/store/vitest.config.ts b/store/vitest.config.ts new file mode 100644 index 0000000..e2ec332 --- /dev/null +++ b/store/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + }, +});