Compare commits

..

No commits in common. "main" and "feat/svelte-5" have entirely different histories.

67 changed files with 1554 additions and 4969 deletions

View File

@ -1,4 +1,4 @@
FROM node:22
FROM node:21
# IMAGE CUSTOMISATIONS

View File

@ -13,34 +13,34 @@
"@nodes/registry": "link:../packages/registry",
"@nodes/ui": "link:../packages/ui",
"@nodes/utils": "link:../packages/utils",
"@sveltejs/kit": "^2.12.2",
"@sveltejs/kit": "^2.7.4",
"@threlte/core": "8.0.0-next.23",
"@threlte/extras": "9.0.0-next.33",
"@types/three": "^0.171.0",
"@unocss/reset": "^0.65.2",
"comlink": "^4.4.2",
"@types/three": "^0.169.0",
"@unocss/reset": "^0.63.6",
"comlink": "^4.4.1",
"file-saver": "^2.0.5",
"idb": "^8.0.1",
"idb": "^8.0.0",
"jsondiffpatch": "^0.6.0",
"three": "^0.171.0"
"three": "^0.170.0"
},
"devDependencies": {
"@iconify-json/tabler": "^1.2.13",
"@iconify-json/tabler": "^1.2.7",
"@nodes/types": "link:../packages/types",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tsconfig/svelte": "^5.0.4",
"@types/file-saver": "^2.0.7",
"@unocss/preset-icons": "^0.65.2",
"svelte": "^5.14.4",
"svelte-check": "^4.1.1",
"@unocss/preset-icons": "^0.63.6",
"svelte": "^5.1.9",
"svelte-check": "^4.0.5",
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"unocss": "^0.65.2",
"vite": "^6.0.4",
"typescript": "^5.6.3",
"unocss": "^0.63.6",
"vite": "^5.4.10",
"vite-plugin-comlink": "^5.1.0",
"vite-plugin-glsl": "^1.3.1",
"vite-plugin-glsl": "^1.3.0",
"vite-plugin-wasm": "^3.3.0",
"vitest": "^2.1.8"
"vitest": "^2.1.4"
}
}

View File

@ -5,7 +5,6 @@
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/svelte.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script defer src="https://umami.max-richter.dev/script.js" data-website-id="585c442b-0524-4874-8955-f9853b44b17e"></script>
%sveltekit.head%
<title>Nodes</title>
<script>

View File

@ -9,7 +9,7 @@
$effect(() => {
appSettings.theme;
circleMaterial.color = colors.edge.clone().convertSRGBToLinear();
});
})
});
const lineCache = new Map<number, BufferGeometry>();
@ -25,25 +25,23 @@
<script lang="ts">
import { T } from "@threlte/core";
import { MeshLineMaterial } from "@threlte/extras";
import { BufferGeometry, MeshBasicMaterial, Vector3 } from "three";
import { BufferGeometry, MeshBasicMaterial, Vector3 } from "three";
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
import { Vector2 } from "three/src/math/Vector2.js";
import { createEdgeGeometry } from "./createEdgeGeometry.js";
import { appSettings } from "$lib/settings/app-settings.svelte";
import { appSettings } from "$lib/settings/app-settings.svelte";
type Props = {
from: { x: number; y: number };
to: { x: number; y: number };
z: number;
z:number;
};
const { from, to, z }: Props = $props();
let geometry: BufferGeometry | null = $state(null);
let geometry: BufferGeometry|null = $state(null);
const lineColor = $derived(
appSettings.theme && colors.edge.clone().convertSRGBToLinear(),
);
const lineColor = $derived(appSettings.theme && colors.edge.clone().convertSRGBToLinear());
let lastId: number | null = null;
@ -83,7 +81,8 @@
geometry = createEdgeGeometry(points);
lineCache.set(curveId, geometry);
}
};
$effect(() => {
if (from || to) {
@ -114,6 +113,6 @@
{#if geometry}
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
<MeshLineMaterial width={Math.max(z * 0.0001, 0.00001)} color={lineColor} />
<MeshLineMaterial width={Math.max(z*0.0001,0.00001)} color={lineColor} />
</T.Mesh>
{/if}

View File

@ -6,7 +6,7 @@ export function createEdgeGeometry(points: Vector3[]) {
const length = points[0].distanceTo(points[points.length - 1]);
const startRadius = 8;
const startRadius = 10.5;
const constantWidth = 2;
const taperFraction = 0.8 / length;

View File

@ -1,6 +1,6 @@
import type { Edge, Graph, Node, NodeInput, NodeRegistry, Socket, } from "@nodes/types";
import { fastHashString } from "@nodes/utils";
import { writable, type Writable } from "svelte/store";
import { get, writable, type Writable } from "svelte/store";
import EventEmitter from "./helpers/EventEmitter.js";
import { createLogger } from "./helpers/index.js";
import throttle from "./helpers/throttle.js";
@ -42,6 +42,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
history: HistoryManager = new HistoryManager();
execute = throttle(() => {
console.log("Props", get(this.nodes).values().find(n => n.type === "max/plantarium/gravity")?.props);
if (this.loaded === false) return;
this.emit("result", this.serialize());
}, 10);

View File

@ -48,7 +48,7 @@
$effect(() => {
if (graphState.activeNodeId !== -1) {
activeNode = manager.getNode(graphState.activeNodeId);
} else if (activeNode) {
} else {
activeNode = undefined;
}
});
@ -64,7 +64,7 @@
});
manager.on("settings", (_settings) => {
settingTypes = { ...settingTypes, ..._settings.types };
settingTypes = _settings.types;
settings = _settings.values;
});

View File

@ -1,5 +1,5 @@
import { appSettings } from "$lib/settings/app-settings.svelte";
import { Color, LinearSRGBColorSpace } from "three";
import { Color } from "three";
const variables = [
"layer-0",
@ -15,7 +15,7 @@ const variables = [
function getColor(variable: typeof variables[number]) {
const style = getComputedStyle(document.body.parentElement!);
let color = style.getPropertyValue(`--${variable}`);
return new Color().setStyle(color, LinearSRGBColorSpace);
return new Color().setStyle(color);
}
export const colors = Object.fromEntries(variables.map(v => [v, getColor(v)])) as Record<typeof variables[number], Color>;
@ -25,8 +25,7 @@ $effect.root(() => {
if (!appSettings.theme || !("getComputedStyle" in globalThis)) return;
const style = getComputedStyle(document.body.parentElement!);
for (const v of variables) {
const hex = style.getPropertyValue(`--${v}`);
colors[v].setStyle(hex, LinearSRGBColorSpace);
colors[v].setStyle(style.getPropertyValue(`--${v}`));
}
});
})

View File

@ -25,10 +25,10 @@
$effect(() => {
appSettings.theme;
strokeColor = isSelected
? colors.selected
? colors.selected.clone()
: isActive
? colors.active
: colors.outline;
? colors.active.clone()
: colors.outline.clone();
});
const updateNodePosition =

View File

@ -8,7 +8,8 @@
type Props = {
node: Node;
position?: "absolute" | "fixed" | "relative";
position?: "absolute" | "fixed";
isActive?: boolean;
isSelected?: boolean;
inView?: boolean;
@ -16,7 +17,7 @@
};
let {
node = $bindable(),
node,
position = "absolute",
isActive = false,
isSelected = false,

View File

@ -19,18 +19,7 @@
const graph = getGraphManager();
function getDefaultValue() {
if (node?.props?.[id] !== undefined) return node?.props?.[id] as number;
if ("value" in input && input?.value !== undefined)
return input?.value as number;
if (input.type === "boolean") return 0;
if (input.type === "float") return 0.5;
if (input.type === "integer") return 0;
if (input.type === "select") return 0;
return 0;
}
let value = $state(getDefaultValue());
let value = $state(node?.props?.[id] ?? input.value);
$effect(() => {
if (value !== undefined && node?.props?.[id] !== value) {

View File

@ -1,20 +0,0 @@
import type { Node, NodeDefinition } from "@nodes/types";
export type GraphNode = Node & {
tmp?: {
depth?: number;
mesh?: any;
random?: number;
parents?: Node[];
children?: Node[];
inputNodes?: Record<string, Node>;
type?: NodeDefinition;
downX?: number;
downY?: number;
x?: number;
y?: number;
ref?: HTMLElement;
visible?: boolean;
isMoving?: boolean;
};
};

View File

@ -65,7 +65,7 @@ export function createNodePath({
export const debounce = (fn: Function, ms = 300) => {
let timeoutId: ReturnType<typeof setTimeout>;
return function (this: any, ...args: any[]) {
return function(this: any, ...args: any[]) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
@ -131,99 +131,40 @@ export function humanizeDuration(durationInMilliseconds: number) {
return durationString.trim();
}
// export function debounceAsyncFunction<T extends any[], R>(
// func: (...args: T) => Promise<R>
// ): (...args: T) => Promise<R> {
// let timeoutId: ReturnType<typeof setTimeout> | null = null;
// let lastPromise: Promise<R> | null = null;
// let lastReject: ((reason?: any) => void) | null = null;
//
// return (...args: T): Promise<R> => {
// if (timeoutId) {
// clearTimeout(timeoutId);
// if (lastReject) {
// lastReject(new Error("Debounced: Previous call was canceled."));
// }
// }
//
// return new Promise<R>((resolve, reject) => {
// lastReject = reject;
// timeoutId = setTimeout(() => {
// timeoutId = null;
// lastReject = null;
// lastPromise = func(...args).then(resolve, reject);
// }, 300); // Default debounce time is 300ms; you can make this configurable.
// });
// };
// }
export function debounceAsyncFunction<T extends (...args: any[]) => Promise<any>>(asyncFn: T): T {
let isRunning = false;
let latestArgs: Parameters<T> | null = null;
let resolveNext: (() => void) | null = null;
export function debounceAsyncFunction<T extends any[], R>(func: (...args: T) => Promise<R>): (...args: T) => Promise<R> {
let currentPromise: Promise<R> | null = null;
let nextArgs: T | null = null;
let resolveNext: ((result: R) => void) | null = null;
return (async function serializedFunction(...args: Parameters<T>): Promise<ReturnType<T>> {
latestArgs = args;
if (isRunning) {
// Wait for the current execution to finish
await new Promise<void>((resolve) => {
const debouncedFunction = async (...args: T): Promise<R> => {
if (currentPromise) {
// Store the latest arguments and create a new promise to resolve them later
nextArgs = args;
return new Promise<R>((resolve) => {
resolveNext = resolve;
});
}
// Indicate the function is running
isRunning = true;
try {
// Execute with the latest arguments
const result = await asyncFn(...latestArgs!);
return result;
} finally {
// Allow the next execution
isRunning = false;
if (resolveNext) {
resolveNext();
resolveNext = null;
} else {
// Execute the function immediately
try {
currentPromise = func(...args);
const result = await currentPromise;
return result;
} finally {
currentPromise = null;
// If there are stored arguments, call the function again with the latest arguments
if (nextArgs) {
const argsToUse = nextArgs;
const resolver = resolveNext;
nextArgs = null;
resolveNext = null;
resolver!(await debouncedFunction(...argsToUse));
}
}
}
}) as T;
}
};
// export function debounceAsyncFunction<T extends any[], R>(func: (...args: T) => Promise<R>): (...args: T) => Promise<R> {
// let currentPromise: Promise<R> | null = null;
// let nextArgs: T | null = null;
// let resolveNext: ((result: R) => void) | null = null;
//
// const debouncedFunction = async (...args: T): Promise<R> => {
// if (currentPromise) {
// // Store the latest arguments and create a new promise to resolve them later
// nextArgs = args;
// return new Promise<R>((resolve) => {
// resolveNext = resolve;
// });
// } else {
// // Execute the function immediately
// try {
// currentPromise = func(...args);
// const result = await currentPromise;
// return result;
// } finally {
// currentPromise = null;
// // If there are stored arguments, call the function again with the latest arguments
// if (nextArgs) {
// const argsToUse = nextArgs;
// const resolver = resolveNext;
// nextArgs = null;
// resolveNext = null;
// resolver!(await debouncedFunction(...argsToUse));
// }
// }
// }
// };
//
// return debouncedFunction;
// }
return debouncedFunction;
}
export function withArgsChangeOnly<T extends any[], R>(func: (...args: T) => R): (...args: T) => R {
let lastArgs: T | undefined = undefined;

View File

@ -3,6 +3,7 @@
import type { NodeDefinition } from "@nodes/types";
export let node: NodeDefinition;
console.log(node);
let dragging = false;

View File

@ -23,7 +23,7 @@
<div class="wrapper">
{#if !activeUser}
{#await registry.fetchUsers()}
<div>Loading Users...</div>
<div>Loading...</div>
{:then users}
{#each users as user}
<button
@ -37,7 +37,7 @@
{/await}
{:else if !activeCollection}
{#await registry.fetchUser(activeUser)}
<div>Loading User...</div>
<div>Loading...</div>
{:then user}
{#each user.collections as collection}
<button
@ -53,11 +53,11 @@
{/await}
{:else if !activeNode}
{#await registry.fetchCollection(`${activeUser}/${activeCollection}`)}
<div>Loading Collection...</div>
<div>Loading...</div>
{:then collection}
{#each collection.nodes as node}
{#await registry.fetchNodeDefinition(node.id)}
<div>Loading Node... {node.id}</div>
<div>Loading... {node.id}</div>
{:then node}
{#if node}
<DraggableNode {node} />

View File

@ -1,177 +0,0 @@
<script module lang="ts">
let openSections = localState<Record<string, boolean>>("open-details", {});
</script>
<script lang="ts">
import NestedSettings from "./NestedSettings.svelte";
import { localState } from "$lib/helpers/localState.svelte";
import type { NodeInput } from "@nodes/types";
import Input from "@nodes/ui";
type Button = { type: "button"; label?: string };
type InputType = NodeInput | Button;
interface Nested {
[key: string]: (Nested & { title?: string }) | InputType;
}
type SettingsType = Record<string, Nested>;
type SettingsValue = Record<
string,
Record<string, unknown> | string | number | boolean | number[]
>;
type Props = {
id: string;
key?: string;
value: SettingsValue;
type: SettingsType;
depth?: number;
};
let { id, key = "", value = $bindable(), type, depth = 0 }: Props = $props();
function isNodeInput(v: InputType | Nested): v is InputType {
return v && "type" in v;
}
function getDefaultValue() {
if (key === "") return;
if (key === "title") return;
if (Array.isArray(type[key]?.options)) {
if (value?.[key] !== undefined) {
return type[key]?.options?.indexOf(value?.[key]);
} else {
return 0;
}
}
if (value?.[key] !== undefined) return value?.[key];
if (type[key]?.value !== undefined) return type[key]?.value;
if (isNodeInput(type[key])) {
if (type[key].type === "boolean") return 0;
if (type[key].type === "float") return 0.5;
if (type[key].type === "integer") return 0;
if (type[key].type === "select") return 0;
}
return 0;
}
let internalValue = $state(getDefaultValue());
let open = $state(openSections[id]);
if (depth > 0 && !isNodeInput(type[key])) {
$effect(() => {
if (open !== undefined) {
openSections[id] = open;
}
});
}
$effect(() => {
if (key === "" || internalValue === undefined) return;
if (
isNodeInput(type[key]) &&
Array.isArray(type[key]?.options) &&
typeof internalValue === "number"
) {
value[key] = type[key].options?.[internalValue];
} else {
value[key] = internalValue;
}
});
</script>
{#if key && isNodeInput(type?.[key])}
<div class="input input-{type[key].type}" class:first-level={depth === 1}>
{#if type[key].type === "button"}
<button onclick={() => console.log(type[key])}>
{type[key].label || key}
</button>
{:else}
<label for={id}>{type[key].label || key}</label>
<Input {id} input={type[key]} bind:value={internalValue} />
{/if}
</div>
{:else if depth === 0}
{#each Object.keys(type ?? {}).filter((key) => key !== "title") as childKey}
<NestedSettings
id={`${id}.${childKey}`}
key={childKey}
{value}
{type}
depth={depth + 1}
/>
{/each}
<hr />
{:else if key && type?.[key]}
{#if depth > 0}
<hr />
{/if}
<details bind:open>
<summary><p>{type[key]?.title || key}</p></summary>
<div class="content">
{#each Object.keys(type[key]).filter((key) => key !== "title") as childKey}
<NestedSettings
id={`${id}.${childKey}`}
key={childKey}
value={value[key] as SettingsValue}
type={type[key] as SettingsType}
depth={depth + 1}
/>
{/each}
</div>
</details>
{/if}
<style>
summary {
cursor: pointer;
user-select: none;
margin-bottom: 1em;
}
summary > p {
display: inline;
padding-left: 6px;
}
details {
padding: 1em;
padding-bottom: 0;
padding-left: 21px;
}
.input {
margin-top: 15px;
margin-bottom: 15px;
display: flex;
flex-direction: column;
gap: 10px;
padding-left: 20px;
}
.input-boolean {
display: flex;
flex-direction: row;
align-items: center;
}
.input-boolean > label {
order: 2;
}
.first-level.input {
padding-left: 1em;
padding-right: 1em;
padding-bottom: 1px;
}
hr {
position: absolute;
margin: 0;
left: 0;
right: 0;
border: none;
border-bottom: solid thin var(--outline);
}
</style>

View File

@ -105,26 +105,16 @@ export const AppSettingTypes = {
}
},
}
} as const;
} as const
type IsInputDefinition<T> = T extends NodeInput ? T : never;
type HasTitle = { title: string };
type Widen<T> = T extends boolean
? boolean
: T extends number
? number
: T extends string
? string
: T;
type ExtractSettingsValues<T> = {
-readonly [K in keyof T]: T[K] extends HasTitle
[K in keyof T]: T[K] extends HasTitle
? ExtractSettingsValues<Omit<T[K], 'title'>>
: T[K] extends IsInputDefinition<T[K]>
? T[K] extends { value: infer V }
? Widen<V>
? T[K] extends { value: any }
? T[K]['value']
: never
: T[K] extends Record<string, any>
? ExtractSettingsValues<T[K]>

View File

@ -1,13 +0,0 @@
import type { NodeInput } from "@nodes/types";
type Button = { type: "button"; label?: string };
type InputType = NodeInput | Button;
export interface SettingsType {
[key: string]: (SettingsType & { title?: string }) | InputType;
}
export type SettingsStore = {
[key: string]: SettingsStore | string | number | boolean
};

View File

@ -1,6 +1,6 @@
<script lang="ts">
import type { Node, NodeInput } from "@nodes/types";
import NestedSettings from "$lib/settings/NestedSettings.svelte";
import NestedSettings from "./NestedSettings.svelte";
import type { GraphManager } from "$lib/graph-interface/graph-manager";
type Props = {
@ -69,14 +69,24 @@
}
$effect(() => {
if (store) {
if (store && store) {
updateNode();
}
});
</script>
<NestedSettings
id="activeNodeSettings"
bind:value={store}
type={nodeDefinition}
/>
{#if node}
{#key node.id}
{#if nodeDefinition && store && Object.keys(nodeDefinition).length > 0}
<NestedSettings
id="activeNodeSettings"
bind:value={store}
type={nodeDefinition}
/>
{:else}
<p class="mx-4">Active Node has no Settings</p>
{/if}
{/key}
{:else}
<p class="mx-4">No active node</p>
{/if}

View File

@ -3,6 +3,7 @@
import type { OBJExporter } from "three/addons/exporters/OBJExporter.js";
import type { GLTFExporter } from "three/addons/exporters/GLTFExporter.js";
import FileSaver from "file-saver";
import { appSettings } from "../app-settings.svelte";
// Download
const download = (
@ -51,6 +52,8 @@
// download .obj file
download(result, "plant", "text/plain", "obj");
}
</script>
<div class="p-2">

View File

@ -0,0 +1,40 @@
<script lang="ts">
import type { NodeInput } from "@nodes/types";
import NestedSettings from "./NestedSettings.svelte";
import type { Writable } from "svelte/store";
interface Nested {
[key: string]: NodeInput | Nested;
}
export let type: Record<string, NodeInput>;
export let store: Writable<Record<string, any>>;
function constructNested(type: Record<string, NodeInput>) {
const nested: Nested = {};
for (const key in type) {
const parts = key.split(".");
let current = nested;
for (let i = 0; i < parts.length; i++) {
if (i === parts.length - 1) {
current[parts[i]] = type[key];
} else {
current[parts[i]] = current[parts[i]] || {};
current = current[parts[i]] as Nested;
}
}
}
return nested;
}
$: settings = constructNested({
randomSeed: { type: "boolean", value: false },
...type,
});
</script>
{#key settings}
<NestedSettings id="graph-settings" {settings} {store} />
{/key}

View File

@ -0,0 +1,157 @@
<script module lang="ts">
let openSections = localState<Record<string,boolean>>("open-details", {});
</script>
<script lang="ts">
import NestedSettings from "./NestedSettings.svelte";
import {localState} from "$lib/helpers/localState.svelte";
import type { NodeInput } from "@nodes/types";
import Input from "@nodes/ui";
type Button = { type: "button"; label?: string };
type InputType = NodeInput | Button;
interface Nested {
[key: string]: (Nested & { title?: string }) | InputType;
}
type SettingsType = Record<string, Nested>;
type SettingsValue = Record<string, Record<string, unknown> | string | number | boolean>;
type Props = {
id: string;
key?: string;
value: SettingsValue;
type: SettingsType;
depth?: number;
};
let { id, key = "", value = $bindable(), type, depth = 0 }: Props = $props();
function isNodeInput(v: InputType | Nested): v is InputType {
return v && "type" in v;
}
let internalValue = $state(Array.isArray(type?.[key]?.options) ? type[key]?.options?.indexOf(value?.[key]) : value?.[key]);
let open = $state(openSections[id]);
if(depth > 0 && !isNodeInput(type[key])){
$effect(() => {
if(open !== undefined){
openSections[id] = open;
};
});
}
$effect(() => {
if(key === "" || internalValue === undefined) return;
if(isNodeInput(type[key]) && Array.isArray(type[key]?.options) && typeof internalValue === "number"){
value[key] = type[key].options?.[internalValue];
}else{
value[key] = internalValue;
}
})
</script>
{#if key && isNodeInput(type?.[key]) }
<div class="input input-{type[key].type}" class:first-level={depth === 1}>
{#if type[key].type === "button"}
<button onclick={() => console.log(type[key])}>
{type[key].label || key}
</button>
{:else}
<label for={id}>{type[key].label || key}</label>
<Input id={id} input={type[key]} bind:value={internalValue} />
{/if}
</div>
{:else}
{#if depth === 0}
{#each Object.keys(type).filter((key) => key !== "title") as childKey}
<NestedSettings
id={`${id}.${childKey}`}
key={childKey}
value={value}
type={type}
depth={depth + 1}
/>
{/each}
<hr />
{:else if key && type?.[key]}
{#if depth > 0}
<hr />
{/if}
<details bind:open>
<summary><p>{type[key]?.title||key}</p></summary>
<div class="content">
{#each Object.keys(type[key]).filter((key) => key !== "title") as childKey}
<NestedSettings
id={`${id}.${childKey}`}
key={childKey}
value={value[key] as SettingsValue}
type={type[key] as SettingsType}
depth={depth + 1}
/>
{/each}
</div>
</details>
{/if}
{/if}
<style>
summary {
cursor: pointer;
user-select: none;
margin-bottom: 1em;
}
summary::marker { }
summary > p {
display: inline;
padding-left: 6px;
}
details {
padding: 1em;
padding-bottom: 0;
padding-left: 21px;
}
.input {
margin-top: 15px;
margin-bottom: 15px;
display: flex;
flex-direction: column;
gap: 10px;
padding-left: 20px;
}
.input-boolean {
display: flex;
flex-direction: row;
align-items: center;
}
.input-boolean > label {
order: 2;
}
.first-level.input {
padding-left: 1em;
padding-right: 1em;
padding-bottom: 1px;
}
hr {
position: absolute;
margin: 0;
left: 0;
right: 0;
border: none;
border-bottom: solid thin var(--outline);
}
</style>

View File

@ -1,8 +0,0 @@
import type {
Graph,
Node as NodeType,
NodeDefinition,
NodeInput,
RuntimeExecutor,
} from "@nodes/types";
export type { Graph, NodeDefinition, NodeInput };

View File

@ -4,20 +4,21 @@
import * as templates from "$lib/graph-templates";
import type { Graph, Node } from "@nodes/types";
import Viewer from "$lib/result-viewer/Viewer.svelte";
import Settings from "$lib/settings/Settings.svelte";
import {
appSettings,
AppSettingTypes,
} from "$lib/settings/app-settings.svelte";
import Keymap from "$lib/sidebar/panels/Keymap.svelte";
import Sidebar from "$lib/sidebar/Sidebar.svelte";
import Keymap from "$lib/settings/panels/Keymap.svelte";
import { createKeyMap } from "$lib/helpers/createKeyMap";
import NodeStore from "$lib/node-store/NodeStore.svelte";
import ActiveNodeSettings from "$lib/sidebar/panels/ActiveNodeSettings.svelte";
import ActiveNodeSettings from "$lib/settings/panels/ActiveNodeSettings.svelte";
import PerformanceViewer from "$lib/performance/PerformanceViewer.svelte";
import Panel from "$lib/sidebar/Panel.svelte";
import NestedSettings from "$lib/settings/NestedSettings.svelte";
import Panel from "$lib/settings/Panel.svelte";
import GraphSettings from "$lib/settings/panels/GraphSettings.svelte";
import NestedSettings from "$lib/settings/panels/NestedSettings.svelte";
import type { Group } from "three";
import ExportSettings from "$lib/sidebar/panels/ExportSettings.svelte";
import ExportSettings from "$lib/settings/panels/ExportSettings.svelte";
import {
MemoryRuntimeCache,
WorkerRuntimeExecutor,
@ -25,17 +26,15 @@
} from "$lib/runtime";
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry";
import { createPerformanceStore } from "@nodes/utils";
import BenchmarkPanel from "$lib/sidebar/panels/BenchmarkPanel.svelte";
import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte";
import { debounceAsyncFunction } from "$lib/helpers";
import { onMount } from "svelte";
let performanceStore = createPerformanceStore();
const registryCache = new IndexDBCache("node-registry");
const nodeRegistry = new RemoteNodeRegistry(
"https://node-store.app.max-richter.dev",
registryCache,
);
const nodeRegistry = new RemoteNodeRegistry("");
nodeRegistry.cache = registryCache;
const workerRuntime = new WorkerRuntimeExecutor();
const runtimeCache = new MemoryRuntimeCache();
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
@ -72,9 +71,7 @@
},
]);
let graphSettings = $state<Record<string, any>>({});
let graphSettingTypes = $state({
randomSeed: { type: "boolean", value: false },
});
let graphSettingTypes = $state({});
const handleUpdate = debounceAsyncFunction(
async (g: Graph, s: Record<string, any> = graphSettings) => {
@ -165,7 +162,7 @@
onresult={(result) => handleUpdate(result)}
onsave={(graph) => handleSave(graph)}
/>
<Sidebar>
<Settings>
<Panel id="general" title="General" icon="i-tabler-settings">
<NestedSettings
id="general"
@ -222,11 +219,9 @@
classes="text-blue-400"
icon="i-custom-graph"
>
<NestedSettings
id="graph-settings"
type={graphSettingTypes}
bind:value={graphSettings}
/>
{#if Object.keys(graphSettingTypes).length > 0}
<GraphSettings type={graphSettingTypes} store={graphSettings} />
{/if}
</Panel>
<Panel
id="active-node"
@ -236,7 +231,7 @@
>
<ActiveNodeSettings {manager} node={activeNode} />
</Panel>
</Sidebar>
</Settings>
{/key}
</Grid.Cell>
</Grid.Row>

View File

@ -10,22 +10,22 @@
"strength": {
"type": "float",
"min": 0,
"max": 1,
"value": 1
"max": 1
"value": 1,
},
"curviness": {
"type": "float",
"hidden": true,
"min": 0,
"max": 1,
"value": 0.5
"value": 0.5,
},
"depth": {
"type": "integer",
"min": 1,
"max": 10,
"hidden": true,
"value": 1
"value": 1,
}
}
}

View File

@ -1,12 +1,10 @@
{
"scripts": {
"build": "pnpm build:nodes && pnpm build:app",
"build:story": "pnpm -r --filter 'ui' story:build",
"build:app": "BASE_PATH=/ui pnpm -r --filter 'ui' build && pnpm -r --filter 'app' build",
"build:nodes": "pnpm -r --filter './nodes/**' build",
"dev:nodes": "pnpm -r --parallel --filter './nodes/**' dev",
"build:deploy": "pnpm build",
"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.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c"
}
}

View File

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

View File

@ -1,19 +0,0 @@
{
"name": "store-client",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"generate": "openapi-ts -i ../../store/openapi.json -o src/client -c @hey-api/client-fetch"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@hey-api/client-fetch": "^0.5.6"
},
"devDependencies": {
"@hey-api/openapi-ts": "^0.60.0"
}
}

View File

@ -1,3 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
export * from './sdk.gen';
export * from './types.gen';

View File

@ -1,55 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import { createClient, createConfig, type OptionsLegacyParser } from '@hey-api/client-fetch';
import type { GetV1NodesByUserJsonData, GetV1NodesByUserJsonError, GetV1NodesByUserJsonResponse, GetV1NodesByUserBySystemJsonData, GetV1NodesByUserBySystemJsonError, GetV1NodesByUserBySystemJsonResponse, GetV1NodesByUserBySystemByNodeIdby-.+jsonData, GetV1NodesByUserBySystemByNodeIdby-.+jsonError, GetV1NodesByUserBySystemByNodeIdby-.+jsonResponse, GetV1NodesByUserBySystemByNodeIdby-.+WasmData, GetV1NodesByUserBySystemByNodeIdby-.+WasmError, GetV1NodesByUserBySystemByNodeIdby-.+WasmResponse, PostV1NodesError, PostV1NodesResponse, GetV1UsersUsersJsonError, GetV1UsersUsersJsonResponse, GetV1UsersByUserIdJsonData, GetV1UsersByUserIdJsonError, GetV1UsersByUserIdJsonResponse } from './types.gen';
export const client = createClient(createConfig());
export const getV1NodesByUserJson = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<GetV1NodesByUserJsonData, ThrowOnError>) => {
return (options?.client ?? client).get<GetV1NodesByUserJsonResponse, GetV1NodesByUserJsonError, ThrowOnError>({
...options,
url: '/v1/nodes/{user}.json'
});
};
export const getV1NodesByUserBySystemJson = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<GetV1NodesByUserBySystemJsonData, ThrowOnError>) => {
return (options?.client ?? client).get<GetV1NodesByUserBySystemJsonResponse, GetV1NodesByUserBySystemJsonError, ThrowOnError>({
...options,
url: '/v1/nodes/{user}/{system}.json'
});
};
export const getV1NodesByUserBySystemByNodeIdby-.+Json = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<GetV1NodesByUserBySystemByNodeIdby-.+jsonData, ThrowOnError>) => {
return (options?.client ?? client).get<GetV1NodesByUserBySystemByNodeIdby-.+jsonResponse, GetV1NodesByUserBySystemByNodeIdby-.+jsonError, ThrowOnError>({
...options,
url: '/v1/nodes/{user}/{system}/{nodeId}{.+\\.json}'
});
};
export const getV1NodesByUserBySystemByNodeIdby-.+Wasm = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<GetV1NodesByUserBySystemByNodeIdby-.+WasmData, ThrowOnError>) => {
return (options?.client ?? client).get<GetV1NodesByUserBySystemByNodeIdby-.+WasmResponse, GetV1NodesByUserBySystemByNodeIdby-.+WasmError, ThrowOnError>({
...options,
url: '/v1/nodes/{user}/{system}/{nodeId}{.+\\.wasm}'
});
};
export const postV1Nodes = <ThrowOnError extends boolean = false>(options?: OptionsLegacyParser<unknown, ThrowOnError>) => {
return (options?.client ?? client).post<PostV1NodesResponse, PostV1NodesError, ThrowOnError>({
...options,
url: '/v1/nodes'
});
};
export const getV1UsersUsersJson = <ThrowOnError extends boolean = false>(options?: OptionsLegacyParser<unknown, ThrowOnError>) => {
return (options?.client ?? client).get<GetV1UsersUsersJsonResponse, GetV1UsersUsersJsonError, ThrowOnError>({
...options,
url: '/v1/users/users.json'
});
};
export const getV1UsersByUserIdJson = <ThrowOnError extends boolean = false>(options?: OptionsLegacyParser<GetV1UsersByUserIdJsonData, ThrowOnError>) => {
return (options?.client ?? client).get<GetV1UsersByUserIdJsonResponse, GetV1UsersByUserIdJsonError, ThrowOnError>({
...options,
url: '/v1/users/{userId}.json'
});
};

View File

@ -1,173 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
export type NodeDefinition = {
id: string;
inputs?: {
[key: string]: NodeInput;
};
outputs?: Array<(string)>;
meta?: {
description?: string;
title?: string;
};
};
export type NodeInput = {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<(string)>;
hidden?: boolean;
type: 'seed';
value?: number;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<(string)>;
hidden?: boolean;
type: 'boolean';
value?: boolean;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<(string)>;
hidden?: boolean;
type: 'float';
element?: 'slider';
value?: number;
min?: number;
max?: number;
step?: number;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<(string)>;
hidden?: boolean;
type: 'integer';
element?: 'slider';
value?: number;
min?: number;
max?: number;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<(string)>;
hidden?: boolean;
type: 'select';
options?: Array<(string)>;
value?: number;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<(string)>;
hidden?: boolean;
type: 'vec3';
value?: Array<(number)>;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<(string)>;
hidden?: boolean;
type: 'geometry';
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<(string)>;
hidden?: boolean;
type: 'path';
};
export type type = 'seed';
export type element = 'slider';
export type User = {
id: string;
name: string;
};
export type GetV1NodesByUserJsonData = {
path: {
user: string;
};
};
export type GetV1NodesByUserJsonResponse = (Array<NodeDefinition>);
export type GetV1NodesByUserJsonError = unknown;
export type GetV1NodesByUserBySystemJsonData = {
path: {
system?: string;
user: string;
};
};
export type GetV1NodesByUserBySystemJsonResponse = (Array<NodeDefinition>);
export type GetV1NodesByUserBySystemJsonError = unknown;
export type GetV1NodesByUserBySystemByNodeIdby-.+jsonData = {
path: {
nodeId: string;
system: string;
user: string;
};
};
export type GetV1NodesByUserBySystemByNodeIdby-.+jsonResponse = (NodeDefinition);
export type GetV1NodesByUserBySystemByNodeIdby-.+jsonError = unknown;
export type GetV1NodesByUserBySystemByNodeIdby-.+WasmData = {
path: {
nodeId: string;
system: string;
user: string;
};
};
export type GetV1NodesByUserBySystemByNodeIdby-.+WasmResponse = (unknown);
export type GetV1NodesByUserBySystemByNodeIdby-.+WasmError = unknown;
export type PostV1NodesResponse = (NodeDefinition);
export type PostV1NodesError = unknown;
export type GetV1UsersUsersJsonResponse = (Array<User>);
export type GetV1UsersUsersJsonError = unknown;
export type GetV1UsersByUserIdJsonData = {
path?: {
userId?: string;
};
};
export type GetV1UsersByUserIdJsonResponse = (User);
export type GetV1UsersByUserIdJsonError = unknown;

2924
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
DATABASE_URL=postgres://nodarium:nodarium@postgres-db:5432/nodarium

View File

@ -1,13 +0,0 @@
FROM denoland/deno:alpine
ARG GIT_REVISION
ENV DENO_DEPLOYMENT_ID=${GIT_REVISION}
WORKDIR /app
COPY . .
RUN deno cache src/server.ts
EXPOSE 8000
CMD ["task", "run"]

View File

@ -1,65 +0,0 @@
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/nodes`;
// const url = "https://node-store.app.max-richter.dev/nodes";
const res = await fetch(url, {
method: "POST",
body: wasmContent,
});
if (res.ok) {
console.log(`Uploaded ${node.id}`);
} else {
const text = await res.text();
console.log(`Failed to upload ${node.id}: ${res.status} ${text}`);
}
}
for (const node of nodes) {
await postNode(node);
}

View File

@ -1,29 +0,0 @@
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: task dev
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:

View File

@ -1,22 +0,0 @@
{
"tasks": {
"dev": "deno run -A --watch src/server.ts",
"run": "deno run -A src/server.ts",
"test": "deno run vitest",
"drizzle": "podman-compose exec app deno --env -A --node-modules-dir npm:drizzle-kit",
"upload": "deno run --allow-read --allow-net bin/upload.ts"
},
"imports": {
"@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-kit": "npm:drizzle-kit@^0.30.1",
"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"
}
}

883
store/deno.lock generated
View File

@ -1,883 +0,0 @@
{
"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-kit@~0.30.1": "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-kit@~0.30.1",
"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"
]
}
}

View File

@ -1,11 +0,0 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "./drizzle",
schema: "./src/db/schema.ts",
dialect: "postgresql",
dbCredentials: {
url: Deno.env.get("DATABASE_URL")!,
},
});

View File

@ -1,25 +0,0 @@
CREATE TABLE "users" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
CONSTRAINT "users_name_unique" UNIQUE("name")
);
--> statement-breakpoint
CREATE TABLE "nodes" (
"id" serial PRIMARY KEY NOT NULL,
"userId" varchar NOT NULL,
"createdAt" timestamp DEFAULT now(),
"systemId" varchar NOT NULL,
"nodeId" varchar NOT NULL,
"content" "bytea" NOT NULL,
"definition" json NOT NULL,
"hash" varchar(16) NOT NULL,
"previous" varchar(16),
CONSTRAINT "nodes_hash_unique" UNIQUE("hash")
);
--> statement-breakpoint
ALTER TABLE "nodes" ADD CONSTRAINT "nodes_userId_users_name_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("name") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "nodes" ADD CONSTRAINT "node_previous_fk" FOREIGN KEY ("previous") REFERENCES "public"."nodes"("hash") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "user_id_idx" ON "nodes" USING btree ("userId");--> statement-breakpoint
CREATE INDEX "system_id_idx" ON "nodes" USING btree ("systemId");--> statement-breakpoint
CREATE INDEX "node_id_idx" ON "nodes" USING btree ("nodeId");--> statement-breakpoint
CREATE INDEX "hash_idx" ON "nodes" USING btree ("hash");

View File

@ -1,217 +0,0 @@
{
"id": "15ad729d-5756-4c06-87ed-cb8b721201f9",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_name_unique": {
"name": "users_name_unique",
"nullsNotDistinct": false,
"columns": [
"name"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.nodes": {
"name": "nodes",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"userId": {
"name": "userId",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"systemId": {
"name": "systemId",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"nodeId": {
"name": "nodeId",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"content": {
"name": "content",
"type": "bytea",
"primaryKey": false,
"notNull": true
},
"definition": {
"name": "definition",
"type": "json",
"primaryKey": false,
"notNull": true
},
"hash": {
"name": "hash",
"type": "varchar(16)",
"primaryKey": false,
"notNull": true
},
"previous": {
"name": "previous",
"type": "varchar(16)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_id_idx": {
"name": "user_id_idx",
"columns": [
{
"expression": "userId",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"system_id_idx": {
"name": "system_id_idx",
"columns": [
{
"expression": "systemId",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"node_id_idx": {
"name": "node_id_idx",
"columns": [
{
"expression": "nodeId",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"hash_idx": {
"name": "hash_idx",
"columns": [
{
"expression": "hash",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"nodes_userId_users_name_fk": {
"name": "nodes_userId_users_name_fk",
"tableFrom": "nodes",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"name"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"node_previous_fk": {
"name": "node_previous_fk",
"tableFrom": "nodes",
"tableTo": "nodes",
"columnsFrom": [
"previous"
],
"columnsTo": [
"hash"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"nodes_hash_unique": {
"name": "nodes_hash_unique",
"nullsNotDistinct": false,
"columns": [
"hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -1,13 +0,0 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1734703963242,
"tag": "0000_known_kid_colt",
"breakpoints": true
}
]
}

File diff suppressed because one or more lines are too long

View File

@ -1,22 +0,0 @@
import { drizzle } from "drizzle-orm/node-postgres";
import pg from "pg";
import * as schema from "./schema.ts";
import { migrate } from "drizzle-orm/node-postgres/migrator";
// Use pg driver.
const { Pool } = pg;
// Instantiate Drizzle client with pg driver and schema.
export const db = drizzle({
client: new Pool({
max: 20,
connectionString: Deno.env.get("DATABASE_URL"),
}),
schema,
});
export async function migrateDb() {
await migrate(db, { migrationsFolder: "drizzle" });
console.log("Database migrated");
}

View File

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

View File

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

View File

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

View File

@ -1,43 +0,0 @@
import {
customType,
foreignKey,
index,
json,
pgTable,
serial,
timestamp,
varchar,
} from "drizzle-orm/pg-core";
import { usersTable } from "../user/user.schema.ts";
import { NodeDefinition } from "./validations/types.ts";
const bytea = customType<{
data: ArrayBuffer;
default: false;
}>({
dataType() {
return "bytea";
},
});
export const nodeTable = pgTable("nodes", {
id: serial().primaryKey(),
userId: varchar().notNull().references(() => usersTable.name),
createdAt: timestamp().defaultNow(),
systemId: varchar().notNull(),
nodeId: varchar().notNull(),
content: bytea().notNull(),
definition: json().notNull().$type<NodeDefinition>(),
hash: varchar({ length: 16 }).notNull().unique(),
previous: varchar({ length: 16 }),
}, (table) => [
foreignKey({
columns: [table.previous],
foreignColumns: [table.hash],
name: "node_previous_fk",
}),
index("user_id_idx").on(table.userId),
index("system_id_idx").on(table.systemId),
index("node_id_idx").on(table.nodeId),
index("hash_idx").on(table.hash),
]);

View File

@ -1,241 +0,0 @@
import { db } from "../../db/db.ts";
import { nodeTable } from "./node.schema.ts";
import { NodeDefinition, NodeDefinitionSchema } from "./validations/types.ts";
import { and, asc, eq } from "drizzle-orm";
import { createHash } from "node:crypto";
import { extractDefinition } from "./worker/index.ts";
import { InvalidNodeDefinitionError, NodeNotFoundError } from "./errors.ts";
export type CreateNodeDTO = {
id: string;
system: string;
user: string;
content: ArrayBuffer;
};
function getNodeHash(content: Uint8Array) {
const hash = createHash("sha256");
hash.update(content);
return hash.digest("hex").slice(0, 16);
}
export async function createNode(
wasmBuffer: ArrayBuffer,
content: Uint8Array,
): Promise<NodeDefinition> {
const def = await extractDefinition(wasmBuffer);
const [userId, systemId, nodeId] = def.id.split("/");
const hash = getNodeHash(content);
const node: typeof nodeTable.$inferInsert = {
userId,
systemId,
nodeId,
definition: def,
hash,
content: content,
};
const previousNode = await db
.select({ hash: nodeTable.hash })
.from(nodeTable)
.orderBy(asc(nodeTable.createdAt))
.limit(1);
if (previousNode[0]) {
node.previous = previousNode[0].hash;
}
await db.insert(nodeTable).values(node);
return def;
}
export async function getNodeDefinitionsByUser(userName: string) {
const nodes = await db
.select({
definition: nodeTable.definition,
hash: nodeTable.hash,
})
.from(nodeTable)
.where(and(eq(nodeTable.userId, userName)));
return nodes.map((n) => ({
...n.definition,
// id: n.definition.id + "@" + n.hash,
}));
}
export async function getNodesBySystem(
username: string,
systemId: string,
): Promise<NodeDefinition[]> {
const nodes = await db
.selectDistinctOn(
[nodeTable.userId, nodeTable.systemId, nodeTable.nodeId],
{ definition: nodeTable.definition, hash: nodeTable.hash },
)
.from(nodeTable)
.where(
and(eq(nodeTable.systemId, systemId), eq(nodeTable.userId, username)),
)
.orderBy(nodeTable.userId, nodeTable.systemId, nodeTable.nodeId);
const definitions = nodes
.map(
(node) =>
[NodeDefinitionSchema.safeParse(node.definition), node.hash] as const,
)
.filter(([v]) => v.success)
.map(([v, hash]) => ({
...v.data,
// id: v?.data?.id + "@" + hash,
}));
return definitions;
}
export async function getNodeWasmById(
userName: string,
systemId: string,
nodeId: string,
) {
const node = await db
.select({ content: nodeTable.content })
.from(nodeTable)
.where(
and(
eq(nodeTable.userId, userName),
eq(nodeTable.systemId, systemId),
eq(nodeTable.nodeId, nodeId),
),
)
.orderBy(asc(nodeTable.createdAt))
.limit(1);
if (!node[0]) {
throw new NodeNotFoundError();
}
return node[0].content;
}
export async function getNodeDefinitionById(
userName: string,
systemId: string,
nodeId: string,
) {
const node = await db
.select({
definition: nodeTable.definition,
hash: nodeTable.hash,
})
.from(nodeTable)
.where(
and(
eq(nodeTable.userId, userName),
eq(nodeTable.systemId, systemId),
eq(nodeTable.nodeId, nodeId),
),
)
.orderBy(asc(nodeTable.createdAt))
.limit(1);
if (!node[0]) {
throw new NodeNotFoundError();
}
const definition = NodeDefinitionSchema.safeParse(node[0]?.definition);
if (!definition.success) {
throw new InvalidNodeDefinitionError();
}
return {
...definition.data,
// id: definition.data.id + "@" + node[0].hash
};
}
export async function getNodeVersions(
user: string,
system: string,
nodeId: string,
) {
const nodes = await db
.select({
definition: nodeTable.definition,
hash: nodeTable.hash,
})
.from(nodeTable)
.where(
and(
eq(nodeTable.userId, user),
eq(nodeTable.systemId, system),
eq(nodeTable.nodeId, nodeId),
),
)
.orderBy(asc(nodeTable.createdAt));
return nodes.map((node) => ({
...node.definition,
// id: node.definition.id + "@" + node.hash,
}));
}
export async function getNodeVersion(
user: string,
system: string,
nodeId: string,
hash: string,
) {
const nodes = await db
.select({
definition: nodeTable.definition,
})
.from(nodeTable)
.where(
and(
eq(nodeTable.userId, user),
eq(nodeTable.systemId, system),
eq(nodeTable.nodeId, nodeId),
eq(nodeTable.hash, hash),
),
)
.limit(1);
if (nodes.length === 0) {
throw new NodeNotFoundError();
}
return nodes[0].definition;
}
export async function getNodeVersionWasm(
user: string,
system: string,
nodeId: string,
hash: string,
) {
const node = await db
.select({
content: nodeTable.content,
})
.from(nodeTable)
.where(
and(
eq(nodeTable.userId, user),
eq(nodeTable.systemId, system),
eq(nodeTable.nodeId, nodeId),
eq(nodeTable.hash, hash),
),
)
.limit(1);
if (node.length === 0) {
throw new NodeNotFoundError();
}
return node[0].content;
}

View File

@ -1,11 +0,0 @@
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();
expect(true).toEqual(true);
expect(json).toEqual({ hello: "world" });
});

View File

@ -1,81 +0,0 @@
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(),
});
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(),
});
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(),
});
const NodeInputBooleanSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("boolean"),
value: z.boolean().optional(),
});
const NodeInputSelectSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("select"),
options: z.array(z.string()).optional(),
value: z.number().optional(),
});
const NodeInputSeedSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("seed"),
value: z.number().optional(),
});
const NodeInputVec3Schema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("vec3"),
value: z.array(z.number()).optional(),
});
const NodeInputGeometrySchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("geometry"),
});
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>;

View File

@ -1,31 +0,0 @@
import { z } from "zod";
import { NodeInputSchema } from "./inputs.ts";
export type NodeId = `${string}/${string}/${string}`;
export const idRegex = /[a-z0-9-]+/i;
const idSchema = z
.string()
.regex(
new RegExp(
`^(${idRegex.source})/(${idRegex.source})/(${idRegex.source})$`,
),
"Invalid id format",
);
export const NodeDefinitionSchema = z
.object({
id: idSchema,
inputs: z.record(NodeInputSchema).optional(),
outputs: z.array(z.string()).optional(),
meta: z
.object({
description: z.string().optional(),
title: z.string().optional(),
})
.optional(),
})
.openapi("NodeDefinition");
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema>;

View File

@ -1,36 +0,0 @@
import { UnknownWorkerResponseError, WorkerTimeoutError } from "../errors.ts";
import { NodeDefinition } from "../validations/types.ts";
import { WorkerMessage } from "./messages.ts";
export function extractDefinition(
content: ArrayBuffer,
): Promise<NodeDefinition> {
const worker = new Worker(
new URL("./node.worker.ts", import.meta.url).href,
{
type: "module",
},
) as Worker & {
postMessage: (message: WorkerMessage) => void;
};
return new Promise((res, rej) => {
worker.postMessage({ action: "extract-definition", content });
setTimeout(() => {
worker.terminate();
rej(new WorkerTimeoutError());
}, 100);
worker.onmessage = function (e) {
switch (e.data.action) {
case "result":
res(e.data.result);
break;
case "error":
rej(e.data.error);
break;
default:
rej(new UnknownWorkerResponseError());
}
};
});
}

View File

@ -1,21 +0,0 @@
import { NodeDefinition } from "../validations/types.ts";
type ExtractDefinitionMessage = {
action: "extract-definition";
content: ArrayBuffer;
};
type ErrorMessage = {
action: "error";
error: Error;
};
type ResultMessage = {
action: "result";
result: NodeDefinition;
};
export type WorkerMessage =
| ErrorMessage
| ResultMessage
| ExtractDefinitionMessage;

View File

@ -1,40 +0,0 @@
/// <reference lib="webworker" />
import { NodeDefinitionSchema } from "../validations/types.ts";
import { WorkerMessage } from "./messages.ts";
import { createWasmWrapper } from "./utils.ts";
const workerSelf = self as DedicatedWorkerGlobalScope & {
postMessage: (message: WorkerMessage) => void;
};
function extractDefinition(wasmCode: ArrayBuffer) {
try {
const wasm = createWasmWrapper(wasmCode);
const definition = wasm.get_definition();
const p = NodeDefinitionSchema.safeParse(definition);
if (!p.success) {
workerSelf.postMessage({ action: "error", error: p.error });
return;
}
workerSelf.postMessage({ action: "result", result: p.data });
} catch (e) {
console.log("HEEERE", e);
workerSelf.postMessage({ action: "error", error: e });
}
}
self.onmessage = (e: MessageEvent<WorkerMessage>) => {
switch (e.data.action) {
case "extract-definition":
extractDefinition(e.data.content);
self.close();
break;
default:
throw new Error("Unknown action: " + e.data.action);
}
};

View File

@ -1,255 +0,0 @@
// @ts-nocheck: Nocheck
import { NodeDefinition } from "../validations/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;
}

View File

@ -1,55 +0,0 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { usersTable } from "./user.schema.ts";
import { db } from "../../db/db.ts";
import { findUserByName } from "./user.service.ts";
import { UserSchema } from "./user.validation.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 };

View File

@ -1,6 +0,0 @@
import { pgTable, text, uuid } from "drizzle-orm/pg-core";
export const usersTable = pgTable("users", {
id: uuid().primaryKey().defaultRandom(),
name: text().unique().notNull(),
});

View File

@ -1,28 +0,0 @@
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)).limit(1);
return users[0];
}

View File

@ -1,8 +0,0 @@
import { z } from "@hono/zod-openapi";
export const UserSchema = z
.object({
id: z.string().uuid(),
name: z.string().min(1),
})
.openapi("User");

View File

@ -1,37 +0,0 @@
import { createUser } from "./routes/user/user.service.ts";
import { swaggerUI } from "@hono/swagger-ui";
import { logger } from "hono/logger";
import { cors } from "hono/cors";
import { OpenAPIHono } from "@hono/zod-openapi";
import { nodeRouter } from "./routes/node/node.controller.ts";
import { userRouter } from "./routes/user/user.controller.ts";
import { migrateDb } from "./db/db.ts";
const router = new OpenAPIHono();
router.use(logger());
router.use(cors());
router.route("nodes", nodeRouter);
router.route("users", userRouter);
router.doc("/openapi.json", {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "Nodarium API",
},
});
router.get("/ui", swaggerUI({ url: "/openapi.json" }));
Deno.serve(router.fetch);
async function init() {
await migrateDb();
await createUser("max");
const openapi = await router.request("/openapi.json");
const json = await openapi.text();
Deno.writeTextFile("openapi.json", json);
}
await init();

View File

@ -1,7 +0,0 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
},
});