diff --git a/README.md b/README.md index 8ddaf00..af194fa 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Sophisticated scheduler for durable tasks, built on Durable Object Alarms. - schedule tasks by time, delay, or cron expression - schedule multiple tasks on the same object -- query tasks by name, id, or payload pattern +- query tasks by description or id (or by time range?) - cancel tasks Bonus: This will be particularly useful when wired up with an LLM agent, so you'll be able to schedule tasks by describing them in natural language. Like "remind me to call my friend every monday at 10:00" @@ -16,8 +16,7 @@ import { Scheduler } from "durable-scheduler"; type Task = { id: string; - name: string; - payload: any; + description: string; time: Date; } & ( | { @@ -25,7 +24,7 @@ type Task = { type: "scheduled"; } | { - delay: number; + delayInSeconds: number; type: "delayed"; } | { @@ -38,52 +37,38 @@ class MyClass extends Scheduler { foo() { // schedule at specific time this.scheduler.scheduleTask({ - name: "my-task", + description: "my-task", time: new Date(Date.now() + 1000), - payload: { - // ... - }, }); // schedule after a certain amount of time this.scheduler.scheduleTask({ - name: "my-task", - delay: 1000, // in ms? s? - payload: { - // ... - }, + description: "my-task", + delayInSeconds: 1000, // in ms? s? }); // schedule to run periodically this.scheduler.scheduleTask({ - name: "my-task", + description: "my-task", cron: "*/1 * * * *", // every minute - payload: { - // ... - }, }); - // you can also use an id instead of a name + // you can also specify an id this.scheduler.scheduleTask({ id: "my-task", time: new Date(Date.now() + 1000), - payload: { - // ... - }, }); // ids must be unique - // names can be repeated - // if you don't provide a name or id, it will default to a random uuid + // if you don't provide an id, it will default to a random uuid // if you try to schedule a task with an id that already exists, // it will overwrite the existing task // query for tasks const tasks = this.scheduler.query({ - // by name + // by description // by id - // by payload pattern matching (?) // by time range // some kind of sql syntax here? dunno.. }); diff --git a/eslint.config.mjs b/eslint.config.mjs index 3f5c3a9..ab1c78c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -48,7 +48,8 @@ export default [ "no-undef": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], - + "@typescript-eslint/require-await": "off", + "@typescript-eslint/await-thenable": "off", // React rules ...reactPlugin.configs.recommended.rules, "react/react-in-jsx-scope": "off", diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..9731e54 --- /dev/null +++ b/example/README.md @@ -0,0 +1,7 @@ +This is an example of how to use the `durable-scheduler` package. + +The example app is a TODO list app that can schedule tasks to be run at a future date. It is a client side rendered React app that uses tailwind for styling, built with Vite. + +We have one input at the top-middle of the page, where you can add a new task. This takes freeform natural language text input (eg: "Buy milk tomorrow at 10am", "Send me a report every friday evening", "Remind me to call my wife in 20 minutes"). We take the input from the user, which is then parsed into a task. + +Underneath the input, there is a list of all the tasks that have been scheduled. Each task has a description, a due date, and a status checkbox. We use the status checkbox to mark a task as complete. We can also delete a task altogether by clicking the delete button. diff --git a/example/src/client/app.tsx b/example/src/client/app.tsx index a745377..4b9ecbe 100644 --- a/example/src/client/app.tsx +++ b/example/src/client/app.tsx @@ -1,3 +1,124 @@ +import { useState } from "react"; + +import { SqlTask } from "../../../src"; + +interface ToDo { + id: string; + inputText: string; + parsedTask: SqlTask | undefined; + completed: boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const ROOM_ID = "username"; // TODO: this will read a username from auth later + export default function App() { - return

Hello World

; + const [todos, setTodos] = useState([]); + const [inputText, setInputText] = useState(""); + + const handleAddToDo = async (e: React.FormEvent) => { + e.preventDefault(); + if (!inputText.trim()) return; + + try { + const newToDo: ToDo = { + id: crypto.randomUUID(), + inputText, + parsedTask: undefined, + completed: false, + }; + + setTodos((prev) => [...prev, newToDo]); + // TODO: Schedule the task and update the task with the parsedTask + + // let's first convert it to the object that the scheduler expects + const result = await fetch("/api/string-to-schedule", { + method: "POST", + body: inputText, + }); + const parsedTask = await result.json(); + // eslint-disable-next-line no-console + console.log("parsedTask", parsedTask); + // ok now let's schedule it + + // TODO: schedule the task + setInputText(""); + } catch (error) { + console.error("Failed to parse todo:", error); + // You might want to show an error message to the user here + } + }; + + const handleToggleToDo = async (todoId: string) => { + setTodos((prev) => + prev.map((todo) => (todo.id === todoId ? { ...todo, completed: !todo.completed } : todo)) + ); + }; + + const handleDeleteToDo = async (todoId: string) => { + // TODO: Cancel the scheduled task + setTodos((prev) => prev.filter((todo) => todo.id !== todoId)); + }; + + return ( +
+
+

ToDo List

+ +
void handleAddToDo(e)} className="mb-8"> +
+ setInputText(e.target.value)} + placeholder="Add a todo (e.g., 'Buy milk tomorrow at 10am')" + className="flex-1 px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + +
+
+ +
+ {todos.map((todo) => ( +
+ void handleToggleToDo(todo.id)} + className="h-5 w-5 rounded border-gray-300 text-blue-500 focus:ring-blue-500" + /> +
+

+ {todo.parsedTask?.description} +

+

Due: {todo.parsedTask?.time}

+
+ +
+ ))} +
+
+
+ ); } diff --git a/example/src/server/index.ts b/example/src/server/index.ts index eb2a31d..d8bcc9c 100644 --- a/example/src/server/index.ts +++ b/example/src/server/index.ts @@ -1,17 +1,42 @@ +import { createOpenAI } from "@ai-sdk/openai"; +import { generateObject } from "ai"; +import { Server, routePartykitRequest } from "partyserver"; import { z } from "zod"; import { Scheduler } from "../../../src"; +export { Scheduler }; + type Env = { + OPENAI_API_KEY: string; AI: Ai; - SCHEDULER: DurableObjectNamespace; + Scheduler: DurableObjectNamespace>; + ToDos: DurableObjectNamespace; }; const taskSchema = z .object({ - id: z.string().default(() => crypto.randomUUID()), - name: z.string().optional(), - payload: z.record(z.any()).default({}), + id: z.string().default(() => { + return crypto.randomUUID(); + }), + description: z.string().optional(), + // // we haven't implemented this yet + // payload: z.record(z.any()).optional(), + // // this isn't necessary, but it's here for reference + // callback: z + // .union([ + // z.object({ + // type: z.literal("webhook"), + // url: z.string(), + // }), + // z.object({ + // type: z.literal("durable-object"), + // namespace: z.string(), + // id: z.string(), + // function: z.string(), + // }), + // ]) + // .optional(), }) .and( z.discriminatedUnion("type", [ @@ -21,12 +46,15 @@ const taskSchema = z }), z.object({ type: z.literal("delayed"), - delay: z.number(), + delayInSeconds: z.number(), }), z.object({ type: z.literal("cron"), cron: z.string(), }), + z.object({ + type: z.literal("no-schedule"), + }), ]) ); @@ -34,67 +62,29 @@ const taskSchema = z // if (!condition) throw new Error(message); // } -export class MyScheduler extends Scheduler { +export class ToDos extends Server { + scheduler: DurableObjectStub>; + constructor(state: DurableObjectState, env: Env) { + super(state, env); + this.scheduler = env.Scheduler.get(state.id); + } async fetch(request: Request) { const url = new URL(request.url); - if (!url.pathname.startsWith("/api/")) { - return fetch(request.url.replace("http://localhost:8787", "http://localhost:5173"), request); - } - const route = `${request.method} ${url.pathname}`; switch (route) { case "GET /api/": return new Response("Hello, world!"); - case "GET /api/string-to-schedule": { - const result = await this.env.AI.run("@cf/meta/llama-3.1-8b-instruct", { - prompt: ` - You are a helpful assistant. Today is ${new Date().toUTCString()}. - You are given a string that has to be input as an object into a scheduler. - The string may be: - - a delay like "in 10 minutes" - - you need to convert this into an object with a delay property like - { - "type": "delayed", - "delay": 600000 - } - - a specific time like "next monday at 10:00" - - you need to convert this into an object with a time property with a UTC timestamp like - { - "type": "scheduled", - "time": "Mon, 02 Dec 2024 10:00:00 GMT" - } - - a cron expression like "every 10 minutes" - - you need to convert this into an object with a cron property like - { - "type": "cron", - "cron": "*/10 * * * *" - } - - Here is the input string: - ${url.searchParams.get("input")} - - Do not include any other text than the json object. - `, - }); - - // @ts-expect-error - this is a string - // eslint-disable-next-line no-console - console.log(result.response); - // @ts-expect-error - this is a string - return new Response(result.response as string); - } - case "GET /api/tasks": { - const tasks = await this.query(); + const tasks = await this.scheduler.query(); return new Response(JSON.stringify(tasks)); } case "POST /api/tasks": { const task = taskSchema.parse(await request.json()); - await this.scheduleTask(task); + await this.scheduler.scheduleTask(task); return new Response(JSON.stringify(task)); } @@ -112,8 +102,35 @@ export default { return fetch(request.url.replace("http://localhost:8787", "http://localhost:5173"), request); } - const id = env.SCHEDULER.idFromName("example"); - const stub = env.SCHEDULER.get(id); - return stub.fetch(request); + switch (`${request.method} ${url.pathname}`) { + case "POST /api/string-to-schedule": { + const openai = createOpenAI({ + apiKey: env.OPENAI_API_KEY, + }); + + const result = await generateObject({ + model: openai("gpt-4o"), + mode: "json", + schemaName: "task", + schemaDescription: "A task to be scheduled", + schema: taskSchema, + maxRetries: 5, + prompt: ` +Today is ${new Date().toUTCString()}. +You are given a string that has to be input as an object into a scheduler. + +Here is the string: +${await request.text()} +`, + }); + + // eslint-disable-next-line no-console + console.log(result.object); + + return new Response(JSON.stringify(result.object)); + } + } + + return (await routePartykitRequest(request, env)) || new Response("Not found", { status: 404 }); }, } satisfies ExportedHandler; diff --git a/example/wrangler.toml b/example/wrangler.toml index e90cb6e..36cbfd7 100644 --- a/example/wrangler.toml +++ b/example/wrangler.toml @@ -8,11 +8,16 @@ assets = { directory = "public" } [[durable_objects.bindings]] name = "SCHEDULER" -class_name = "MyScheduler" +class_name = "Scheduler" + +[[durable_objects.bindings]] +name = "TODOS" +class_name = "ToDos" + [[migrations]] tag = "v1" -new_sqlite_classes = [ "MyScheduler" ] +new_sqlite_classes = [ "Scheduler", "ToDos" ] [ai] binding = "AI" diff --git a/package-lock.json b/package-lock.json index de12bbe..26313dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,41 +12,164 @@ "cron-parser": "^4.9.0" }, "devDependencies": { + "@ai-sdk/openai": "^1.0.7", "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.10", - "@cloudflare/vitest-pool-workers": "^0.5.32", - "@cloudflare/workers-types": "^4.20241127.0", - "@eslint/js": "^9.15.0", + "@cloudflare/vitest-pool-workers": "^0.5.34", + "@cloudflare/workers-types": "^4.20241205.0", + "@eslint/js": "^9.16.0", "@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", - "@tailwindcss/vite": "^4.0.0-beta.3", + "@tailwindcss/vite": "^4.0.0-beta.6", "@types/bun": "^1.1.14", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@typescript-eslint/eslint-plugin": "^8.16.0", - "@typescript-eslint/parser": "^8.16.0", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.1", + "@typescript-eslint/eslint-plugin": "^8.17.0", + "@typescript-eslint/parser": "^8.17.0", "@vitejs/plugin-react": "^4.3.4", + "ai": "^4.0.13", "concurrently": "^9.1.0", - "eslint": "^9.15.0", + "eslint": "^9.16.0", "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.1", + "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0", - "prettier": "^3.4.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "tailwindcss": "^4.0.0-beta.3", + "eslint-plugin-react-hooks": "^5.1.0", + "partyserver": "^0.0.57", + "partysocket": "^1.0.2", + "prettier": "^3.4.2", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwindcss": "^4.0.0-beta.6", "tshy": "^3.0.2", "typescript": "^5.7.2", - "vite": "^6.0.1", - "vitest": "2.1.6", - "wrangler": "^3.91.0", + "vite": "^6.0.3", + "vitest": "2.1.8", + "wrangler": "^3.93.0", "zod": "^3.23.8" } }, + "node_modules/@ai-sdk/openai": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.0.7.tgz", + "integrity": "sha512-eXWERuUrMyH3k1GpmYHzTj7Xzxx5/0dENAkbxnj7t2Sn2Mgfb5Td/8QYfGSxuuPd8BrfGTyknArff6yqPkuvPA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.0.1", + "@ai-sdk/provider-utils": "2.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.0.1.tgz", + "integrity": "sha512-mV+3iNDkzUsZ0pR2jG0sVzU6xtQY5DtSCBy3JFycLp6PwjyLw/iodfL3MwdmMCRJWgs3dadcHejRnMvF9nGTBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.0.3.tgz", + "integrity": "sha512-Cyk7GlFEse2jQ4I3FWYuZ1Zhr5w1mD9SHMJTYm/in1rd7r89nmEoQiOy3h8YV2ZvTa2/6aR10xZ4M0k4B3BluA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.0.1", + "eventsource-parser": "^3.0.0", + "nanoid": "^3.3.7", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/react": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.0.5.tgz", + "integrity": "sha512-OPqYhltJE9dceWxw5pTXdYtAhs1Ca6Ly8xR7z/T+JZ0lrcgembFIMvnJ0dMBkba07P4GQBmuvd5DVTeAqPM9SQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "2.0.3", + "@ai-sdk/ui-utils": "1.0.4", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/react/node_modules/swr": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@ai-sdk/ui-utils": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.0.4.tgz", + "integrity": "sha512-P2vDvASaGsD+lmbsQ5WYjELxJBQgse3CpxyLSA+usZiZxspwYbLFsSWiYz3zhIemcnS0T6/OwQdU6UlMB4N5BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.0.1", + "@ai-sdk/provider-utils": "2.0.3", + "zod-to-json-schema": "^3.23.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -700,9 +823,9 @@ } }, "node_modules/@cloudflare/vitest-pool-workers": { - "version": "0.5.32", - "resolved": "https://registry.npmjs.org/@cloudflare/vitest-pool-workers/-/vitest-pool-workers-0.5.32.tgz", - "integrity": "sha512-lJHwXq3VWKgzy7BzXs61O/QukT9QQ42hl27eRcqCV6P20OF4/bLH8rmUaYxdR8IxLlmNY7Q5h0wGN0nXBn8XKg==", + "version": "0.5.34", + "resolved": "https://registry.npmjs.org/@cloudflare/vitest-pool-workers/-/vitest-pool-workers-0.5.34.tgz", + "integrity": "sha512-OoC51MBgz0z/SNG5GnvE9VAyBB/xxJODb7OazoWyZlMWe8HIGjg/Dti1hyn18dugJeIL6WiT11E90CLVbNl48g==", "dev": true, "license": "MIT", "dependencies": { @@ -710,9 +833,9 @@ "cjs-module-lexer": "^1.2.3", "devalue": "^4.3.0", "esbuild": "0.17.19", - "miniflare": "3.20241106.1", + "miniflare": "3.20241205.0", "semver": "^7.5.1", - "wrangler": "3.91.0", + "wrangler": "3.93.0", "zod": "^3.22.3" }, "peerDependencies": { @@ -722,9 +845,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20241106.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20241106.1.tgz", - "integrity": "sha512-zxvaToi1m0qzAScrxFt7UvFVqU8DxrCO2CinM1yQkv5no7pA1HolpIrwZ0xOhR3ny64Is2s/J6BrRjpO5dM9Zw==", + "version": "1.20241205.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20241205.0.tgz", + "integrity": "sha512-TArEZkSZkHJyEwnlWWkSpCI99cF6lJ14OVeEoI9Um/+cD9CKZLM9vCmsLeKglKheJ0KcdCnkA+DbeD15t3VaWg==", "cpu": [ "x64" ], @@ -739,9 +862,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20241106.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20241106.1.tgz", - "integrity": "sha512-j3dg/42D/bPgfNP3cRUBxF+4waCKO/5YKwXNj+lnVOwHxDu+ne5pFw9TIkKYcWTcwn0ZUkbNZNM5rhJqRn4xbg==", + "version": "1.20241205.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20241205.0.tgz", + "integrity": "sha512-u5eqKa9QRdA8MugfgCoD+ADDjY6EpKbv3hSYJETmmUh17l7WXjWBzv4pUvOKIX67C0UzMUy4jZYwC53MymhX3w==", "cpu": [ "arm64" ], @@ -756,9 +879,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20241106.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20241106.1.tgz", - "integrity": "sha512-Ih+Ye8E1DMBXcKrJktGfGztFqHKaX1CeByqshmTbODnWKHt6O65ax3oTecUwyC0+abuyraOpAtdhHNpFMhUkmw==", + "version": "1.20241205.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20241205.0.tgz", + "integrity": "sha512-OYA7S5zpumMamWEW+IhhBU6YojIEocyE5X/YFPiTOCrDE3dsfr9t6oqNE7hxGm1VAAu+Irtl+a/5LwmBOU681w==", "cpu": [ "x64" ], @@ -773,9 +896,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20241106.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20241106.1.tgz", - "integrity": "sha512-mdQFPk4+14Yywn7n1xIzI+6olWM8Ybz10R7H3h+rk0XulMumCWUCy1CzIDauOx6GyIcSgKIibYMssVHZR30ObA==", + "version": "1.20241205.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20241205.0.tgz", + "integrity": "sha512-qAzecONjFJGIAVJZKExQ5dlbic0f3d4A+GdKa+H6SoUJtPaWiE3K6WuePo4JOT7W3/Zfh25McmX+MmpMUUcM5Q==", "cpu": [ "arm64" ], @@ -790,9 +913,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20241106.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20241106.1.tgz", - "integrity": "sha512-4rtcss31E/Rb/PeFocZfr+B9i1MdrkhsTBWizh8siNR4KMmkslU2xs2wPaH1z8+ErxkOsHrKRa5EPLh5rIiFeg==", + "version": "1.20241205.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20241205.0.tgz", + "integrity": "sha512-BEab+HiUgCdl6GXAT7EI2yaRtDPiRJlB94XLvRvXi1ZcmQqsrq6awGo6apctFo4WUL29V7c09LxmN4HQ3X2Tvg==", "cpu": [ "x64" ], @@ -807,9 +930,9 @@ } }, "node_modules/@cloudflare/workers-shared": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-shared/-/workers-shared-0.9.0.tgz", - "integrity": "sha512-eP6Ir45uPbKnpADVzUCtkRUYxYxjB1Ew6n/whTJvHu8H4m93USHAceCMm736VBZdlxuhXXUjEP3fCUxKPn+cfw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-shared/-/workers-shared-0.10.0.tgz", + "integrity": "sha512-j3EwZBc9ctavmFVOQT1gqztRO/Plx4ZR0LMEEOif+5YoCcuD1P7/NEjlODPMc5a1w+8+7A/H+Ci8Ihd55+x0Zw==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { @@ -821,9 +944,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20241127.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20241127.0.tgz", - "integrity": "sha512-UqlvtqV8eI0CdPR7nxlbVlE52+lcjHvGdbYXEPwisy23+39RsFV7OOy0da0moJAhqnL2OhDmWTOaKdsVcPHiJQ==", + "version": "4.20241205.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20241205.0.tgz", + "integrity": "sha512-pj1VKRHT/ScQbHOIMFODZaNAlJHQHdBSZXNIdr9ebJzwBff9Qz8VdqhbhggV7f+aUEh8WSbrsPIo4a+WtgjUvw==", "dev": true, "license": "MIT OR Apache-2.0" }, @@ -1432,9 +1555,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", + "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", "dev": true, "license": "MIT", "engines": { @@ -1827,6 +1950,16 @@ "node": ">=12.4.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.27.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", @@ -2120,44 +2253,44 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.0-beta.3.tgz", - "integrity": "sha512-TPS/a/fsLwPU/p3VeSSKXfuhzKjvmMTJDIp8zVrcBmSk+GNbIrcjkc2p/KcvTSh7Zfw6xF/Z8cjA/JnTgMVjQA==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.0-beta.6.tgz", + "integrity": "sha512-W07J19+05rRFKfk4sVtkNTb4iuGTI3BU6Ip/iEbb/w5NvaP+fSFK31OYFpJ+CXEWrbNgidUg46BQpw4Y1gophA==", "dev": true, "license": "MIT", "dependencies": { "enhanced-resolve": "^5.17.1", "jiti": "^2.4.0", - "tailwindcss": "4.0.0-beta.3" + "tailwindcss": "4.0.0-beta.6" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.0-beta.3.tgz", - "integrity": "sha512-uN9ZlT8w4LlktqzS3aH2SztfxixzTYGZqEaRQgVuBPkFfTahInX1+GEGNZwmZ/hadR2jkCuCqxg8z1IuhWsSbw==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.0-beta.6.tgz", + "integrity": "sha512-i/Fg/rXYwzV7OJEXbcwjNVkYPgnyfLdYXtduOX7Kvf686xn6jR/k6bhl9fVkQcDb/ujyKzNjD2POAjwhHAC8aw==", "dev": true, "license": "MIT", "engines": { "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.0.0-beta.3", - "@tailwindcss/oxide-darwin-arm64": "4.0.0-beta.3", - "@tailwindcss/oxide-darwin-x64": "4.0.0-beta.3", - "@tailwindcss/oxide-freebsd-x64": "4.0.0-beta.3", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.0-beta.3", - "@tailwindcss/oxide-linux-arm64-gnu": "4.0.0-beta.3", - "@tailwindcss/oxide-linux-arm64-musl": "4.0.0-beta.3", - "@tailwindcss/oxide-linux-x64-gnu": "4.0.0-beta.3", - "@tailwindcss/oxide-linux-x64-musl": "4.0.0-beta.3", - "@tailwindcss/oxide-win32-arm64-msvc": "4.0.0-beta.3", - "@tailwindcss/oxide-win32-x64-msvc": "4.0.0-beta.3" + "@tailwindcss/oxide-android-arm64": "4.0.0-beta.6", + "@tailwindcss/oxide-darwin-arm64": "4.0.0-beta.6", + "@tailwindcss/oxide-darwin-x64": "4.0.0-beta.6", + "@tailwindcss/oxide-freebsd-x64": "4.0.0-beta.6", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.0-beta.6", + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.0-beta.6", + "@tailwindcss/oxide-linux-arm64-musl": "4.0.0-beta.6", + "@tailwindcss/oxide-linux-x64-gnu": "4.0.0-beta.6", + "@tailwindcss/oxide-linux-x64-musl": "4.0.0-beta.6", + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.0-beta.6", + "@tailwindcss/oxide-win32-x64-msvc": "4.0.0-beta.6" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.0-beta.3.tgz", - "integrity": "sha512-YXXoYFvzELo3YRzFIgJrcJCdw1p31xY2ONO1akZwajNjP+Ac8QdvjZejFJc4sLtnL5EXmYSOFv5w3Pa2gRzcAA==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.0-beta.6.tgz", + "integrity": "sha512-yrUH/IfhkJWSYjF86UxpwrfaO866MjOhVQKG+wCgp0+YZGwirs8vX//a+8Y0SpEJrO0PVgmUU7xbsIM1EQWyTw==", "cpu": [ "arm64" ], @@ -2172,9 +2305,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.0-beta.3.tgz", - "integrity": "sha512-ERDVLTEbjFOEnWEYQx8FWgAVl0gPhtoUJ8jXXhTcY4lIGQ/UNWg7amRwFF8jV2qOXUhMh1XppfsxemFMNl/XtQ==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.0-beta.6.tgz", + "integrity": "sha512-WqFHSD/kocp8rH3KYJow8zTatTEMfdrkZu2KL8nnKfuAEk+juJuUnOQaXDmfsYNQSkGjtnGdcB/fueq4UtUSvw==", "cpu": [ "arm64" ], @@ -2189,9 +2322,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.0-beta.3.tgz", - "integrity": "sha512-FE5rWGNDNS6q0eSZiuD6M9VmBZgyEep+h4Oq7K6Ei+62sRTD0CRyLNhuot/w/C/IrkZqi2t/spHJa4mM3YKNiQ==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.0-beta.6.tgz", + "integrity": "sha512-zFc/TTBkDNLOKl/G9i1JOpg0AgrUOgBcSgURT/E0RsF0ZcKdiIjcyNswNThmjQnj+JAA8QL1Rct+04dH5j7Xjw==", "cpu": [ "x64" ], @@ -2206,9 +2339,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.0-beta.3.tgz", - "integrity": "sha512-CLmGpKpmtZpD/+9kvGmtDeY55OiwuePGsOpCrAZQ3PD40uNK2ghwWt+T/BmQJTI5RzsOPbMjzfzKmQthdtWZrg==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.0-beta.6.tgz", + "integrity": "sha512-J3+uiPmKTeyefQBy4F0fz5sBQOj9eUTke6W/do/CplM5zXORQVCOKnrY2CPyDpN9bh4TYutldoiI0BP0Yo2ShA==", "cpu": [ "x64" ], @@ -2223,9 +2356,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.0-beta.3.tgz", - "integrity": "sha512-szLw3+50bgAubvoFrynnt6ELbiLd9A/rYpchfA5AnWUakz29IxP7ZYruCkOnYnW9+KRTNv4kFSsRlPJAEJY5sw==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.0-beta.6.tgz", + "integrity": "sha512-oWAwN6LjX2KH8ej0a/Uc1qnuy0ZSyAwOoJ1Fm2s38ipDmvxC8ARkNfWbYKN/D7dFhY7T3NMV4qjzamFKsrgchQ==", "cpu": [ "arm" ], @@ -2240,9 +2373,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.0-beta.3.tgz", - "integrity": "sha512-BpsBhwgW1V152GQ7FepJR1zqq17oyl3pVe2UyTCIPv92xCwJQ+eDm2vTvaNF9L6dznBVpppMPPfDfxk4KGvgTg==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.0-beta.6.tgz", + "integrity": "sha512-p5jDtF6P3gOPl4eOH0YsN09yx/kXqLbgy4VMnOA9DB6Nlt+RVwM+JDa+RK/GAHL/qkAqO6YBEHeBURbWOeSuBw==", "cpu": [ "arm64" ], @@ -2257,9 +2390,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.0-beta.3.tgz", - "integrity": "sha512-S4+PO+C10cG58sNb9qcjPN0A7q9UUxUFdGjetrNVN/t1JseFNOy1OhPp7oQCiXwCYye7vZzKNAUJK9nIGLAMEg==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.0-beta.6.tgz", + "integrity": "sha512-ZI92S4LFuXx50gzkv98RXmybbRga183Y3NwBrQ+PfbRUOgDR0/tyT3Zh4CF/0eL9zzxa1mIE7cBbQjXNnejYqQ==", "cpu": [ "arm64" ], @@ -2274,9 +2407,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.0-beta.3.tgz", - "integrity": "sha512-iAGIPAE26xNAbHSaTpu8N2+eG0csOpJ+ukOoKvoQWKArjIfquRw/7XNX1A6pTMvVteFvKPt4e305U7kdf3KtfA==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.0-beta.6.tgz", + "integrity": "sha512-F0z48W0Yksrw+xg0GK54wUFa0KmFO9lpbKy2u/2yZtHs+ZwJ55PjdljjW1OJzEHpD81AT+8DT1PhNCkaqpOlqg==", "cpu": [ "x64" ], @@ -2291,9 +2424,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.0-beta.3.tgz", - "integrity": "sha512-dKeqjuK94YG+INIYpcfrynq9B5YkrBXEBsX26p1g0K6ai42nzO3Eg/fEadePSqGUBjL1CjtR4pChqaEUPNq1Xw==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.0-beta.6.tgz", + "integrity": "sha512-GQx5f3qzLuAO7s1NTGY8PDhiEvrRTgYQhGN8HzH1LhP+URSbwgysyHKWFacejWrZS2azr6jlE84usxk8yn/7Xg==", "cpu": [ "x64" ], @@ -2308,9 +2441,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.0-beta.3.tgz", - "integrity": "sha512-AEHX9depJFa3GCxmV9s+abklPPo0OmL2uNpPBCumk6N4GLUpmXNpkDlEYeSgggHPRwoSyfvbs957Ahvf5E4v4w==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.0-beta.6.tgz", + "integrity": "sha512-DUs1A5rVK1Da7XvY2FzVgS+5vinwVDOeXgOsL6dblL8ag70wlwzZjyB3YRbxKRekPynwKdILgIv8/TYE3cv7AQ==", "cpu": [ "arm64" ], @@ -2325,9 +2458,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.0-beta.3.tgz", - "integrity": "sha512-hb24QffsW/9dM+6DgLv5VbTJKGLgfXMjIF47KLCqkhuZRvkQoSPHT6kXJSerEkO3rY3ptCxkPIhyfudACeHMSQ==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.0-beta.6.tgz", + "integrity": "sha512-VRsHAs3QBZa88N2Bp+LOV1vxr6f82L3KtamLIhyNTYXnEQjJxazaGwVow0VzN9sru2MTvcXjVFwdd9hUbeBKbA==", "cpu": [ "x64" ], @@ -2358,20 +2491,19 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.0-beta.3.tgz", - "integrity": "sha512-OEnRluaa3hvjnCQhY11YN8GiPoUbXQL5Qb/727q+3FBiGA1vIqrVbIJ01Z256bkaZLCl+AUyqCGasRd2jsSVLA==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.0-beta.6.tgz", + "integrity": "sha512-SDlW8wrfhoGvEmNdbxdjPD0RJV5ErPegeJQFXNlS6CF815tzMMTekToIeMJphvwUO6w2TLSjvO9w3F9Z0ElMgQ==", "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.0.0-beta.3", - "@tailwindcss/oxide": "4.0.0-beta.3", + "@tailwindcss/node": "4.0.0-beta.6", + "@tailwindcss/oxide": "4.0.0-beta.6", "lightningcss": "^1.26.0", - "svelte-preprocess": "^6.0.2", - "tailwindcss": "4.0.0-beta.3" + "tailwindcss": "4.0.0-beta.6" }, "peerDependencies": { - "vite": "^5.2.0" + "vite": "^5.2.0 || ^6" } }, "node_modules/@types/babel__core": { @@ -2429,6 +2561,13 @@ "bun-types": "1.1.37" } }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -2470,28 +2609,20 @@ "@types/node": "*" } }, - "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/react": { - "version": "18.3.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", - "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.1.tgz", + "integrity": "sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.1.tgz", + "integrity": "sha512-hljHij7MpWPKF6u5vojuyfV0YA4YURsQG7KT6SzV0Zs2BXAtgdTxG6A229Ub/xiWV4w/7JL8fi6aAyjshH4meA==", "dev": true, "license": "MIT", "dependencies": { @@ -2509,17 +2640,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", - "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", + "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/type-utils": "8.16.0", - "@typescript-eslint/utils": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/type-utils": "8.17.0", + "@typescript-eslint/utils": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2543,16 +2674,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", - "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", + "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/typescript-estree": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", "debug": "^4.3.4" }, "engines": { @@ -2572,14 +2703,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", - "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", + "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0" + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2590,14 +2721,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", - "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", + "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.16.0", - "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/typescript-estree": "8.17.0", + "@typescript-eslint/utils": "8.17.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2618,9 +2749,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", - "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", "dev": true, "license": "MIT", "engines": { @@ -2632,14 +2763,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", - "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", + "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2677,16 +2808,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", - "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", + "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/typescript-estree": "8.16.0" + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2705,13 +2836,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", - "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/types": "8.17.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2756,14 +2887,14 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.6.tgz", - "integrity": "sha512-9M1UR9CAmrhJOMoSwVnPh2rELPKhYo0m/CSgqw9PyStpxtkwhmdM6XYlXGKeYyERY1N6EIuzkQ7e3Lm1WKCoUg==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.6", - "@vitest/utils": "2.1.6", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, @@ -2771,57 +2902,10 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/mocker": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.6.tgz", - "integrity": "sha512-MHZp2Z+Q/A3am5oD4WSH04f9B0T7UvwEb+v5W0kCYMhtXGYbdyl2NUk1wdSMqGthmhpiThPDp/hEoVwu16+u1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.6", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/@vitest/mocker/node_modules/magic-string": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", - "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, "node_modules/@vitest/pretty-format": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.6.tgz", - "integrity": "sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2832,13 +2916,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.6.tgz", - "integrity": "sha512-SjkRGSFyrA82m5nz7To4CkRSEVWn/rwQISHoia/DB8c6IHIhaE/UNAo+7UfeaeJRE979XceGl00LNkIz09RFsA==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.6", + "@vitest/utils": "2.1.8", "pathe": "^1.1.2" }, "funding": { @@ -2846,13 +2930,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.6.tgz", - "integrity": "sha512-5JTWHw8iS9l3v4/VSuthCndw1lN/hpPB+mlgn1BUhFbobeIUj1J1V/Bj2t2ovGEmkXLTckFjQddsxS5T6LuVWw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.6", + "@vitest/pretty-format": "2.1.8", "magic-string": "^0.30.12", "pathe": "^1.1.2" }, @@ -2871,9 +2955,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.6.tgz", - "integrity": "sha512-oTFObV8bd4SDdRka5O+mSh5w9irgx5IetrD5i+OsUUsk/shsBoHifwCzy45SAORzAhtNiprUVaK3hSCCzZh1jQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", "dev": true, "license": "MIT", "dependencies": { @@ -2884,13 +2968,13 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.6.tgz", - "integrity": "sha512-ixNkFy3k4vokOUTU2blIUvOgKq/N2PW8vKIjZZYsGJCMX69MRa9J2sKqX5hY/k5O5Gty3YJChepkqZ3KM9LyIQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.6", + "@vitest/pretty-format": "2.1.8", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, @@ -2921,17 +3005,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-typescript": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", - "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "acorn": ">=8.9.0" - } - }, "node_modules/acorn-walk": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", @@ -2945,6 +3018,37 @@ "node": ">=0.4.0" } }, + "node_modules/ai": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.0.13.tgz", + "integrity": "sha512-ic+qEVPQhfLpGPnZ2M55ErofeuKaD/TQebeh0qSPwv2PF+dQwsPr2Pw+JNYXahezAOaxFNdrDPz0EF1kKcSFSw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.0.1", + "@ai-sdk/provider-utils": "2.0.3", + "@ai-sdk/react": "1.0.5", + "@ai-sdk/ui-utils": "1.0.4", + "@opentelemetry/api": "1.9.0", + "jsondiffpatch": "0.6.0", + "zod-to-json-schema": "^3.23.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3022,17 +3126,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -3237,17 +3330,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3557,6 +3639,13 @@ "dev": true, "license": "MIT" }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "dev": true, + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3876,6 +3965,13 @@ "dev": true, "license": "MIT" }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4199,9 +4295,9 @@ } }, "node_modules/eslint": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", - "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", + "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", "dev": true, "license": "MIT", "dependencies": { @@ -4210,7 +4306,7 @@ "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.15.0", + "@eslint/js": "9.16.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4294,20 +4390,20 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz", - "integrity": "sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", + "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", "dev": true, "license": "ISC", "dependencies": { "@nolyfill/is-core-module": "1.0.39", - "debug": "^4.3.5", + "debug": "^4.3.7", "enhanced-resolve": "^5.15.0", - "eslint-module-utils": "^2.8.1", "fast-glob": "^3.3.2", "get-tsconfig": "^4.7.5", "is-bun-module": "^1.0.2", - "is-glob": "^4.0.3" + "is-glob": "^4.0.3", + "stable-hash": "^0.0.4" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4469,9 +4565,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz", - "integrity": "sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz", + "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==", "dev": true, "license": "MIT", "engines": { @@ -4678,14 +4774,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esm-env": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz", - "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/espree": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", @@ -4744,18 +4832,6 @@ "node": ">=0.10" } }, - "node_modules/esrap": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", - "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -4796,6 +4872,29 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-6.0.2.tgz", + "integrity": "sha512-8q3LsZjRezbFZ2PN+uP+Q7pnHUMmAOziU2vA2OwoFaKIXxlxl38IylhSSgUorWu/rf4er67w0ikBqjBFk/pomA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", + "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/exit-hook": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", @@ -5681,17 +5780,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "^1.0.6" - } - }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -5957,6 +6045,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5984,6 +6079,37 @@ "node": ">=6" } }, + "node_modules/jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/jsondiffpatch/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -6273,14 +6399,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -6426,9 +6544,9 @@ } }, "node_modules/miniflare": { - "version": "3.20241106.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20241106.1.tgz", - "integrity": "sha512-dM3RBlJE8rUFxnqlPCaFCq0E7qQqEQvKbYX7W/APGCK+rLcyLmEBzC4GQR/niXdNM/oV6gdg9AA50ghnn2ALuw==", + "version": "3.20241205.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20241205.0.tgz", + "integrity": "sha512-Z0cTtIf6ZrcAJ3SrOI9EUM3s4dkGhNeU6Ubl8sroYhsPVD+rtz3m5+p6McHFWCkcMff1o60X5XEKVTmkz0gbpA==", "dev": true, "license": "MIT", "dependencies": { @@ -6440,7 +6558,7 @@ "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", "undici": "^5.28.4", - "workerd": "1.20241106.1", + "workerd": "1.20241205.0", "ws": "^8.18.0", "youch": "^3.2.2", "zod": "^3.22.3" @@ -6855,6 +6973,48 @@ "node": ">=6" } }, + "node_modules/partyserver": { + "version": "0.0.57", + "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.0.57.tgz", + "integrity": "sha512-AVoNcslX+z8XjcESoNFC0WLYIH0WdxtnbaRKOHqHfzVOmEDvOlh/WUsWxeRY38f5v6aFH/gyUHNcxiW1KXlabw==", + "dev": true, + "license": "ISC", + "dependencies": { + "nanoid": "^5.0.7" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20240729.0" + } + }, + "node_modules/partyserver/node_modules/nanoid": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", + "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/partysocket": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.0.2.tgz", + "integrity": "sha512-rAFOUKImaq+VBk2B+2RTBsWEvlnarEP53nchoUHzpVs8V6fG2/estihOTslTQUWHVuHEKDL5k8htG8K3TngyFA==", + "dev": true, + "license": "ISC", + "dependencies": { + "event-target-shim": "^6.0.2" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7050,9 +7210,9 @@ } }, "node_modules/prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", - "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { @@ -7116,30 +7276,26 @@ "license": "MIT" }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "dev": true, "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "dev": true, "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.25.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } }, "node_modules/react-is": { @@ -7302,16 +7458,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -7493,14 +7639,18 @@ "license": "MIT" }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "license": "MIT" + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/selfsigned": { "version": "2.4.1", @@ -7694,6 +7844,13 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/stable-hash": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", + "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", + "dev": true, + "license": "MIT" + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -7930,99 +8087,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svelte": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.2.10.tgz", - "integrity": "sha512-ON0OyO7vOmSjTc9mLjusu3vf1I7BvjovbiRB7j84F1WZMXV6dR+Tj4btIzxQxMHfzbGskaFmRa7qjgmBSVBnhQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "acorn-typescript": "^1.4.13", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "esm-env": "^1.2.0", - "esrap": "^1.2.2", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/svelte-preprocess": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.3.tgz", - "integrity": "sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.10.2", - "coffeescript": "^2.5.1", - "less": "^3.11.3 || ^4.0.0", - "postcss": "^7 || ^8", - "postcss-load-config": ">=3", - "pug": "^3.0.0", - "sass": "^1.26.8", - "stylus": ">=0.55", - "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.100 || ^5.0.0", - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "coffeescript": { - "optional": true - }, - "less": { - "optional": true - }, - "postcss": { - "optional": true - }, - "postcss-load-config": { - "optional": true - }, - "pug": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/svelte/node_modules/magic-string": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", - "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, "node_modules/sync-content": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/sync-content/-/sync-content-2.0.1.tgz", @@ -8047,9 +8111,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.3.tgz", - "integrity": "sha512-Cem7SF6OYcijYA1ZOGmuZHxk704iOxsRboFCXIpSJSR0XFft/NNz93Yoo0kHZIGNlqZmnQItI7JUSdR++Y9hZQ==", + "version": "4.0.0-beta.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.6.tgz", + "integrity": "sha512-eCCuMk3H65w4J/QWkjxfeWoBSKbCD3E6Uj2LA6Xkkl4eMa1MXuwVpIU1RXcLIp+BVsXGEZMP7i7uJig7KxfAXQ==", "dev": true, "license": "MIT" }, @@ -8076,6 +8140,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -8436,9 +8513,9 @@ }, "node_modules/unenv": { "name": "unenv-nightly", - "version": "2.0.0-20241121-161142-806b5c0", - "resolved": "https://registry.npmjs.org/unenv-nightly/-/unenv-nightly-2.0.0-20241121-161142-806b5c0.tgz", - "integrity": "sha512-RnFOasE/O0Q55gBkNB1b84OgKttgLEijGO0JCWpbn+O4XxpyCQg89NmcqQ5RGUiy4y+rMIrKzePTquQcLQF5pQ==", + "version": "2.0.0-20241204-140205-a5d5190", + "resolved": "https://registry.npmjs.org/unenv-nightly/-/unenv-nightly-2.0.0-20241204-140205-a5d5190.tgz", + "integrity": "sha512-jpmAytLeiiW01pl5bhVn9wYJ4vtiLdhGe10oXlJBuQEX8mxjxO8BlEXGHU4vr4yEikjFP1wsomTHt/CLU8kUwg==", "dev": true, "license": "MIT", "dependencies": { @@ -8499,6 +8576,16 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8507,9 +8594,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.1.tgz", - "integrity": "sha512-Ldn6gorLGr4mCdFnmeAOLweJxZ34HjKnDm4HGo6P66IEqTxQb36VEdFJQENKxWjupNfoIjvRUnswjn1hpYEpjQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz", + "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==", "dev": true, "license": "MIT", "dependencies": { @@ -8579,9 +8666,9 @@ } }, "node_modules/vite-node": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.6.tgz", - "integrity": "sha512-DBfJY0n9JUwnyLxPSSUmEePT21j8JZp/sR9n+/gBwQU6DcQOioPdb8/pibWfXForbirSagZCilseYIwaL3f95A==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", "dev": true, "license": "MIT", "dependencies": { @@ -8589,32 +8676,92 @@ "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", - "vite": "^5.0.0 || ^6.0.0" + "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-node/node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, "node_modules/vitest": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.6.tgz", - "integrity": "sha512-isUCkvPL30J4c5O5hgONeFRsDmlw6kzFEdLQHLezmDdKQHy8Ke/B/dgdTMEgU0vm+iZ0TjW8GuK83DiahBoKWQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.6", - "@vitest/mocker": "2.1.6", - "@vitest/pretty-format": "^2.1.6", - "@vitest/runner": "2.1.6", - "@vitest/snapshot": "2.1.6", - "@vitest/spy": "2.1.6", - "@vitest/utils": "2.1.6", + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", @@ -8625,24 +8772,24 @@ "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", - "vite": "^5.0.0 || ^6.0.0", - "vite-node": "2.1.6", + "vite": "^5.0.0", + "vite-node": "2.1.8", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "2.1.6", - "@vitest/ui": "2.1.6", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", "happy-dom": "*", "jsdom": "*" }, @@ -8667,6 +8814,43 @@ } } }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/vitest/node_modules/magic-string": { "version": "0.30.14", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", @@ -8677,6 +8861,66 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/vitest/node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, "node_modules/walk-up-path": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", @@ -8833,9 +9077,9 @@ } }, "node_modules/workerd": { - "version": "1.20241106.1", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20241106.1.tgz", - "integrity": "sha512-1GdKl0kDw8rrirr/ThcK66Kbl4/jd4h8uHx5g7YHBrnenY5SX1UPuop2cnCzYUxlg55kPjzIqqYslz1muRFgFw==", + "version": "1.20241205.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20241205.0.tgz", + "integrity": "sha512-vso/2n0c5SdBDWiD+Sx5gM7unA6SiZXRVUHDqH1euoP/9mFVHZF8icoYsNLB87b/TX8zNgpae+I5N/xFpd9v0g==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -8846,22 +9090,22 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20241106.1", - "@cloudflare/workerd-darwin-arm64": "1.20241106.1", - "@cloudflare/workerd-linux-64": "1.20241106.1", - "@cloudflare/workerd-linux-arm64": "1.20241106.1", - "@cloudflare/workerd-windows-64": "1.20241106.1" + "@cloudflare/workerd-darwin-64": "1.20241205.0", + "@cloudflare/workerd-darwin-arm64": "1.20241205.0", + "@cloudflare/workerd-linux-64": "1.20241205.0", + "@cloudflare/workerd-linux-arm64": "1.20241205.0", + "@cloudflare/workerd-windows-64": "1.20241205.0" } }, "node_modules/wrangler": { - "version": "3.91.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.91.0.tgz", - "integrity": "sha512-Hdzn6wbY9cz5kL85ZUvWLwLIH7nPaEVRblfms40jhRf4qQO/Zf74aFlku8rQFbe8/2aVZFaxJVfBd6JQMeMSBQ==", + "version": "3.93.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.93.0.tgz", + "integrity": "sha512-+wfxjOrtm6YgDS+NdJkB6aiBIS3ED97mNRQmfrEShRJW4pVo4sWY6oQ1FsGT+j4tGHplrTbWCE6U5yTgjNW/lw==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", - "@cloudflare/workers-shared": "0.9.0", + "@cloudflare/workers-shared": "0.10.0", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "blake3-wasm": "^2.1.5", @@ -8869,15 +9113,14 @@ "date-fns": "^4.1.0", "esbuild": "0.17.19", "itty-time": "^1.0.6", - "miniflare": "3.20241106.1", + "miniflare": "3.20241205.0", "nanoid": "^3.3.3", "path-to-regexp": "^6.3.0", "resolve": "^1.22.8", - "resolve.exports": "^2.0.2", "selfsigned": "^2.0.1", "source-map": "^0.6.1", - "unenv": "npm:unenv-nightly@2.0.0-20241121-161142-806b5c0", - "workerd": "1.20241106.1", + "unenv": "npm:unenv-nightly@2.0.0-20241204-140205-a5d5190", + "workerd": "1.20241205.0", "xxhash-wasm": "^1.0.1" }, "bin": { @@ -8891,7 +9134,7 @@ "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20241106.0" + "@cloudflare/workers-types": "^4.20241205.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -9036,14 +9279,6 @@ "stacktracey": "^2.1.8" } }, - "node_modules/zimmerframe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/zod": { "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", @@ -9053,6 +9288,16 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz", + "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.23.3" + } } } } diff --git a/package.json b/package.json index 0e51c33..00a4193 100644 --- a/package.json +++ b/package.json @@ -32,39 +32,43 @@ "license": "MIT", "description": "Sophisticated scheduler for durable tasks, built on Durable Object Alarms.", "devDependencies": { + "@ai-sdk/openai": "^1.0.7", "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.10", - "@cloudflare/vitest-pool-workers": "^0.5.32", - "@cloudflare/workers-types": "^4.20241127.0", + "@cloudflare/vitest-pool-workers": "^0.5.34", + "@cloudflare/workers-types": "^4.20241205.0", + "@eslint/js": "^9.16.0", "@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", - "@tailwindcss/vite": "^4.0.0-beta.3", + "@tailwindcss/vite": "^4.0.0-beta.6", "@types/bun": "^1.1.14", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.1", + "@typescript-eslint/eslint-plugin": "^8.17.0", + "@typescript-eslint/parser": "^8.17.0", "@vitejs/plugin-react": "^4.3.4", + "ai": "^4.0.13", "concurrently": "^9.1.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "tailwindcss": "^4.0.0-beta.3", - "tshy": "^3.0.2", - "typescript": "^5.7.2", - "vite": "^6.0.1", - "vitest": "2.1.6", - "wrangler": "^3.91.0", - "zod": "^3.23.8", - "@typescript-eslint/eslint-plugin": "^8.16.0", - "@typescript-eslint/parser": "^8.16.0", - "eslint": "^9.15.0", - "@eslint/js": "^9.15.0", + "eslint": "^9.16.0", "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0", - "prettier": "^3.4.1", - "eslint-import-resolver-typescript": "^3.6.1" + "eslint-plugin-react-hooks": "^5.1.0", + "partyserver": "^0.0.57", + "partysocket": "^1.0.2", + "prettier": "^3.4.2", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwindcss": "^4.0.0-beta.6", + "tshy": "^3.0.2", + "typescript": "^5.7.2", + "vite": "^6.0.3", + "vitest": "2.1.8", + "wrangler": "^3.93.0", + "zod": "^3.23.8" }, "overrides": { "esbuild": "0.24.0" diff --git a/src/index.ts b/src/index.ts index 6b0bb8f..f756867 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,33 +3,50 @@ import cronParser from "cron-parser"; export type Task = { id: string; - name?: string | undefined; - payload: Record; + description?: string | undefined; + payload?: Record | undefined; + callback?: Callback | undefined; } & ( - | { + | { time: Date; type: "scheduled"; } - | { - delay: number; + | { + delayInSeconds: number; type: "delayed"; } - | { + | { cron: string; type: "cron"; } - ); + | { + type: "no-schedule"; + } +); -type SqlTask = { +export type SqlTask = { id: string; - name: string | null; - type: string | null; + description: string | null; payload: string | null; + callback: string | null; + type: string | null; time: number | null; - delay: number | null; + delayInSeconds: number | null; cron: string | null; created_at: number | null; -} +}; + +type Callback = + | { + type: "webhook"; + url: string; + } + | { + type: "durable-object"; + namespace: string; + id: string; + function: string; + }; export class Scheduler extends DurableObject { constructor(state: DurableObjectState, env: Env) { @@ -40,11 +57,12 @@ export class Scheduler extends DurableObject { ` CREATE TABLE IF NOT EXISTS tasks ( id TEXT PRIMARY KEY, - name TEXT, - type TEXT NOT NULL, + description TEXT, payload TEXT, + callback TEXT, + type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron', 'no-schedule')), time INTEGER, - delay INTEGER, + delayInSeconds INTEGER, cron TEXT, created_at INTEGER DEFAULT (unixepoch()) ) @@ -64,7 +82,6 @@ export class Scheduler extends DurableObject { }; } - // eslint-disable-next-line @typescript-eslint/require-await async fetch(_request: Request): Promise { return new Response("Hello World!"); } @@ -82,7 +99,9 @@ export class Scheduler extends DurableObject { ORDER BY time ASC LIMIT 1 `; - const { result } = this.querySql([{ sql: query, params: [Math.floor(Date.now() / 1000)] }]) + const { result } = this.querySql([ + { sql: query, params: [Math.floor(Date.now() / 1000)] }, + ]); if (!result) return; if (result.length > 0 && "time" in result[0]) { @@ -93,74 +112,135 @@ export class Scheduler extends DurableObject { async scheduleTask(task: Task): Promise { const { id } = task; - const payload = JSON.stringify(task.payload); if ("time" in task && task.time) { const timestamp = Math.floor(task.time.getTime() / 1000); const query = ` - INSERT OR REPLACE INTO tasks (id, name, type, payload, time) - VALUES (?, ?, 'scheduled', ?, ?) + INSERT OR REPLACE INTO tasks (id, description, payload, callback, type, time) + VALUES (?, ?, ?, ?, 'scheduled', ?) `; - this.querySql([{ sql: query, params: [id, task.name || null, payload, timestamp] }]) + this.querySql([ + { + sql: query, + params: [ + id, + task.description || null, + JSON.stringify(task.payload || null), + JSON.stringify(task.callback || null), + timestamp, + ], + }, + ]); await this.scheduleNextAlarm(); return { id, - name: task.name, + description: task.description, payload: task.payload, + callback: task.callback, time: task.time, type: "scheduled", }; - } else if ("delay" in task && task.delay) { - const time = new Date(Date.now() + task.delay); + } else if ("delayInSeconds" in task && task.delayInSeconds) { + const time = new Date(Date.now() + task.delayInSeconds * 1000); const timestamp = Math.floor(time.getTime() / 1000); const query = ` - INSERT OR REPLACE INTO tasks (id, name, type, payload, delay, time) - VALUES (?, ?, 'delayed', ?, ?, ?) + INSERT OR REPLACE INTO tasks (id, description, payload, callback, type, delayInSeconds, time) + VALUES (?, ?, ?, ?, 'delayed', ?, ?) `; - this.querySql([{ sql: query, params: [id, task.name || null, payload, task.delay, timestamp] }]) + this.querySql([ + { + sql: query, + params: [ + id, + task.description || null, + JSON.stringify(task.payload || null), + JSON.stringify(task.callback || null), + task.delayInSeconds, + timestamp, + ], + }, + ]); await this.scheduleNextAlarm(); return { id, - name: task.name, + description: task.description, payload: task.payload, - delay: task.delay, + callback: task.callback, + delayInSeconds: task.delayInSeconds, type: "delayed", }; } else if ("cron" in task && task.cron) { const nextExecutionTime = this.getNextCronTime(task.cron); const timestamp = Math.floor(nextExecutionTime.getTime() / 1000); const query = ` - INSERT OR REPLACE INTO tasks (id, name, type, payload, cron, time) - VALUES (?, ?, 'cron', ?, ?, ?) + INSERT OR REPLACE INTO tasks (id, description, payload, callback, type, cron, time) + VALUES (?, ?, ?, ?, 'cron', ?, ?) `; - this.querySql([{ sql: query, params: [id, task.name || null, payload, task.cron, timestamp] }]) + this.querySql([ + { + sql: query, + params: [ + id, + task.description || null, + JSON.stringify(task.payload || null), + JSON.stringify(task.callback || null), + task.cron, + timestamp, + ], + }, + ]); await this.scheduleNextAlarm(); return { id, - name: task.name, + description: task.description, payload: task.payload, + callback: task.callback, cron: task.cron, type: "cron", }; - } + } else { + const query = ` + INSERT OR REPLACE INTO tasks (id, description, payload, callback, type) + VALUES (?, ?, ?, ?, 'no-schedule') + `; + this.querySql([ + { + sql: query, + params: [ + id, + task.description || null, + JSON.stringify(task.payload || null), + JSON.stringify(task.callback || null), + ], + }, + ]); - throw new Error("Invalid task configuration"); + return { + id, + description: task.description, + payload: task.payload, + callback: task.callback, + type: "no-schedule", + }; + } } async alarm(): Promise { const now = Math.floor(Date.now() / 1000); // Get all tasks that should be executed now - const { result: tasks } = this.querySql([{ sql: "SELECT * FROM tasks WHERE time <= ?", params: [now] }]) + const { result: tasks } = this.querySql([ + { sql: "SELECT * FROM tasks WHERE time <= ?", params: [now] }, + ]); - for (const row of (tasks || [])) { + for (const row of tasks || []) { const task = this.rowToTask(row); await this.executeTask(task); @@ -169,10 +249,12 @@ export class Scheduler extends DurableObject { const nextExecutionTime = this.getNextCronTime(task.cron); const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1000); - this.querySql([{ sql: "UPDATE tasks SET time = ? WHERE id = ?", params: [nextTimestamp, task.id] }]) + this.querySql([ + { sql: "UPDATE tasks SET time = ? WHERE id = ?", params: [nextTimestamp, task.id] }, + ]); } else { // Delete one-time tasks after execution - this.querySql([{ sql: "DELETE FROM tasks WHERE id = ?", params: [task.id] }]) + this.querySql([{ sql: "DELETE FROM tasks WHERE id = ?", params: [task.id] }]); } } @@ -183,8 +265,9 @@ export class Scheduler extends DurableObject { private rowToTask(row: SqlTask): Task { const base = { id: row.id, - name: row.name, - payload: JSON.parse(row.payload as string) as Record, // TODO: should probably parse/validate this + description: row.description, + payload: row.payload ? (JSON.parse(row.payload) as Record) : undefined, + callback: row.callback ? (JSON.parse(row.callback) as Callback) : undefined, } as Task; switch (row.type) { @@ -197,7 +280,7 @@ export class Scheduler extends DurableObject { case "delayed": return { ...base, - delay: row.delay as number, + delayInSeconds: row.delayInSeconds as number, type: "delayed", }; case "cron": @@ -211,11 +294,29 @@ export class Scheduler extends DurableObject { } } - // eslint-disable-next-line @typescript-eslint/require-await private async executeTask(task: Task): Promise { // This is where you would implement the actual task execution // eslint-disable-next-line no-console console.log(`Executing task ${task.id}:`, task); + if ("callback" in task && task.callback) { + const { type } = task.callback; + if (type === "webhook") { + await fetch(task.callback.url, { + method: "POST", + body: JSON.stringify(task.payload), + headers: { + "Content-Type": "application/json", + }, + }); + } else if (type === "durable-object") { + //@ts-expect-error yeah whatever + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + const stub = this.env[task.callback.namespace].get(task.callback.id); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + await stub[task.callback.function](task.payload); + } + } } private getNextCronTime(cronExpression: string): Date { @@ -223,10 +324,9 @@ export class Scheduler extends DurableObject { return interval.next().toDate(); } - // eslint-disable-next-line @typescript-eslint/require-await async query( criteria: { - name?: string; + description?: string; id?: string; timeRange?: { start?: Date; end?: Date }; } = {} @@ -239,9 +339,9 @@ export class Scheduler extends DurableObject { params.push(criteria.id); } - if (criteria.name) { - query += " AND name = ?"; - params.push(criteria.name); + if (criteria.description) { + query += " AND description = ?"; + params.push(criteria.description); } if (criteria.timeRange) { @@ -251,13 +351,13 @@ export class Scheduler extends DurableObject { params.push(Math.floor(start.getTime() / 1000), Math.floor(end.getTime() / 1000)); } - const { result } = this.querySql([{ sql: query, params }]) + const { result } = this.querySql([{ sql: query, params }]); return result?.map((row) => this.rowToTask(row)) || []; } async cancelTask(id: string): Promise { const query = "DELETE FROM tasks WHERE id = ?"; - this.querySql([{ sql: query, params: [id] }]) + this.querySql([{ sql: query, params: [id] }]); await this.scheduleNextAlarm(); return true; @@ -278,7 +378,6 @@ export class Scheduler extends DurableObject { return { sql, params }; }) || []; - let result: QueryResponse | QueryResponse[]; if (queries.length > 1) { @@ -292,73 +391,74 @@ export class Scheduler extends DurableObject { error: null, status: 200, result: result as T[], - } + }; } catch (error) { return { result: null, error: (error as Error).message ?? "Operation failed.", status: 500, - } + }; } } - private executeTransaction(queries: { sql: string; params?: SqliteParams[] }[], isRaw: boolean): QueryResponse[] { + private executeTransaction( + queries: { sql: string; params?: SqliteParams[] }[], + isRaw: boolean + ): QueryResponse[] { return this.ctx.storage.transactionSync(() => { const results: QueryResponse[] = []; - try { - for (const queryObj of queries) { - const { sql, params } = queryObj; - const result = this.executeQuery(sql, params, isRaw); - results.push(result); - } - - return results; - } catch (error) { - throw error; + for (const queryObj of queries) { + const { sql, params } = queryObj; + const result = this.executeQuery(sql, params, isRaw); + results.push(result); } + + return results; }); } - executeQuery(sql: string, params: SqliteParams[] | undefined, isRaw: boolean): QueryResponse { - try { - const cursor = params?.length ? this.ctx.storage.sql.exec(sql, ...params) : this.ctx.storage.sql.exec(sql); - - let result: QueryResponse; - - if (isRaw) { - result = { - columns: cursor.columnNames, - rows: (cursor.raw() as any).toArray() as T[], - meta: { - rows_read: cursor.rowsRead, - rows_written: cursor.rowsWritten, - }, - }; - } else { - result = cursor.toArray() as T[]; - } - - return result; - } catch (error) { - throw error; + executeQuery( + sql: string, + params: SqliteParams[] | undefined, + isRaw: boolean + ): QueryResponse { + const cursor = params?.length + ? this.ctx.storage.sql.exec(sql, ...params) + : this.ctx.storage.sql.exec(sql); + + let result: QueryResponse; + + if (isRaw) { + result = { + columns: cursor.columnNames, + // @ts-expect-error TODO fix this!!! + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + rows: (cursor.raw() as unknown as T[]).toArray() as T[], + meta: { + rows_read: cursor.rowsRead, + rows_written: cursor.rowsWritten, + }, + }; + } else { + result = cursor.toArray() as T[]; } - } + return result; + } } - export type QueueResult = | { - result: T[]; - error: null; - status: 200; - } + result: T[]; + error: null; + status: 200; + } | { - result: null; - error: string; - status: 500 | 408; - }; + result: null; + error: string; + status: 500 | 408; + }; export type RawSqliteResponse = { columns: string[]; diff --git a/tests/index.test.ts b/tests/index.test.ts index 44dcfe1..fc69b96 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -30,24 +30,33 @@ describe("Hello World worker", () => { const stub = getStub(env); const id = "scheduled-task-001"; const time = new Date(Date.now() + 10000); - // eslint-disable-next-line @typescript-eslint/await-thenable const task = await stub.scheduleTask({ id, - name: "test", + description: "test", + payload: { test: "test" }, + callback: { + type: "webhook", + url: "https://example.com", + }, type: "scheduled", - payload: {}, time, }); expect(task).toMatchInlineSnapshot(` - { - "id": "scheduled-task-001", - "name": "test", - "payload": {}, - "time": ${time.toISOString()}, - "type": "scheduled", - } - `); + { + "callback": { + "type": "webhook", + "url": "https://example.com", + }, + "description": "test", + "id": "scheduled-task-001", + "payload": { + "test": "test", + }, + "time": ${time.toISOString()}, + "type": "scheduled", + } + `); const timestamp = Math.floor(time.getTime() / 1000); const debug = await stub.getAllTasks(); @@ -61,11 +70,12 @@ describe("Hello World worker", () => { expect(rest).toMatchInlineSnapshot(` { + "callback": "{"type":"webhook","url":"https://example.com"}", "cron": null, - "delay": null, + "delayInSeconds": null, + "description": "test", "id": "scheduled-task-001", - "name": "test", - "payload": "{}", + "payload": "{"test":"test"}", "time": ${timestamp}, "type": "scheduled", } @@ -75,30 +85,41 @@ describe("Hello World worker", () => { it("can schedule a delayed task", async () => { const stub = getStub(env); const id = "delayed-task-001"; - const delay = 10000; - const timestamp = new Date().getTime() + delay; - // eslint-disable-next-line @typescript-eslint/await-thenable + const delayInSeconds = 10000; + const task = await stub.scheduleTask({ id, - name: "test", + description: "test", + payload: { test: "test" }, + callback: { + type: "webhook", + url: "https://example.com", + }, type: "delayed", - payload: {}, - delay, + delayInSeconds, }); expect(task).toMatchInlineSnapshot(` - { - "delay": 10000, - "id": "delayed-task-001", - "name": "test", - "payload": {}, - "type": "delayed", - } - `); + { + "callback": { + "type": "webhook", + "url": "https://example.com", + }, + "delayInSeconds": 10000, + "description": "test", + "id": "delayed-task-001", + "payload": { + "test": "test", + }, + "type": "delayed", + } + `); const debug = await stub.getAllTasks(); expect(debug.result).toHaveLength(1); + const timestamp = Math.floor((new Date().getTime() + delayInSeconds * 1000) / 1000); + const { // eslint-disable-next-line @typescript-eslint/no-unused-vars created_at, @@ -107,12 +128,13 @@ describe("Hello World worker", () => { expect(rest).toMatchInlineSnapshot(` { + "callback": "{"type":"webhook","url":"https://example.com"}", "cron": null, - "delay": 10000, + "delayInSeconds": 10000, + "description": "test", "id": "delayed-task-001", - "name": "test", - "payload": "{}", - "time": ${Math.floor(timestamp / 1000)}, + "payload": "{"test":"test"}", + "time": ${timestamp}, "type": "delayed", } `); @@ -125,24 +147,85 @@ describe("Hello World worker", () => { const next = cronParser.parseExpression(cron).next(); const timestamp = Math.floor(next.toDate().getTime() / 1000); - // eslint-disable-next-line @typescript-eslint/await-thenable const task = await stub.scheduleTask({ id, - name: "test", + description: "test", + payload: { test: "test" }, + callback: { + type: "webhook", + url: "https://example.com", + }, type: "cron", - payload: {}, cron, }); expect(task).toMatchInlineSnapshot(` { + "callback": { + "type": "webhook", + "url": "https://example.com", + }, + "cron": "0 0 * * 2", + "description": "test", + "id": "cron-task-001", + "payload": { + "test": "test", + }, + "type": "cron", + } + `); + + const debug = await stub.getAllTasks(); + expect(debug.result).toHaveLength(1); + + const { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + created_at, + ...rest + } = (debug.result || [])[0]; + + expect(rest).toMatchInlineSnapshot(` + { + "callback": "{"type":"webhook","url":"https://example.com"}", "cron": "0 0 * * 2", + "delayInSeconds": null, + "description": "test", "id": "cron-task-001", - "name": "test", - "payload": {}, + "payload": "{"test":"test"}", + "time": ${timestamp}, "type": "cron", } `); + }); + + it("can schedule a no-schedule task", async () => { + const stub = getStub(env); + const id = "no-schedule-task-001"; + const task = await stub.scheduleTask({ + id, + description: "test", + payload: { test: "test" }, + callback: { + type: "webhook", + url: "https://example.com", + }, + type: "no-schedule", + }); + + expect(task).toMatchInlineSnapshot(` + { + "callback": { + "type": "webhook", + "url": "https://example.com", + }, + "description": "test", + "id": "no-schedule-task-001", + "payload": { + "test": "test", + }, + "type": "no-schedule", + } + `); const debug = await stub.getAllTasks(); expect(debug.result).toHaveLength(1); @@ -154,15 +237,16 @@ describe("Hello World worker", () => { } = (debug.result || [])[0]; expect(rest).toMatchInlineSnapshot(` - { - "cron": "0 0 * * 2", - "delay": null, - "id": "cron-task-001", - "name": "test", - "payload": "{}", - "time": ${timestamp}, - "type": "cron", - } - `); + { + "callback": "{"type":"webhook","url":"https://example.com"}", + "cron": null, + "delayInSeconds": null, + "description": "test", + "id": "no-schedule-task-001", + "payload": "{"test":"test"}", + "time": null, + "type": "no-schedule", + } + `); }); });