diff --git a/.changeset/config.json b/.changeset/config.json index 4b700162..ff0a3ea1 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -15,6 +15,7 @@ "@workflow/example-hono", "@workflow/example-nitro-v3", "@workflow/example-nitro-v2", - "@workflow/example-nuxt" + "@workflow/example-nuxt", + "@workflow/example-sveltekit" ] } diff --git a/.changeset/pre.json b/.changeset/pre.json index 4507a2e0..01854a64 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -26,7 +26,8 @@ "nextjs-webpack": "0.0.2-alpha.5", "@workflow/example-nitro-v3": "0.0.0", "@workflow/example-nitro-v2": "0.0.0", - "@workflow/example-nuxt": "0.0.0" + "@workflow/example-nuxt": "0.0.0", + "@workflow/example-sveltekit": "0.0.0" }, "changesets": [ "angry-owls-beg", diff --git a/.changeset/purple-regions-vanish.md b/.changeset/purple-regions-vanish.md new file mode 100644 index 00000000..540aca35 --- /dev/null +++ b/.changeset/purple-regions-vanish.md @@ -0,0 +1,9 @@ +--- +"@workflow/world-local": patch +"@workflow/sveltekit": patch +"workflow": patch +"@workflow/core": patch +"@workflow/cli": patch +--- + +Add sveltekit workflow integration diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f23442bd..5a1f1ac0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,6 +56,8 @@ jobs: project-id: "prj_avRPBF3eWjh6iDNQgmhH4VOg27h0" - name: "nitro" project-id: "prj_e7DZirYdLrQKXNrlxg7KmA6ABx8r" + - name: "sveltekit" + project-id: "prj_MqnBLm71ceXGSnm3Fs8i8gBnI23G" env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} @@ -170,7 +172,7 @@ jobs: run: cd workbench/${{ matrix.app.name }} && ./resolve-symlinks.sh - name: Run E2E Tests (Next.js) - if: matrix.app.name != 'nitro' + if: matrix.app.name != 'nitro' && matrix.app.name != 'sveltekit' run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/next-dev.test.ts && pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} @@ -182,6 +184,12 @@ jobs: env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:3000" + - name: Run E2E Tests (SvelteKit) + if: matrix.app.name == 'sveltekit' + run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/e2e.test.ts + env: + APP_NAME: ${{ matrix.app.name }} + DEPLOYMENT_URL: "http://localhost:3000" e2e-local-prod: name: E2E Local Prod Tests (${{ matrix.app.name }} - ${{ matrix.app.canary && 'canary' || 'stable' }}) diff --git a/docs/app/(home)/components/frameworks.tsx b/docs/app/(home)/components/frameworks.tsx index f5bbbeb4..19e7b68b 100644 --- a/docs/app/(home)/components/frameworks.tsx +++ b/docs/app/(home)/components/frameworks.tsx @@ -485,11 +485,18 @@ export const Frameworks = () => { - - + + + - + + + + + + +
@@ -508,13 +515,6 @@ export const Frameworks = () => {
-
handleRequest('SvelteKit')} - > - - -
handleRequest('Nuxt')} diff --git a/docs/app/docs/[[...slug]]/page.tsx b/docs/app/docs/[[...slug]]/page.tsx index e1426eef..93d9fa41 100644 --- a/docs/app/docs/[[...slug]]/page.tsx +++ b/docs/app/docs/[[...slug]]/page.tsx @@ -34,7 +34,7 @@ function Card({ title, href, className, children, disabled }: CardProps) { diff --git a/docs/content/docs/getting-started/index.mdx b/docs/content/docs/getting-started/index.mdx index 77382758..44822bc0 100644 --- a/docs/content/docs/getting-started/index.mdx +++ b/docs/content/docs/getting-started/index.mdx @@ -25,10 +25,9 @@ Start by choosing your framework. Each guide will walk you through the steps to Nitro - + SvelteKit - Coming soon diff --git a/docs/content/docs/getting-started/sveltekit.mdx b/docs/content/docs/getting-started/sveltekit.mdx new file mode 100644 index 00000000..d64f9ae2 --- /dev/null +++ b/docs/content/docs/getting-started/sveltekit.mdx @@ -0,0 +1,258 @@ +--- +title: SvelteKit +--- + +# SvelteKit + +This guide will walk through setting up your first workflow in a SvelteKit app. Along the way, you'll learn more about the concepts that are fundamental to using the development kit in your own projects. + +--- + + + + +## Create Your SvelteKit Project + +Start by creating a new SvelteKit project. This command will create a new directory named `my-workflow-app` with a minimal setup and setup a SvelteKit project inside it. + +```bash +npx sv create my-workflow-app --template=minimal --types=ts --no-add-ons +``` + +Enter the newly made directory: + +```bash +cd my-workflow-app +``` + +### Install `workflow` + + + + + npm i workflow + + + + + pnpm i workflow + + + + + yarn add workflow + + + + +### Configure Vite + +Add `workflowPlugin()` to your Vite config. This enables usage of the `"use workflow"` and `"use step"` directives. + +```typescript title="vite.config.ts" lineNumbers +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig } from "vite"; +import { workflowPlugin } from "workflow/sveltekit"; // [!code highlight] + +export default defineConfig({ + plugins: [sveltekit(), workflowPlugin()], // [!code highlight] +}); +``` + +### Update `package.json` + +Update your `package.json` to include port `3000` for the development server: + +```json title="package.json" lineNumbers +{ + // ... + "scripts": { + "dev": "vite dev --port 3000" + // ... + }, +} +``` + + + + + ### Setup IntelliSense for TypeScript (Optional) + + + +To enable helpful hints in your IDE, setup the workflow plugin in `tsconfig.json`: + +```json title="tsconfig.json" lineNumbers +{ + "compilerOptions": { + // ... rest of your TypeScript config + "plugins": [ + { + "name": "workflow" // [!code highlight] + } + ] + } +} +``` + + + + + + + + + +## Create Your First Workflow + +Create a new file for our first workflow: + +```typescript title="workflows/user-signup.ts" lineNumbers +import { sleep } from "workflow"; + +export async function handleUserSignup(email: string) { + "use workflow"; // [!code highlight] + + const user = await createUser(email); + await sendWelcomeEmail(user); + + await sleep("5s"); // Pause for 5s - doesn't consume any resources + await sendOnboardingEmail(user); + + return { userId: user.id, status: "onboarded" }; +} + +``` + +We'll fill in those functions next, but let's take a look at this code: + +* We define a **workflow** function with the directive `"use workflow"`. Think of the workflow function as the _orchestrator_ of individual **steps**. +* The Workflow DevKit's `sleep` function allows us to suspend execution of the workflow without using up any resources. A sleep can be a few seconds, hours, days, or even months long. + +## Create Your Workflow Steps + +Let's now define those missing functions. + +```typescript title="workflows/user-signup.ts" lineNumbers +import { FatalError } from "workflow" + +// Our workflow function defined earlier + +async function createUser(email: string) { + "use step"; // [!code highlight] + + console.log(`Creating user with email: ${email}`); + + // Full Node.js access - database calls, APIs, etc. + return { id: crypto.randomUUID(), email }; +} + +async function sendWelcomeEmail(user: { id: string; email: string; }) { + "use step"; // [!code highlight] + + console.log(`Sending welcome email to user: ${user.id}`); + + if (Math.random() < 0.3) { + // By default, steps will be retried for unhandled errors + throw new Error("Retryable!"); + } +} + +async function sendOnboardingEmail(user: { id: string; email: string}) { + "use step"; // [!code highlight] + + if (!user.email.includes("@")) { + // To skip retrying, throw a FatalError instead + throw new FatalError("Invalid Email"); + } + + console.log(`Sending onboarding email to user: ${user.id}`); +} +``` + +Taking a look at this code: + +* Business logic lives inside **steps**. When a step is invoked inside a **workflow**, it gets enqueued to run on a separate request while the workflow is suspended, just like `sleep`. +* If a step throws an error, like in `sendWelcomeEmail`, the step will automatically be retried until it succeeds (or hits the step's max retry count). +* Steps can throw a `FatalError` if an error is intentional and should not be retried. + + +We'll dive deeper into workflows, steps, and other ways to suspend or handle events in [Foundations](/docs/foundations). + + + + + + +## Create Your Route Handler + +To invoke your new workflow, we'll have to add your workflow to a `POST` API route handler, `src/routes/api/signup/+server.ts` with the following code: + +```typescript title="src/routes/api/+server.ts" +import { start } from "workflow/api"; +import { handleUserSignup } from "../../../../workflows/user-signup"; +import { json, type RequestHandler } from "@sveltejs/kit"; + +export const POST: RequestHandler = async ({ + request, +}: { + request: Request; +}) => { + const { email } = await request.json(); + + // Executes asynchronously and doesn't block your app + await start(handleUserSignup, [email]); + + return json({ message: "User signup workflow started" }); +}; + +``` + +This route handler creates a `POST` request endpoint at `/api/signup` that will trigger your workflow. + + +Workflows can be triggered from API routes or any server-side code. + + + + + + +## Run in development + +To start your development server, run the following command in your terminal in the SvelteKit root directory: + +```bash +npm run dev +``` + +Once your development server is running, you can trigger your workflow by running this command in the terminal: + +```bash +curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup +``` + +Check the SvelteKit development server logs to see your workflow execute as well as the steps that are being processed. + +Additionally, you can use the [Workflow DevKit CLI or Web UI](/docs/observability) to inspect your workflow runs and steps in detail. + +```bash +npx workflow inspect runs +# or add '--web' for an interactive Web based UI +``` + +Workflow DevKit Web UI + +--- + +## Deploying to production + +Workflow DevKit apps currently work best when deployed to [Vercel](https://vercel.com/home) and needs no special configuration. + +Check the [Deploying](/docs/deploying) section to learn how your workflows can be deployed elsewhere. + +## Next Steps + +* Learn more about the [Foundations](/docs/foundations). +* Check [Errors](/docs/errors) if you encounter issues. +* Explore the [API Reference](/docs/api-reference). diff --git a/packages/cli/src/lib/builders/base-builder.ts b/packages/cli/src/lib/builders/base-builder.ts index 9ac184ca..422ecdff 100644 --- a/packages/cli/src/lib/builders/base-builder.ts +++ b/packages/cli/src/lib/builders/base-builder.ts @@ -18,6 +18,20 @@ const enhancedResolve = promisify(enhancedResolveOriginal); const EMIT_SOURCEMAPS_FOR_DEBUGGING = process.env.WORKFLOW_EMIT_SOURCEMAPS_FOR_DEBUGGING === '1'; +// Helper function code for converting SvelteKit requests to standard Request objects +const SVELTEKIT_REQUEST_CONVERTER = ` +async function convertSvelteKitRequest(request) { + const options = { + method: request.method, + headers: new Headers(request.headers) + }; + if (!['GET', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'].includes(request.method)) { + options.body = await request.arrayBuffer(); + } + return new Request(request.url, options); +} +`; + export abstract class BaseBuilder { protected config: WorkflowConfig; @@ -254,13 +268,28 @@ export abstract class BaseBuilder { // Create a virtual entry that imports all files. All step definitions // will get registered thanks to the swc transform. const imports = stepFiles.map((file) => `import '${file}';`).join('\n'); - const entryContent = ` + + let entryContent = ` // Built in steps import '${builtInSteps}'; // User steps - ${imports} + ${imports}`; + if (this.config.buildTarget === 'sveltekit') { + entryContent += ` + // API entrypoint + import { stepEntrypoint } from 'workflow/runtime'; + ${SVELTEKIT_REQUEST_CONVERTER} + export const POST = async ({request}) => { + const normalRequest = await convertSvelteKitRequest(request); + return stepEntrypoint(normalRequest); + } + `; + } else { + entryContent += ` // API entrypoint - export { stepEntrypoint as POST } from 'workflow/runtime';`; + export { stepEntrypoint as POST } from 'workflow/runtime'; + `; + } // Bundle with esbuild and our custom SWC plugin const esbuildCtx = await esbuild.context({ @@ -455,14 +484,23 @@ export abstract class BaseBuilder { const bundleFinal = async (interimBundle: string) => { const workflowBundleCode = interimBundle; - // Create the workflow function handler with proper linter suppressions - const workflowFunctionCode = `// biome-ignore-all lint: generated file + let workflowFunctionCode = `// biome-ignore-all lint: generated file /* eslint-disable */ import { workflowEntrypoint } from 'workflow/runtime'; const workflowCode = \`${workflowBundleCode.replace(/[\\`$]/g, '\\$&')}\`; - +`; + if (this.config.buildTarget === 'sveltekit') { + workflowFunctionCode += ` +${SVELTEKIT_REQUEST_CONVERTER} +export const POST = async ({ request }) => { + const normalRequest = await convertSvelteKitRequest(request); + return workflowEntrypoint(workflowCode)(normalRequest); +}`; + } else { + workflowFunctionCode += ` export const POST = workflowEntrypoint(workflowCode);`; + } // we skip the final bundling step for Next.js so it can bundle itself if (!bundleFinalOutput) { @@ -585,7 +623,7 @@ export const POST = workflowEntrypoint(workflowCode);`; // Create a static route that calls resumeWebhook // This route works for both Next.js and Vercel Build Output API - const routeContent = `import { resumeWebhook } from 'workflow/api'; + let routeContent = `import { resumeWebhook } from 'workflow/api'; async function handler(request) { const url = new URL(request.url); @@ -605,8 +643,25 @@ async function handler(request) { console.error('Error during resumeWebhook', error); return new Response(null, { status: 404 }); } -} - +}`; + if (this.config.buildTarget === 'sveltekit') { + routeContent += ` +${SVELTEKIT_REQUEST_CONVERTER} +const createSvelteKitHandler = (method) => async ({ request }) => { + const normalRequest = await convertSvelteKitRequest(request); + return handler(normalRequest); +}; + +export const GET = createSvelteKitHandler('GET'); +export const POST = createSvelteKitHandler('POST'); +export const PUT = createSvelteKitHandler('PUT'); +export const PATCH = createSvelteKitHandler('PATCH'); +export const DELETE = createSvelteKitHandler('DELETE'); +export const HEAD = createSvelteKitHandler('HEAD'); +export const OPTIONS = createSvelteKitHandler('OPTIONS'); +`; + } else { + routeContent += ` export const GET = handler; export const POST = handler; export const PUT = handler; @@ -615,7 +670,7 @@ export const DELETE = handler; export const HEAD = handler; export const OPTIONS = handler; `; - + } if (!bundle) { // For Next.js, just write the unbundled file await writeFile(outfile, routeContent); diff --git a/packages/cli/src/lib/config/types.ts b/packages/cli/src/lib/config/types.ts index 9bdeb712..b4d729c7 100644 --- a/packages/cli/src/lib/config/types.ts +++ b/packages/cli/src/lib/config/types.ts @@ -2,6 +2,7 @@ export const validBuildTargets = [ 'standalone', 'vercel-build-output-api', 'next', + 'sveltekit', ] as const; export type BuildTarget = (typeof validBuildTargets)[number]; diff --git a/packages/core/e2e/local-build.test.ts b/packages/core/e2e/local-build.test.ts index c32a3690..237d6628 100644 --- a/packages/core/e2e/local-build.test.ts +++ b/packages/core/e2e/local-build.test.ts @@ -5,7 +5,7 @@ import { getWorkbenchAppPath } from './utils'; const exec = promisify(execOriginal); -describe.each(['nextjs-webpack', 'nextjs-turbopack', 'nitro'])( +describe.each(['nextjs-webpack', 'nextjs-turbopack', 'nitro', 'sveltekit'])( 'e2e', (project) => { test('builds without errors', { timeout: 180_000 }, async () => { diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 9fa31c92..01bce4bc 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -401,7 +401,11 @@ export function workflowEntrypoint(workflowCode: string) { input: dehydratedArgs as Serializable[], }); - waitUntil(Promise.all(ops)); + waitUntil( + Promise.all(ops).catch((err) => { + console.error('Error waiting for ops', err); + }) + ); await world.queue( `__wkf_step_${queueItem.stepName}`, @@ -680,7 +684,11 @@ export const stepEntrypoint = result = dehydrateStepReturnValue(result, ops); - waitUntil(Promise.all(ops)); + waitUntil( + Promise.all(ops).catch((err) => { + console.error('Error waiting for ops', err); + }) + ); // Update the event log with the step result await world.events.create(workflowRunId, { diff --git a/packages/core/src/runtime/resume-hook.ts b/packages/core/src/runtime/resume-hook.ts index aa6401aa..119ed71c 100644 --- a/packages/core/src/runtime/resume-hook.ts +++ b/packages/core/src/runtime/resume-hook.ts @@ -78,7 +78,11 @@ export async function resumeHook( ops, globalThis ); - waitUntil(Promise.all(ops)); + waitUntil( + Promise.all(ops).catch((err) => { + console.error('Error waiting for ops', err); + }) + ); // Create a hook_received event with the payload await world.events.create(hook.runId, { diff --git a/packages/core/src/runtime/start.ts b/packages/core/src/runtime/start.ts index ee61093d..56e379cb 100644 --- a/packages/core/src/runtime/start.ts +++ b/packages/core/src/runtime/start.ts @@ -91,7 +91,11 @@ export async function start( input: workflowArguments, executionContext: { traceCarrier }, }); - waitUntil(Promise.all(ops)); + waitUntil( + Promise.all(ops).catch((err) => { + console.error('Error waiting for ops', err); + }) + ); span?.setAttributes({ ...Attribute.WorkflowRunId(runResponse.runId), diff --git a/packages/sveltekit/LICENSE.md b/packages/sveltekit/LICENSE.md new file mode 120000 index 00000000..f0608a63 --- /dev/null +++ b/packages/sveltekit/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/packages/sveltekit/README.md b/packages/sveltekit/README.md new file mode 100644 index 00000000..88571598 --- /dev/null +++ b/packages/sveltekit/README.md @@ -0,0 +1,3 @@ +# workflow/sveltekit + +The docs have moved! Refer to them [here](https://useworkflow.dev/) diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json new file mode 100644 index 00000000..47477052 --- /dev/null +++ b/packages/sveltekit/package.json @@ -0,0 +1,39 @@ +{ + "name": "@workflow/sveltekit", + "version": "4.0.0-beta.1", + "description": "SvelteKit integration for Workflow DevKit", + "type": "module", + "main": "dist/index.js", + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/vercel/workflow.git", + "directory": "packages/sveltekit" + }, + "exports": { + ".": "./dist/index.js" + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "clean": "tsc --build --clean && rm -rf dist" + }, + "dependencies": { + "@sveltejs/kit": "^2.48.2", + "@workflow/cli": "workspace:*", + "fs-extra": "^11.3.2", + "@workflow/nitro": "workspace:*" + }, + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/node": "catalog:", + "@workflow/tsconfig": "workspace:*", + "vite": "^7.1.11" + } +} diff --git a/packages/sveltekit/src/builders.ts b/packages/sveltekit/src/builders.ts new file mode 100644 index 00000000..bea422bb --- /dev/null +++ b/packages/sveltekit/src/builders.ts @@ -0,0 +1,151 @@ +import { constants } from 'node:fs'; +import { access, mkdir, stat, writeFile } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; +import { BaseBuilder } from '@workflow/cli/dist/lib/builders/base-builder.js'; +import type { WorkflowConfig } from '@workflow/cli/dist/lib/config/types.js'; + +const CommonBuildOptions = { + dirs: ['workflows', 'src/workflows'], + buildTarget: 'sveltekit' as const, + stepsBundlePath: '', // unused in base + workflowsBundlePath: '', // unused in base + webhookBundlePath: '', // unused in base +}; + +export class LocalBuilder extends BaseBuilder { + constructor(config?: Partial) { + super({ + ...CommonBuildOptions, + ...config, + workingDir: config?.workingDir || process.cwd(), + }); + } + + override async build(): Promise { + // Find SvelteKit routes directory (src/routes or routes) + const routesDir = await this.findRoutesDirectory(); + const workflowGeneratedDir = join(routesDir, '.well-known/workflow/v1'); + + // Ensure output directories exist + await mkdir(workflowGeneratedDir, { recursive: true }); + + // Add .gitignore to exclude generated files from version control + if (process.env.VERCEL_DEPLOYMENT_ID === undefined) { + await writeFile(join(workflowGeneratedDir, '.gitignore'), '*'); + } + + // Get workflow and step files to bundle + const inputFiles = await this.getInputFiles(); + const tsConfig = await this.getTsConfigOptions(); + + const options = { + inputFiles, + workflowGeneratedDir, + tsBaseUrl: tsConfig.baseUrl, + tsPaths: tsConfig.paths, + }; + + // Generate the three SvelteKit route handlers + await this.buildStepsRoute(options); + await this.buildWorkflowsRoute(options); + await this.buildWebhookRoute({ workflowGeneratedDir }); + } + + private async buildStepsRoute({ + inputFiles, + workflowGeneratedDir, + tsPaths, + tsBaseUrl, + }: { + inputFiles: string[]; + workflowGeneratedDir: string; + tsBaseUrl?: string; + tsPaths?: Record; + }) { + // Create steps route: .well-known/workflow/v1/step/+server.js + const stepsRouteDir = join(workflowGeneratedDir, 'step'); + await mkdir(stepsRouteDir, { recursive: true }); + + return await this.createStepsBundle({ + format: 'esm', + inputFiles, + outfile: join(stepsRouteDir, '+server.js'), + externalizeNonSteps: true, + tsBaseUrl, + tsPaths, + }); + } + + private async buildWorkflowsRoute({ + inputFiles, + workflowGeneratedDir, + tsPaths, + tsBaseUrl, + }: { + inputFiles: string[]; + workflowGeneratedDir: string; + tsBaseUrl?: string; + tsPaths?: Record; + }) { + // Create workflows route: .well-known/workflow/v1/flow/+server.js + const workflowsRouteDir = join(workflowGeneratedDir, 'flow'); + await mkdir(workflowsRouteDir, { recursive: true }); + + return await this.createWorkflowsBundle({ + format: 'esm', + outfile: join(workflowsRouteDir, '+server.js'), + bundleFinalOutput: false, + inputFiles, + tsBaseUrl, + tsPaths, + }); + } + + private async buildWebhookRoute({ + workflowGeneratedDir, + }: { + workflowGeneratedDir: string; + }) { + // Create webhook route: .well-known/workflow/v1/webhook/[token]/+server.js + const webhookRouteFile = join( + workflowGeneratedDir, + 'webhook/[token]/+server.js' + ); + + return await this.createWebhookBundle({ + outfile: webhookRouteFile, + bundle: false, // SvelteKit will handle bundling + }); + } + + private async findRoutesDirectory(): Promise { + const routesDir = resolve(this.config.workingDir, 'src/routes'); + const rootRoutesDir = resolve(this.config.workingDir, 'routes'); + + // Try src/routes first (standard SvelteKit convention) + try { + await access(routesDir, constants.F_OK); + const routesStats = await stat(routesDir); + if (!routesStats.isDirectory()) { + throw new Error(`Path exists but is not a directory: ${routesDir}`); + } + return routesDir; + } catch { + // Try routes as fallback + try { + await access(rootRoutesDir, constants.F_OK); + const rootRoutesStats = await stat(rootRoutesDir); + if (!rootRoutesStats.isDirectory()) { + throw new Error( + `Path exists but is not a directory: ${rootRoutesDir}` + ); + } + return rootRoutesDir; + } catch { + throw new Error( + 'Could not find SvelteKit routes directory. Expected either "src/routes" or "routes" to exist.' + ); + } + } + } +} diff --git a/packages/sveltekit/src/index.ts b/packages/sveltekit/src/index.ts new file mode 100644 index 00000000..df9b09ba --- /dev/null +++ b/packages/sveltekit/src/index.ts @@ -0,0 +1,78 @@ +import path from 'node:path'; +import fs from 'fs-extra'; +import { LocalBuilder } from './builders.js'; + +const localBuilder = new LocalBuilder(); + +// This needs to be in the top-level as we need to create these +// entries before svelte plugin is started or the entries are +// a race to be created before svelte discovers entries +await localBuilder.build(); + +process.on('beforeExit', () => { + // Don't patch functions output if not in Vercel adapter + if (!process.env.VERCEL_DEPLOYMENT_ID) { + return; + } + for (const { file, config } of [ + { + file: '.vercel/output/functions/.well-known/workflow/v1/flow.func/.vc-config.json', + config: { + experimentalTriggers: [ + { + type: 'queue/v1beta', + topic: '__wkf_workflow_*', + consumer: 'default', + maxDeliveries: 64, + retryAfterSeconds: 5, + initialDelaySeconds: 0, + }, + ], + }, + }, + { + file: '.vercel/output/functions/.well-known/workflow/v1/step.func/.vc-config.json', + config: { + experimentalTriggers: [ + { + type: 'queue/v1beta', + topic: '__wkf_step_*', + consumer: 'default', + maxDeliveries: 64, + retryAfterSeconds: 5, + initialDelaySeconds: 0, + }, + ], + }, + }, + ]) { + // Un-symlink these as they can't be shared due to different + // experimental triggers config + const toCopy = fs.readdirSync(path.dirname(file)); + fs.removeSync(path.dirname(file)); + fs.mkdirSync(path.dirname(file), { recursive: true }); + + for (const item of toCopy) { + fs.copySync( + path.join( + path.dirname(file).replace(/\.func$/, ''), + '__data.json.func', + item + ), + path.join(path.dirname(file), item) + ); + } + + // Update .vc-config.json with the new experimental triggers config + const existingConfig = JSON.parse(fs.readFileSync(file, 'utf8')); + fs.writeFileSync( + file, + JSON.stringify({ + ...existingConfig, + ...config, + }) + ); + } +}); + +export { workflowRollupPlugin as workflowPlugin } from '@workflow/nitro/rollup-plugin'; diff --git a/packages/sveltekit/tsconfig.json b/packages/sveltekit/tsconfig.json new file mode 100644 index 00000000..ba5d9aec --- /dev/null +++ b/packages/sveltekit/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@workflow/tsconfig/base.json", + "compilerOptions": { + "outDir": "dist", + "target": "es2022", + "module": "preserve", + "baseUrl": ".", + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["node_modules", "**/*.test.ts"] +} diff --git a/packages/workflow/package.json b/packages/workflow/package.json index de26b012..8d6a49f3 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -34,6 +34,7 @@ "./internal/private": "./dist/internal/private.js", "./next": "./dist/next.js", "./nitro": "./dist/nitro.js", + "./sveltekit": "./dist/sveltekit.js", "./rollup-plugin": "./dist/rollup-plugin.js", "./runtime": "./dist/runtime.js" }, @@ -51,7 +52,8 @@ "@workflow/typescript-plugin": "workspace:*", "ms": "2.1.3", "@workflow/next": "workspace:*", - "@workflow/nitro": "workspace:*" + "@workflow/nitro": "workspace:*", + "@workflow/sveltekit": "workspace:*" }, "devDependencies": { "@types/ms": "^2.1.0", diff --git a/packages/workflow/src/sveltekit.ts b/packages/workflow/src/sveltekit.ts new file mode 100644 index 00000000..7de66f9c --- /dev/null +++ b/packages/workflow/src/sveltekit.ts @@ -0,0 +1 @@ +export * from '@workflow/sveltekit'; diff --git a/packages/world-local/src/queue.ts b/packages/world-local/src/queue.ts index 93ce627d..2b02973c 100644 --- a/packages/world-local/src/queue.ts +++ b/packages/world-local/src/queue.ts @@ -59,6 +59,7 @@ export function createQueue(port?: number): Queue { method: 'POST', duplex: 'half', headers: { + 'content-type': 'application/json', 'x-vqs-queue-name': queueName, 'x-vqs-message-id': messageId, 'x-vqs-message-attempt': String(attempt + 1), @@ -115,7 +116,11 @@ export function createQueue(port?: number): Queue { if (!headers.success || !req.body) { return Response.json( - { error: 'Missing required headers' }, + { + error: !req.body + ? 'Missing request body' + : 'Missing required headers', + }, { status: 400 } ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3177f9c9..41ff4110 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,7 +106,7 @@ importers: version: 19.1.9(@types/react@19.1.13) '@vercel/analytics': specifier: ^1.5.0 - version: 1.5.0(next@15.6.0-canary.13(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(vue-router@4.6.3(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)) + version: 1.5.0(@sveltejs/kit@2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(next@15.6.0-canary.13(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(svelte@5.43.0)(vue-router@4.6.3(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)) '@vercel/edge-config': specifier: ^1.4.0 version: 1.4.0(@opentelemetry/api@1.9.0) @@ -508,6 +508,34 @@ importers: specifier: latest version: 3.0.1-alpha.0(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(better-sqlite3@11.10.0)(chokidar@4.0.3)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0))(ioredis@5.8.2)(lru-cache@11.2.2)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + packages/sveltekit: + dependencies: + '@sveltejs/kit': + specifier: ^2.48.2 + version: 2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + '@workflow/cli': + specifier: workspace:* + version: link:../cli + '@workflow/nitro': + specifier: workspace:* + version: link:../nitro + fs-extra: + specifier: ^11.3.2 + version: 11.3.2 + devDependencies: + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 + '@types/node': + specifier: 'catalog:' + version: 24.6.2 + '@workflow/tsconfig': + specifier: workspace:* + version: link:../tsconfig + vite: + specifier: ^7.1.11 + version: 7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0) + packages/swc-plugin-workflow: dependencies: '@swc/core': @@ -704,6 +732,9 @@ importers: '@workflow/nitro': specifier: workspace:* version: link:../nitro + '@workflow/sveltekit': + specifier: workspace:* + version: link:../sveltekit '@workflow/typescript-plugin': specifier: workspace:* version: link:../typescript-plugin @@ -1175,6 +1206,67 @@ importers: specifier: 'catalog:' version: 4.1.11 + workbench/sveltekit: + dependencies: + '@ai-sdk/react': + specifier: 2.0.76 + version: 2.0.76(react@19.2.0)(zod@4.1.11) + '@node-rs/xxhash': + specifier: 1.7.6 + version: 1.7.6 + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@sveltejs/adapter-node': + specifier: ^5.4.0 + version: 5.4.0(@sveltejs/kit@2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0))) + '@sveltejs/adapter-vercel': + specifier: ^6.1.1 + version: 6.1.1(@sveltejs/kit@2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(rollup@4.52.5) + '@vercel/otel': + specifier: ^1.13.0 + version: 1.13.0(@opentelemetry/api-logs@0.57.2)(@opentelemetry/api@1.9.0)(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)) + '@workflow/ai': + specifier: workspace:* + version: link:../../packages/ai + ai: + specifier: 'catalog:' + version: 5.0.76(zod@4.1.11) + lodash.chunk: + specifier: ^4.2.0 + version: 4.2.0 + workflow: + specifier: workspace:* + version: link:../../packages/workflow + zod: + specifier: 'catalog:' + version: 4.1.11 + devDependencies: + '@sveltejs/kit': + specifier: ^2.43.2 + version: 2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.0 + version: 6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + '@types/lodash.chunk': + specifier: ^4.2.9 + version: 4.2.9 + svelte: + specifier: ^5.39.5 + version: 5.43.0 + svelte-check: + specifier: ^4.3.2 + version: 4.3.3(picomatch@4.0.3)(svelte@5.43.0)(typescript@5.9.3) + typescript: + specifier: ^5.9.2 + version: 5.9.3 + vite: + specifier: ^7.1.7 + version: 7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0) + vite-plugin-devtools-json: + specifier: ^1.0.0 + version: 1.0.0(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + packages: '@ai-sdk/gateway@2.0.0': @@ -4269,6 +4361,50 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@sveltejs/acorn-typescript@1.0.6': + resolution: {integrity: sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-node@5.4.0': + resolution: {integrity: sha512-NMsrwGVPEn+J73zH83Uhss/hYYZN6zT3u31R3IHAn3MiKC3h8fjmIAhLfTSOeNHr5wPYfjjMg8E+1gyFgyrEcQ==} + peerDependencies: + '@sveltejs/kit': ^2.4.0 + + '@sveltejs/adapter-vercel@6.1.1': + resolution: {integrity: sha512-rnuREIO/dBYZn825aXTmdCU7sBr4nQqxNVkqYLUHoUnuv3vaas6O/SxAI1TiYBDEetgeQ5m51I5dTVJvhVzASA==} + engines: {node: '>=20.0'} + peerDependencies: + '@sveltejs/kit': ^2.4.0 + + '@sveltejs/kit@2.48.2': + resolution: {integrity: sha512-WIgVMGt+b9OxPDtu0Txow28RsBrLoV3wr2QoUxEHd4CHbpxbqKQf2SIEzd+SE+bqrUL2D5MxBeQBdY+t7o6n1A==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + + '@sveltejs/vite-plugin-svelte-inspector@5.0.1': + resolution: {integrity: sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@sveltejs/vite-plugin-svelte@6.2.1': + resolution: {integrity: sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + '@swc/core-darwin-arm64@1.11.24': resolution: {integrity: sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==} engines: {node: '>=10'} @@ -4447,6 +4583,9 @@ packages: '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -4558,6 +4697,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -4570,6 +4712,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/jsonlines@0.1.5': resolution: {integrity: sha512-/zOl7I350g4/G6fEW9dktpTrkcKqZDMRkr2SuDla0utgwkUXrm7OFXq2WZT0W9Jl7BYoisGbn1EZsV/Z2F9LGg==} @@ -4965,6 +5110,10 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} @@ -5010,6 +5159,10 @@ packages: peerDependencies: postcss: ^8.1.0 + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + b4a@1.7.3: resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} peerDependencies: @@ -5376,6 +5529,10 @@ packages: cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} @@ -6049,6 +6206,9 @@ packages: jiti: optional: true + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6062,6 +6222,9 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} + esrap@2.1.1: + resolution: {integrity: sha512-ebTT9B6lOtZGMgJ3o5r12wBacHctG7oEWazIda8UlPfA3HD/Wrv8FdXoVo73vzdpwCxNyXjPauyN2bbJzMkB9A==} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -6274,6 +6437,10 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + engines: {node: '>=14.14'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -6733,6 +6900,9 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-ssh@1.4.1: resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} @@ -6861,6 +7031,9 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonlines@0.1.1: resolution: {integrity: sha512-ekDrAGso79Cvf+dtm+mL8OBI2bmAOt3gssYs833De/C9NmIpWDWyUO4zPgB5x2/OhY366dkhgfPMYfwZF7yOZA==} @@ -7002,6 +7175,9 @@ packages: resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -8521,6 +8697,10 @@ packages: rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -8584,6 +8764,9 @@ packages: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -8849,6 +9032,18 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte-check@4.3.3: + resolution: {integrity: sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte@5.43.0: + resolution: {integrity: sha512-1sRxVbgJAB+UGzwkc3GUoiBSzEOf0jqzccMaVoI2+pI+kASUe9qubslxace8+Mzhqw19k4syTA5niCIJwfXpOA==} + engines: {node: '>=18'} + svgo@4.0.0: resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} engines: {node: '>=16'} @@ -9140,6 +9335,10 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unplugin-utils@0.2.5: resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==} engines: {node: '>=18.12.0'} @@ -9423,6 +9622,11 @@ packages: vue-tsc: optional: true + vite-plugin-devtools-json@1.0.0: + resolution: {integrity: sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw==} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vite-plugin-inspect@11.3.3: resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==} engines: {node: '>=14'} @@ -9479,6 +9683,14 @@ packages: yaml: optional: true + vitefu@1.1.1: + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -9680,6 +9892,9 @@ packages: youch@4.1.0-beta.11: resolution: {integrity: sha512-sQi6PERyO/mT8w564ojOVeAlYTtVQmC2GaktQAf+IdI75/GKIggosBuvyVXvEV+FATAT6RbLdIjFoiIId4ozoQ==} + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -9723,6 +9938,16 @@ snapshots: optionalDependencies: zod: 4.1.11 + '@ai-sdk/react@2.0.76(react@19.2.0)(zod@4.1.11)': + dependencies: + '@ai-sdk/provider-utils': 3.0.12(zod@4.1.11) + ai: 5.0.76(zod@4.1.11) + react: 19.2.0 + swr: 2.3.6(react@19.2.0) + throttleit: 2.1.0 + optionalDependencies: + zod: 4.1.11 + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -13344,6 +13569,70 @@ snapshots: '@standard-schema/utils@0.3.0': {} + '@sveltejs/acorn-typescript@1.0.6(acorn@8.15.0)': + dependencies: + acorn: 8.15.0 + + '@sveltejs/adapter-node@5.4.0(@sveltejs/kit@2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))': + dependencies: + '@rollup/plugin-commonjs': 28.0.8(rollup@4.52.5) + '@rollup/plugin-json': 6.1.0(rollup@4.52.5) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.52.5) + '@sveltejs/kit': 2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + rollup: 4.52.5 + + '@sveltejs/adapter-vercel@6.1.1(@sveltejs/kit@2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(rollup@4.52.5)': + dependencies: + '@sveltejs/kit': 2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + '@vercel/nft': 0.30.3(rollup@4.52.5) + esbuild: 0.25.11 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@sveltejs/kit@2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0))': + dependencies: + '@standard-schema/spec': 1.0.0 + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + '@types/cookie': 0.6.0 + acorn: 8.15.0 + cookie: 0.6.0 + devalue: 5.4.1 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.19 + mrmime: 2.0.1 + sade: 1.8.1 + set-cookie-parser: 2.7.2 + sirv: 3.0.2 + svelte: 5.43.0 + vite: 7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0) + optionalDependencies: + '@opentelemetry/api': 1.9.0 + + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0))': + dependencies: + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + debug: 4.4.3(supports-color@8.1.1) + svelte: 5.43.0 + vite: 7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + debug: 4.4.3(supports-color@8.1.1) + deepmerge: 4.3.1 + magic-string: 0.30.19 + svelte: 5.43.0 + vite: 7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0) + vitefu: 1.1.1(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + transitivePeerDependencies: + - supports-color + '@swc/core-darwin-arm64@1.11.24': optional: true @@ -13496,6 +13785,8 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 + '@types/cookie@0.6.0': {} + '@types/d3-array@3.2.2': {} '@types/d3-axis@3.0.6': @@ -13636,6 +13927,11 @@ snapshots: '@types/estree@1.0.8': {} + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 24.6.2 + '@types/geojson@7946.0.16': {} '@types/graceful-fs@4.1.9': @@ -13649,6 +13945,10 @@ snapshots: '@types/json-schema@7.0.15': optional: true + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 24.6.2 + '@types/jsonlines@0.1.5': dependencies: '@types/node': 24.6.2 @@ -13732,10 +14032,12 @@ snapshots: unhead: 2.0.19 vue: 3.5.22(typescript@5.9.3) - '@vercel/analytics@1.5.0(next@15.6.0-canary.13(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(vue-router@4.6.3(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))': + '@vercel/analytics@1.5.0(@sveltejs/kit@2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(next@15.6.0-canary.13(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(svelte@5.43.0)(vue-router@4.6.3(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))': optionalDependencies: + '@sveltejs/kit': 2.48.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.0)(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) next: 15.6.0-canary.13(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 + svelte: 5.43.0 vue: 3.5.22(typescript@5.9.3) vue-router: 4.6.3(vue@3.5.22(typescript@5.9.3)) @@ -14119,6 +14421,8 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.2: {} + array-timsort@1.0.3: {} array-union@2.1.0: {} @@ -14163,6 +14467,8 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 + axobject-query@4.1.0: {} + b4a@1.7.3: {} bail@2.0.2: {} @@ -14528,6 +14834,8 @@ snapshots: cookie-es@2.0.0: {} + cookie@0.6.0: {} + cookie@1.0.2: {} copy-anything@4.0.5: @@ -15232,6 +15540,8 @@ snapshots: - supports-color optional: true + esm-env@1.2.2: {} + espree@10.4.0: dependencies: acorn: 8.15.0 @@ -15246,6 +15556,10 @@ snapshots: estraverse: 5.3.0 optional: true + esrap@2.1.1: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -15468,6 +15782,12 @@ snapshots: fs-constants@1.0.0: {} + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -15974,6 +16294,10 @@ snapshots: dependencies: '@types/estree': 1.0.8 + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + is-ssh@1.4.1: dependencies: protocols: 2.0.2 @@ -16084,6 +16408,12 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jsonlines@0.1.1: {} katex@0.16.25: @@ -16234,6 +16564,8 @@ snapshots: pkg-types: 2.3.0 quansync: 0.2.11 + locate-character@3.0.0: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -18486,6 +18818,10 @@ snapshots: rw@1.3.3: {} + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -18554,6 +18890,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.2: {} + setprototypeof@1.2.0: {} sharp@0.34.4: @@ -18842,6 +19180,35 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svelte-check@4.3.3(picomatch@4.0.3)(svelte@5.43.0)(typescript@5.9.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + chokidar: 4.0.3 + fdir: 6.5.0(picomatch@4.0.3) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.43.0 + typescript: 5.9.3 + transitivePeerDependencies: + - picomatch + + svelte@5.43.0: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) + '@types/estree': 1.0.8 + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + esm-env: 1.2.2 + esrap: 2.1.1 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.19 + zimmerframe: 1.1.4 + svgo@4.0.0: dependencies: commander: 11.1.0 @@ -18864,6 +19231,12 @@ snapshots: react: 19.1.1 use-sync-external-store: 1.5.0(react@19.1.1) + swr@2.3.6(react@19.2.0): + dependencies: + dequal: 2.0.3 + react: 19.2.0 + use-sync-external-store: 1.5.0(react@19.2.0) + system-architecture@0.1.0: {} tagged-tag@1.0.0: {} @@ -19181,6 +19554,8 @@ snapshots: universalify@0.1.2: {} + universalify@2.0.1: {} + unplugin-utils@0.2.5: dependencies: pathe: 2.0.3 @@ -19321,6 +19696,10 @@ snapshots: dependencies: react: 19.1.1 + use-sync-external-store@1.5.0(react@19.2.0): + dependencies: + react: 19.2.0 + util-deprecate@1.0.2: {} uuid@10.0.0: {} @@ -19411,6 +19790,11 @@ snapshots: optionator: 0.9.4 typescript: 5.9.3 + vite-plugin-devtools-json@1.0.0(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)): + dependencies: + uuid: 11.1.0 + vite: 7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0) + vite-plugin-inspect@11.3.3(@nuxt/kit@3.19.3(magicast@0.3.5))(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)): dependencies: ansis: 4.2.0 @@ -19455,6 +19839,10 @@ snapshots: tsx: 4.20.6 yaml: 2.8.0 + vitefu@1.1.1(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)): + optionalDependencies: + vite: 7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0) + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 @@ -19659,6 +20047,8 @@ snapshots: cookie: 1.0.2 youch-core: 0.3.3 + zimmerframe@1.1.4: {} + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f6fe1858..99e01852 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,3 +14,5 @@ catalog: typescript: ^5.9.3 vitest: ^3.2.4 zod: 4.1.11 +onlyBuiltDependencies: + - esbuild diff --git a/scripts/create-test-matrix.mjs b/scripts/create-test-matrix.mjs index bb5731fd..007fd759 100644 --- a/scripts/create-test-matrix.mjs +++ b/scripts/create-test-matrix.mjs @@ -26,4 +26,9 @@ matrix.app.push({ project: 'workbench-nitro-workflow', }); +matrix.app.push({ + name: 'sveltekit', + project: 'workbench-sveltekit-workflow', +}); + console.log(JSON.stringify(matrix)); diff --git a/turbo.json b/turbo.json index ca0f500f..3b5c8147 100644 --- a/turbo.json +++ b/turbo.json @@ -4,17 +4,19 @@ "tasks": { "build": { "dependsOn": ["^build"], - "outputs": ["dist/**"] + "outputs": ["dist/**", "build", ".svelte-kit", ".vercel/output"] }, "dev": { "dependsOn": ["^build"], - "outputs": ["dist/**"] + "outputs": ["dist/**", "build", ".svelte-kit", ".vercel/output"] }, "typecheck": { - "dependsOn": ["^build"] + "dependsOn": ["^build"], + "outputs": ["dist/**", "build", ".svelte-kit", ".vercel/output"] }, "test": { - "dependsOn": ["^build"] + "dependsOn": ["^build"], + "outputs": ["dist/**", "build", ".svelte-kit", ".vercel/output"] }, "clean": { "cache": false diff --git a/workbench/sveltekit/.gitignore b/workbench/sveltekit/.gitignore new file mode 100644 index 00000000..3b462cb0 --- /dev/null +++ b/workbench/sveltekit/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/workbench/sveltekit/.npmrc b/workbench/sveltekit/.npmrc new file mode 100644 index 00000000..b6f27f13 --- /dev/null +++ b/workbench/sveltekit/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/workbench/sveltekit/README.md b/workbench/sveltekit/README.md new file mode 100644 index 00000000..75842c40 --- /dev/null +++ b/workbench/sveltekit/README.md @@ -0,0 +1,38 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/workbench/sveltekit/package.json b/workbench/sveltekit/package.json new file mode 100644 index 00000000..a901376e --- /dev/null +++ b/workbench/sveltekit/package.json @@ -0,0 +1,37 @@ +{ + "name": "@workflow/example-sveltekit", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite dev --port 3000", + "build": "vite build", + "start": "vite preview --port 3000", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "devDependencies": { + "@sveltejs/kit": "^2.43.2", + "@sveltejs/vite-plugin-svelte": "^6.2.0", + "@types/lodash.chunk": "^4.2.9", + "svelte": "^5.39.5", + "svelte-check": "^4.3.2", + "typescript": "^5.9.2", + "vite": "^7.1.7", + "vite-plugin-devtools-json": "^1.0.0" + }, + "dependencies": { + "@ai-sdk/react": "2.0.76", + "@node-rs/xxhash": "1.7.6", + "@opentelemetry/api": "^1.9.0", + "@sveltejs/adapter-node": "^5.4.0", + "@sveltejs/adapter-vercel": "^6.1.1", + "@vercel/otel": "^1.13.0", + "@workflow/ai": "workspace:*", + "ai": "catalog:", + "lodash.chunk": "^4.2.0", + "workflow": "workspace:*", + "zod": "catalog:" + } +} diff --git a/workbench/sveltekit/src/app.d.ts b/workbench/sveltekit/src/app.d.ts new file mode 100644 index 00000000..520c4217 --- /dev/null +++ b/workbench/sveltekit/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/workbench/sveltekit/src/app.html b/workbench/sveltekit/src/app.html new file mode 100644 index 00000000..44e6779c --- /dev/null +++ b/workbench/sveltekit/src/app.html @@ -0,0 +1,72 @@ + + + + + + Workflows DevKit + SvelteKit Example + + %sveltekit.head% + + + + %sveltekit.body% + + + diff --git a/workbench/sveltekit/src/lib/assets/favicon.svg b/workbench/sveltekit/src/lib/assets/favicon.svg new file mode 100644 index 00000000..cc5dc66a --- /dev/null +++ b/workbench/sveltekit/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/workbench/sveltekit/src/lib/index.ts b/workbench/sveltekit/src/lib/index.ts new file mode 100644 index 00000000..856f2b6c --- /dev/null +++ b/workbench/sveltekit/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/workbench/sveltekit/src/routes/+layout.svelte b/workbench/sveltekit/src/routes/+layout.svelte new file mode 100644 index 00000000..20f8d044 --- /dev/null +++ b/workbench/sveltekit/src/routes/+layout.svelte @@ -0,0 +1,11 @@ + + + + + + +{@render children?.()} diff --git a/workbench/sveltekit/src/routes/+page.svelte b/workbench/sveltekit/src/routes/+page.svelte new file mode 100644 index 00000000..8cf31343 --- /dev/null +++ b/workbench/sveltekit/src/routes/+page.svelte @@ -0,0 +1,3 @@ +

Workflows DevKit + SvelteKit Example

+
+ \ No newline at end of file diff --git a/workbench/sveltekit/src/routes/api/hook/+server.ts b/workbench/sveltekit/src/routes/api/hook/+server.ts new file mode 100644 index 00000000..a0254e56 --- /dev/null +++ b/workbench/sveltekit/src/routes/api/hook/+server.ts @@ -0,0 +1,29 @@ +import { json, type RequestHandler } from '@sveltejs/kit'; +import { getHookByToken, resumeHook } from 'workflow/api'; + +export const POST: RequestHandler = async ({ + request, +}: { + request: Request; +}) => { + const { token, data } = await request.json(); + + let hook: Awaited>; + try { + hook = await getHookByToken(token); + console.log('hook', hook); + } catch (error) { + console.log('error during getHookByToken', error); + // TODO: `WorkflowAPIError` is not exported, so for now + // we'll return 404 assuming it's the "invalid" token test case + return json(null, { status: 404 }); + } + + await resumeHook(hook.token, { + ...data, + // @ts-expect-error metadata is not typed + customData: hook.metadata?.customData, + }); + + return json(hook); +}; diff --git a/workbench/sveltekit/src/routes/api/trigger/+server.ts b/workbench/sveltekit/src/routes/api/trigger/+server.ts new file mode 100644 index 00000000..98efaa98 --- /dev/null +++ b/workbench/sveltekit/src/routes/api/trigger/+server.ts @@ -0,0 +1,153 @@ +import { json, type RequestHandler } from '@sveltejs/kit'; +import { getRun, start } from 'workflow/api'; +import { hydrateWorkflowArguments } from 'workflow/internal/serialization'; +import * as calcWorkflow from '../../../../workflows/0_calc'; +import * as batchingWorkflow from '../../../../workflows/6_batching'; +import * as duplicateE2e from '../../../../workflows/98_duplicate_case'; +import * as e2eWorkflows from '../../../../workflows/99_e2e'; + +const WORKFLOW_MODULES = { + 'workflows/0_calc.ts': calcWorkflow, + 'workflows/6_batching.ts': batchingWorkflow, + 'workflows/98_duplicate_case.ts': duplicateE2e, + 'workflows/99_e2e.ts': e2eWorkflows, +} as const; + +export const POST: RequestHandler = async ({ request }) => { + const url = new URL(request.url); + const workflowFile = + url.searchParams.get('workflowFile') || 'workflows/99_e2e.ts'; + const workflowFn = url.searchParams.get('workflowFn') || 'simple'; + + console.log('calling workflow', { workflowFile, workflowFn }); + + const workflows = + WORKFLOW_MODULES[workflowFile as keyof typeof WORKFLOW_MODULES]; + if (!workflows) { + return json( + { error: `Workflow file "${workflowFile}" not found` }, + { status: 404 } + ); + } + + const workflow = workflows[workflowFn as keyof typeof workflows]; + if (!workflow) { + return json( + { + error: `Workflow "${workflowFn}" not found in "${workflowFile}"`, + }, + { status: 404 } + ); + } + + let args: any[] = []; + + // Args from query string + const argsParam = url.searchParams.get('args'); + if (argsParam) { + args = argsParam.split(',').map((arg) => { + const num = parseFloat(arg); + return Number.isNaN(num) ? arg.trim() : num; + }); + } else { + // Args from body + const body = await request.text(); + if (body) { + args = hydrateWorkflowArguments(JSON.parse(body), globalThis); + } else { + args = [42]; + } + } + console.log( + `Starting "${workflowFile}/${workflowFn}" workflow with args: ${args}` + ); + + try { + const run = await start(workflow as any, args); + console.log('Run:', run); + return json(run); + } catch (err) { + console.error(`Failed to start!!`, err); + throw err; + } +}; + +export const GET: RequestHandler = async ({ request }) => { + const url = new URL(request.url); + const runId = url.searchParams.get('runId'); + if (!runId) { + return new Response('No runId provided', { status: 400 }); + } + + const outputStreamParam = url.searchParams.get('output-stream'); + if (outputStreamParam) { + const namespace = outputStreamParam === '1' ? undefined : outputStreamParam; + const run = getRun(runId); + const stream = run.getReadable({ + namespace, + }); + // Add JSON framing to the stream, wrapping binary data in base64 + const streamWithFraming = new TransformStream({ + transform(chunk, controller) { + const data = + chunk instanceof Uint8Array + ? { data: Buffer.from(chunk).toString('base64') } + : chunk; + controller.enqueue(`${JSON.stringify(data)}\n`); + }, + }); + return new Response(stream.pipeThrough(streamWithFraming), { + headers: { + 'Content-Type': 'application/octet-stream', + }, + }); + } + + try { + const run = getRun(runId); + const returnValue = await run.returnValue; + console.log('Return value:', returnValue); + return returnValue instanceof ReadableStream + ? new Response(returnValue, { + headers: { + 'Content-Type': 'application/octet-stream', + }, + }) + : json(returnValue); + } catch (error) { + if (error instanceof Error) { + if (error.name === 'WorkflowRunNotCompletedError') { + return json( + { + ...error, + name: error.name, + message: error.message, + }, + { status: 202 } + ); + } + + if (error.name === 'WorkflowRunFailedError') { + return json( + { + ...error, + name: error.name, + message: error.message, + }, + { status: 400 } + ); + } + } + + console.error( + 'Unexpected error while getting workflow return value:', + error + ); + return json( + { + error: 'Internal server error', + }, + { status: 500 } + ); + } +}; diff --git a/workbench/sveltekit/static/robots.txt b/workbench/sveltekit/static/robots.txt new file mode 100644 index 00000000..b6dd6670 --- /dev/null +++ b/workbench/sveltekit/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/workbench/sveltekit/svelte.config.js b/workbench/sveltekit/svelte.config.js new file mode 100644 index 00000000..5d665261 --- /dev/null +++ b/workbench/sveltekit/svelte.config.js @@ -0,0 +1,23 @@ +import node from '@sveltejs/adapter-node'; +import vercel from '@sveltejs/adapter-vercel'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +// Node adapter needed for ci tests +const adapter = process.env.VERCEL_DEPLOYMENT_ID ? vercel() : node(); + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + kit: { + adapter: adapter, + // WARNING: CSRF protection is disabled for testing/development purposes. + // This configuration trusts all origins and should NOT be used in production. + // In production, specify only trusted origins or remove this configuration + // to use SvelteKit's default CSRF protection. + csrf: { trustedOrigins: ['*'] }, + }, +}; + +export default config; diff --git a/workbench/sveltekit/tsconfig.json b/workbench/sveltekit/tsconfig.json new file mode 100644 index 00000000..e3898cb2 --- /dev/null +++ b/workbench/sveltekit/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/workbench/sveltekit/vite.config.ts b/workbench/sveltekit/vite.config.ts new file mode 100644 index 00000000..aede7b13 --- /dev/null +++ b/workbench/sveltekit/vite.config.ts @@ -0,0 +1,8 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; +import devtoolsJson from 'vite-plugin-devtools-json'; +import { workflowPlugin } from 'workflow/sveltekit'; + +export default defineConfig({ + plugins: [workflowPlugin(), devtoolsJson(), sveltekit()], +}); diff --git a/workbench/sveltekit/workflows/0_calc.ts b/workbench/sveltekit/workflows/0_calc.ts new file mode 100644 index 00000000..3323945e --- /dev/null +++ b/workbench/sveltekit/workflows/0_calc.ts @@ -0,0 +1,15 @@ +// import { FatalError } from 'workflow'; + +export async function calc(n: number) { + 'use workflow'; + console.log('Simple workflow started'); + n = await pow(n); + console.log('Simple workflow finished'); + return n; +} + +async function pow(a: number): Promise { + 'use step'; + console.log('Running step pow with arg:', a); + return a * a; +} diff --git a/workbench/sveltekit/workflows/6_batching.ts b/workbench/sveltekit/workflows/6_batching.ts new file mode 120000 index 00000000..75feb6fe --- /dev/null +++ b/workbench/sveltekit/workflows/6_batching.ts @@ -0,0 +1 @@ +../../example/workflows/6_batching.ts \ No newline at end of file diff --git a/workbench/sveltekit/workflows/98_duplicate_case.ts b/workbench/sveltekit/workflows/98_duplicate_case.ts new file mode 120000 index 00000000..49404a98 --- /dev/null +++ b/workbench/sveltekit/workflows/98_duplicate_case.ts @@ -0,0 +1 @@ +../../example/workflows/98_duplicate_case.ts \ No newline at end of file diff --git a/workbench/sveltekit/workflows/99_e2e.ts b/workbench/sveltekit/workflows/99_e2e.ts new file mode 120000 index 00000000..a7eb151e --- /dev/null +++ b/workbench/sveltekit/workflows/99_e2e.ts @@ -0,0 +1 @@ +../../example/workflows/99_e2e.ts \ No newline at end of file