diff --git a/.changeset/config.json b/.changeset/config.json index 718ab93ec..55c7f67c5 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -16,6 +16,7 @@ "@workflow/example-nitro-v3", "@workflow/example-nitro-v2", "@workflow/example-nuxt", - "@workflow/example-vite" + "@workflow/example-vite", + "@workflow/example-sveltekit" ] } diff --git a/.changeset/forty-crabs-wonder.md b/.changeset/forty-crabs-wonder.md new file mode 100644 index 000000000..1cc385d84 --- /dev/null +++ b/.changeset/forty-crabs-wonder.md @@ -0,0 +1,10 @@ +--- +"@workflow/world-local": patch +"@workflow/sveltekit": patch +"@workflow/builders": patch +"workflow": patch +"@workflow/core": patch +"@workflow/cli": patch +--- + +Add sveltekit workflow integration diff --git a/.changeset/pre.json b/.changeset/pre.json index 6ab1db4e5..594cff9f4 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -27,7 +27,8 @@ "@workflow/example-nitro-v3": "0.0.0", "@workflow/example-nitro-v2": "0.0.0", "@workflow/example-nuxt": "0.0.0", - "@workflow/example-vite": "0.0.0" + "@workflow/example-vite": "0.0.0", + "@workflow/example-sveltekit": "0.0.0" }, "changesets": [ "angry-owls-beg", diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8a2e1974d..0d94803e6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,6 +58,8 @@ jobs: project-id: "prj_e7DZirYdLrQKXNrlxg7KmA6ABx8r" - name: "vite" project-id: "prj_uLIcNZNDmETulAvj5h0IcDHi5432" + - name: "sveltekit" + project-id: "prj_MqnBLm71ceXGSnm3Fs8i8gBnI23G" env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} @@ -172,7 +174,7 @@ jobs: run: cd workbench/${{ matrix.app.name }} && ./resolve-symlinks.sh - name: Run E2E Tests (Next.js) - if: matrix.app.name != 'nitro' && matrix.app.name != 'vite' + if: matrix.app.name != 'nitro' && matrix.app.name != 'vite' && 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 }} @@ -190,6 +192,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 eaf70d086..4b236f0b1 100644 --- a/docs/app/(home)/components/frameworks.tsx +++ b/docs/app/(home)/components/frameworks.tsx @@ -517,11 +517,18 @@ export const Frameworks = () => { - - + + + - + + + + + + +
@@ -547,13 +554,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 e1426eefd..93d9fa412 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 1313b4f3b..29d13cc8a 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 000000000..1c15df29d --- /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/signup/+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/builders/src/base-builder.ts b/packages/builders/src/base-builder.ts index 48caa357d..02b399895 100644 --- a/packages/builders/src/base-builder.ts +++ b/packages/builders/src/base-builder.ts @@ -7,11 +7,11 @@ import enhancedResolveOriginal from 'enhanced-resolve'; import * as esbuild from 'esbuild'; import { findUp } from 'find-up'; import { glob } from 'tinyglobby'; -import type { WorkflowConfig } from './types.js'; import type { WorkflowManifest } from './apply-swc-transform.js'; import { createDiscoverEntriesPlugin } from './discover-entries-esbuild-plugin.js'; import { createNodeModuleErrorPlugin } from './node-module-esbuild-plugin.js'; import { createSwcPlugin } from './swc-esbuild-plugin.js'; +import type { WorkflowConfig } from './types.js'; const enhancedResolve = promisify(enhancedResolveOriginal); @@ -254,6 +254,7 @@ 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 = ` // Built in steps import '${builtInSteps}'; @@ -455,7 +456,6 @@ 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 /* eslint-disable */ import { workflowEntrypoint } from 'workflow/runtime'; @@ -613,8 +613,7 @@ export const PUT = handler; export const PATCH = handler; export const DELETE = handler; export const HEAD = handler; -export const OPTIONS = handler; -`; +export const OPTIONS = handler;`; if (!bundle) { // For Next.js, just write the unbundled file diff --git a/packages/builders/src/types.ts b/packages/builders/src/types.ts index a3114ffcf..4bb99785c 100644 --- a/packages/builders/src/types.ts +++ b/packages/builders/src/types.ts @@ -2,6 +2,7 @@ export const validBuildTargets = [ 'standalone', 'vercel-build-output-api', 'next', + 'sveltekit', ] as const; export type BuildTarget = (typeof validBuildTargets)[number]; @@ -27,5 +28,5 @@ export interface WorkflowConfig { export function isValidBuildTarget( target: string | undefined ): target is BuildTarget { - return !!target && validBuildTargets.includes(target as BuildTarget); + return !!target && validBuildTargets.includes(target as BuildTarget); } diff --git a/packages/cli/src/lib/builders/base-builder.ts b/packages/cli/src/lib/builders/base-builder.ts index 9ac184ca3..6ab141bd6 100644 --- a/packages/cli/src/lib/builders/base-builder.ts +++ b/packages/cli/src/lib/builders/base-builder.ts @@ -615,7 +615,6 @@ 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 882bbff97..2e818e738 100644 --- a/packages/cli/src/lib/config/types.ts +++ b/packages/cli/src/lib/config/types.ts @@ -4,8 +4,8 @@ export type { WorkflowConfig, } from '@workflow/builders'; export { - validBuildTargets, isValidBuildTarget, + validBuildTargets, } from '@workflow/builders'; export type InspectCLIOptions = { diff --git a/packages/core/e2e/local-build.test.ts b/packages/core/e2e/local-build.test.ts index fa989382b..e638cc614 100644 --- a/packages/core/e2e/local-build.test.ts +++ b/packages/core/e2e/local-build.test.ts @@ -5,20 +5,23 @@ import { getWorkbenchAppPath } from './utils'; const exec = promisify(execOriginal); -describe.each(['nextjs-webpack', 'nextjs-turbopack', 'nitro', 'vite'])( - 'e2e', - (project) => { - test('builds without errors', { timeout: 180_000 }, async () => { - // skip if we're targeting specific app to test - if (process.env.APP_NAME && project !== process.env.APP_NAME) { - return; - } +describe.each([ + 'nextjs-webpack', + 'nextjs-turbopack', + 'nitro', + 'vite', + 'sveltekit', +])('e2e', (project) => { + test('builds without errors', { timeout: 180_000 }, async () => { + // skip if we're targeting specific app to test + if (process.env.APP_NAME && project !== process.env.APP_NAME) { + return; + } - const result = await exec('pnpm build', { - cwd: getWorkbenchAppPath(project), - }); - - expect(result.stderr).not.toContain('Error:'); + const result = await exec('pnpm build', { + cwd: getWorkbenchAppPath(project), }); - } -); + + expect(result.stderr).not.toContain('Error:'); + }); +}); diff --git a/packages/nitro/package.json b/packages/nitro/package.json index 9f70e6199..c931a67b6 100644 --- a/packages/nitro/package.json +++ b/packages/nitro/package.json @@ -31,7 +31,6 @@ "@workflow/swc-plugin": "workspace:*", "@workflow/builders": "workspace:*", "@workflow/core": "workspace:*", - "@workflow/swc-plugin": "workspace:*", "exsolve": "^1.0.7", "pathe": "^2.0.3" }, diff --git a/packages/sveltekit/LICENSE.md b/packages/sveltekit/LICENSE.md new file mode 120000 index 000000000..f0608a63a --- /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 000000000..88571598b --- /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 000000000..461267fab --- /dev/null +++ b/packages/sveltekit/package.json @@ -0,0 +1,42 @@ +{ + "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": { + "@swc/core": "1.11.24", + "@sveltejs/kit": "^2.48.2", + "@workflow/builders": "workspace:*", + "@workflow/swc-plugin": "workspace:*", + "fs-extra": "^11.3.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + }, + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/node": "catalog:", + "@workflow/tsconfig": "workspace:*", + "vite": "^7.1.11" + } +} diff --git a/packages/sveltekit/src/builder.ts b/packages/sveltekit/src/builder.ts new file mode 100644 index 000000000..90409d3df --- /dev/null +++ b/packages/sveltekit/src/builder.ts @@ -0,0 +1,258 @@ +import { constants } from 'node:fs'; +import { access, mkdir, readFile, stat, writeFile } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; +import { BaseBuilder, type WorkflowConfig } from '@workflow/builders'; + +// 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 class SvelteKitBuilder extends BaseBuilder { + constructor(config?: Partial) { + super({ + ...config, + dirs: ['workflows'], + buildTarget: 'sveltekit' as const, + stepsBundlePath: '', // unused in base + workflowsBundlePath: '', // unused in base + webhookBundlePath: '', // unused in base + 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 }); + + await this.createStepsBundle({ + format: 'esm', + inputFiles, + outfile: join(stepsRouteDir, '+server.js'), + externalizeNonSteps: true, + tsBaseUrl, + tsPaths, + }); + + // Post-process the generated file to wrap with SvelteKit request converter + const stepsRouteFile = join(stepsRouteDir, '+server.js'); + let stepsRouteContent = await readFile(stepsRouteFile, 'utf-8'); + + // Replace the default export with SvelteKit-compatible handler + stepsRouteContent = stepsRouteContent.replace( + /export\s*\{\s*stepEntrypoint\s+as\s+POST\s*\}\s*;?$/m, + `${SVELTEKIT_REQUEST_CONVERTER} +export const POST = async ({request}) => { + const normalRequest = await convertSvelteKitRequest(request); + return stepEntrypoint(normalRequest); +}` + ); + + await writeFile(stepsRouteFile, stepsRouteContent); + } + + 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 }); + + await this.createWorkflowsBundle({ + format: 'esm', + outfile: join(workflowsRouteDir, '+server.js'), + bundleFinalOutput: false, + inputFiles, + tsBaseUrl, + tsPaths, + }); + + // Post-process the generated file to wrap with SvelteKit request converter + const workflowsRouteFile = join(workflowsRouteDir, '+server.js'); + let workflowsRouteContent = await readFile(workflowsRouteFile, 'utf-8'); + + // Replace the default export with SvelteKit-compatible handler + workflowsRouteContent = workflowsRouteContent.replace( + /export const POST = workflowEntrypoint\(workflowCode\);?$/m, + `${SVELTEKIT_REQUEST_CONVERTER} +export const POST = async ({request}) => { + const normalRequest = await convertSvelteKitRequest(request); + return workflowEntrypoint(workflowCode)(normalRequest); +}` + ); + await writeFile(workflowsRouteFile, workflowsRouteContent); + } + + 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' + ); + + await this.createWebhookBundle({ + outfile: webhookRouteFile, + bundle: false, // SvelteKit will handle bundling + }); + + // Post-process the generated file to wrap with SvelteKit request converter + let webhookRouteContent = await readFile(webhookRouteFile, 'utf-8'); + + // For local dev (node), need this since context isn't available to waitUntil() + // Add SYMBOL_FOR_REQ_CONTEXT at the top after imports + webhookRouteContent = webhookRouteContent.replace( + /(import.*?;)/, + `$1\n\nconst SYMBOL_FOR_REQ_CONTEXT = Symbol.for('@vercel/request-context');` + ); + + // Update handler signature to accept token as parameter + webhookRouteContent = webhookRouteContent.replace( + /async function handler\(request\) \{[\s\S]*?const token = decodeURIComponent\(pathParts\[pathParts\.length - 1\]\);/, + `async function handler(request, token) {` + ); + + // Remove the URL parsing code since we get token from params + webhookRouteContent = webhookRouteContent.replace( + /const url = new URL\(request\.url\);[\s\S]*?const pathParts = url\.pathname\.split\('\/'\);[\s\S]*?\n/, + '' + ); + + // Replace all HTTP method exports with SvelteKit-compatible handlers + webhookRouteContent = webhookRouteContent.replace( + /export const GET = handler;\nexport const POST = handler;\nexport const PUT = handler;\nexport const PATCH = handler;\nexport const DELETE = handler;\nexport const HEAD = handler;\nexport const OPTIONS = handler;/, + `${SVELTEKIT_REQUEST_CONVERTER} +const createSvelteKitHandler = (method) => async ({ request, params, platform }) => { + // Track background promises for local dev + const backgroundPromises = []; + + // Set up context for @vercel/functions waitUntil + const context = { + waitUntil: platform?.waitUntil || ((promise) => { + // Fallback for local dev/tests: collect promises to await them + backgroundPromises.push(promise.catch(err => console.error('Background task error:', err))); + }) + }; + + // Only set context if it doesn't already exist (Vercel sets it and makes it read-only) + if (!globalThis[SYMBOL_FOR_REQ_CONTEXT]) { + globalThis[SYMBOL_FOR_REQ_CONTEXT] = { + get: () => context + }; + } + + const normalRequest = await convertSvelteKitRequest(request); + const response = await handler(normalRequest, params.token); + + // In local dev (no platform.waitUntil), await background tasks before returning + if (!platform?.waitUntil && backgroundPromises.length > 0) { + await Promise.all(backgroundPromises); + } + + return response; +}; + +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');` + ); + + await writeFile(webhookRouteFile, webhookRouteContent); + } + + 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 000000000..a48f7083a --- /dev/null +++ b/packages/sveltekit/src/index.ts @@ -0,0 +1,79 @@ +import path from 'node:path'; +import fs from 'fs-extra'; + +import { SvelteKitBuilder } from './builder.js'; + +const builder = new SvelteKitBuilder(); + +// 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 builder.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 { workflowPlugin } from './plugin.js'; diff --git a/packages/sveltekit/src/plugin.ts b/packages/sveltekit/src/plugin.ts new file mode 100644 index 000000000..4c61786fa --- /dev/null +++ b/packages/sveltekit/src/plugin.ts @@ -0,0 +1,92 @@ +import { transform } from '@swc/core'; +import { resolveModulePath } from 'exsolve'; +import type { HmrContext, Plugin } from 'vite'; +import { SvelteKitBuilder } from './builder.js'; + +export function workflowPlugin(): Plugin { + let builder: SvelteKitBuilder; + + return { + name: 'workflow:sveltekit', + + // TODO: Move this to @workflow/vite or something since this is vite specific + // Transform workflow files with SWC + async transform(code: string, id: string) { + // Only apply the transform if file needs it + if (!code.match(/(use step|use workflow)/)) { + return null; + } + + const isTypeScript = id.endsWith('.ts') || id.endsWith('.tsx'); + const isTsx = id.endsWith('.tsx'); + + const swcPlugin = resolveModulePath('@workflow/swc-plugin', { + from: [import.meta.url], + }); + + // Transform with SWC + const result = await transform(code, { + filename: id, + jsc: { + parser: { + syntax: isTypeScript ? 'typescript' : 'ecmascript', + tsx: isTsx, + }, + target: 'es2022', + experimental: { + plugins: [[swcPlugin, { mode: 'client' }]], + }, + }, + minify: false, + sourceMaps: true, + inlineSourcesContent: true, + }); + + return { + code: result.code, + map: result.map ? JSON.parse(result.map) : null, + }; + }, + + configResolved() { + builder = new SvelteKitBuilder(); + }, + + async buildStart() { + await builder.build(); + }, + + // TODO: Move this to @workflow/vite or something since this is vite specific + async handleHotUpdate(ctx: HmrContext) { + const { file, server, read } = ctx; + + // Check if this is a TS/JS file that might contain workflow directives + const jsTsRegex = /\.(ts|tsx|js|jsx|mjs|cjs)$/; + if (!jsTsRegex.test(file)) { + return; + } + + // Read the file to check for workflow/step directives + const content = await read(); + const useWorkflowPattern = /^\s*(['"])use workflow\1;?\s*$/m; + const useStepPattern = /^\s*(['"])use step\1;?\s*$/m; + + if (!useWorkflowPattern.test(content) && !useStepPattern.test(content)) { + return; + } + + // Rebuild everything - simpler and more reliable than tracking individual files + console.log('Workflow file changed, regenerating routes...'); + await builder.build(); + + // Trigger full reload of workflow routes + server.ws.send({ + type: 'full-reload', + path: '*', + }); + + // Let Vite handle the normal HMR for the changed file + return; + }, + }; +} diff --git a/packages/sveltekit/tsconfig.json b/packages/sveltekit/tsconfig.json new file mode 100644 index 000000000..ba5d9aec0 --- /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 f53427d20..41835da9c 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -35,6 +35,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", "./vite": "./dist/vite.js", "./runtime": "./dist/runtime.js" @@ -53,7 +54,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 000000000..7de66f9c0 --- /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 2f54e66a6..5ea1fd34b 100644 --- a/packages/world-local/src/queue.ts +++ b/packages/world-local/src/queue.ts @@ -67,6 +67,7 @@ export function createQueue(port?: number): Queue { duplex: 'half', dispatcher: httpAgent, headers: { + 'content-type': 'application/json', 'x-vqs-queue-name': queueName, 'x-vqs-message-id': messageId, 'x-vqs-message-attempt': String(attempt + 1), @@ -123,7 +124,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 43fa66fad..253633c3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,7 +109,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.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(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.2)(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) @@ -560,6 +560,43 @@ importers: specifier: ^7.1.12 version: 7.1.12(@types/node@22.19.0)(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.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)) + '@swc/core': + specifier: 1.11.24 + version: 1.11.24 + '@workflow/builders': + specifier: workspace:* + version: link:../builders + '@workflow/swc-plugin': + specifier: workspace:* + version: link:../swc-plugin-workflow + exsolve: + specifier: ^1.0.7 + version: 1.0.7 + fs-extra: + specifier: ^11.3.2 + version: 11.3.2 + pathe: + specifier: ^2.0.3 + version: 2.0.3 + devDependencies: + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 + '@types/node': + specifier: 'catalog:' + version: 22.19.0 + '@workflow/tsconfig': + specifier: workspace:* + version: link:../tsconfig + vite: + specifier: ^7.1.11 + version: 7.1.12(@types/node@22.19.0)(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': @@ -752,6 +789,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 @@ -1226,6 +1266,73 @@ 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.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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))) + '@sveltejs/adapter-vercel': + specifier: ^6.1.1 + version: 6.1.1(@sveltejs/kit@2.48.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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)))(rollup@4.52.5) + '@swc/core': + specifier: 1.11.24 + version: 1.11.24 + '@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) + exsolve: + specifier: ^1.0.7 + version: 1.0.7 + 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.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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)) + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.0 + version: 6.2.1(svelte@5.43.2)(vite@7.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)) + '@types/lodash.chunk': + specifier: ^4.2.9 + version: 4.2.9 + svelte: + specifier: ^5.39.5 + version: 5.43.2 + svelte-check: + specifier: ^4.3.2 + version: 4.3.3(picomatch@4.0.3)(svelte@5.43.2)(typescript@5.9.3) + typescript: + specifier: ^5.9.2 + version: 5.9.3 + vite: + specifier: ^7.1.7 + version: 7.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) + vite-plugin-devtools-json: + specifier: ^1.0.0 + version: 1.0.0(vite@7.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)) + workbench/vite: devDependencies: ai: @@ -4344,6 +4451,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.4': + resolution: {integrity: sha512-TGFX1pZUt9qqY20Cv5NyYvy0iLWHf2jXi8s+eCGsig7jQMdwZWKUFMR6TbvFNhfDSUpc1sH/Y5EHv20g3HHA3g==} + 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'} @@ -4522,6 +4673,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==} @@ -4633,6 +4787,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==} @@ -4645,6 +4802,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==} @@ -5043,6 +5203,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==} @@ -5088,6 +5252,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: @@ -5454,6 +5622,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'} @@ -6127,6 +6299,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} @@ -6140,6 +6315,9 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} + esrap@2.1.2: + resolution: {integrity: sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -6349,6 +6527,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'} @@ -6808,6 +6990,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==} @@ -6936,6 +7121,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==} @@ -7077,6 +7265,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'} @@ -8579,6 +8770,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==} @@ -8642,6 +8837,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==} @@ -8912,6 +9110,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.2: + resolution: {integrity: sha512-ro1umEzX8rT5JpCmlf0PPv7ncD8MdVob9e18bhwqTKNoLjS8kDvhVpaoYVPc+qMwDAOfcwJtyY7ZFSDbOaNPgA==} + engines: {node: '>=18'} + svgo@4.0.0: resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} engines: {node: '>=16'} @@ -9213,6 +9423,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'} @@ -9496,6 +9710,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'} @@ -9592,6 +9811,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} @@ -9793,6 +10020,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'} @@ -9836,6 +10066,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': @@ -13457,6 +13697,112 @@ 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.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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: + '@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.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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)) + rollup: 4.52.5 + + '@sveltejs/adapter-vercel@6.1.1(@sveltejs/kit@2.48.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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)))(rollup@4.52.5)': + dependencies: + '@sveltejs/kit': 2.48.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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)) + '@vercel/nft': 0.30.3(rollup@4.52.5) + esbuild: 0.25.11 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@sveltejs/kit@2.48.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(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.2)(vite@7.1.12(@types/node@22.19.0)(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.2 + vite: 7.1.12(@types/node@22.19.0)(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/kit@2.48.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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: + '@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.2)(vite@7.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)) + '@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.2 + vite: 7.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) + optionalDependencies: + '@opentelemetry/api': 1.9.0 + + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(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.2)(vite@7.1.12(@types/node@22.19.0)(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.2 + vite: 7.1.12(@types/node@22.19.0)(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-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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: + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.2)(vite@7.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)) + debug: 4.4.3(supports-color@8.1.1) + svelte: 5.43.2 + vite: 7.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) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(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.2)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(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.2 + vite: 7.1.12(@types/node@22.19.0)(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.12(@types/node@22.19.0)(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.2)(vite@7.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: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.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)))(svelte@5.43.2)(vite@7.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)) + debug: 4.4.3(supports-color@8.1.1) + deepmerge: 4.3.1 + magic-string: 0.30.19 + svelte: 5.43.2 + vite: 7.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) + vitefu: 1.1.1(vite@7.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)) + transitivePeerDependencies: + - supports-color + '@swc/core-darwin-arm64@1.11.24': optional: true @@ -13609,6 +13955,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': @@ -13749,6 +14097,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': @@ -13762,6 +14115,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 @@ -13849,10 +14206,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.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(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.2)(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.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)))(svelte@5.43.2)(vite@7.1.12(@types/node@22.19.0)(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.2 vue: 3.5.22(typescript@5.9.3) vue-router: 4.6.3(vue@3.5.22(typescript@5.9.3)) @@ -14255,6 +14614,8 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.2: {} + array-timsort@1.0.3: {} array-union@2.1.0: {} @@ -14299,6 +14660,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: {} @@ -14664,6 +15027,8 @@ snapshots: cookie-es@2.0.0: {} + cookie@0.6.0: {} + cookie@1.0.2: {} copy-anything@4.0.5: @@ -15368,6 +15733,8 @@ snapshots: - supports-color optional: true + esm-env@1.2.2: {} + espree@10.4.0: dependencies: acorn: 8.15.0 @@ -15382,6 +15749,10 @@ snapshots: estraverse: 5.3.0 optional: true + esrap@2.1.2: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -15602,6 +15973,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 @@ -16106,6 +16483,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 @@ -16216,6 +16597,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: @@ -16366,6 +16753,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 @@ -18609,6 +18998,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: {} @@ -18677,6 +19070,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.2: {} + setprototypeof@1.2.0: {} sharp@0.34.4: @@ -18967,6 +19362,35 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svelte-check@4.3.3(picomatch@4.0.3)(svelte@5.43.2)(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.2 + typescript: 5.9.3 + transitivePeerDependencies: + - picomatch + + svelte@5.43.2: + 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.2 + 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 @@ -18989,6 +19413,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: {} @@ -19314,6 +19744,8 @@ snapshots: universalify@0.1.2: {} + universalify@2.0.1: {} + unplugin-utils@0.2.5: dependencies: pathe: 2.0.3 @@ -19454,6 +19886,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: {} @@ -19565,6 +20001,11 @@ snapshots: optionator: 0.9.4 typescript: 5.9.3 + vite-plugin-devtools-json@1.0.0(vite@7.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: + uuid: 11.1.0 + vite: 7.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) + vite-plugin-inspect@11.3.3(@nuxt/kit@3.19.3(magicast@0.3.5))(vite@7.1.12(@types/node@22.19.0)(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 @@ -19660,6 +20101,14 @@ snapshots: tsx: 4.20.6 yaml: 2.8.0 + vitefu@1.1.1(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)): + optionalDependencies: + vite: 7.1.12(@types/node@22.19.0)(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.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)): + optionalDependencies: + vite: 7.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) + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(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 @@ -19906,6 +20355,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 1cf61585e..057c232d1 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,3 +16,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 bb5731fd8..007fd7598 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 ca0f500f6..3b5c81471 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 000000000..3b462cb0c --- /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 000000000..b6f27f135 --- /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 000000000..75842c404 --- /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 000000000..7669e7579 --- /dev/null +++ b/workbench/sveltekit/package.json @@ -0,0 +1,39 @@ +{ + "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", + "@swc/core": "1.11.24", + "@vercel/otel": "^1.13.0", + "@workflow/ai": "workspace:*", + "ai": "catalog:", + "exsolve": "^1.0.7", + "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 000000000..520c4217a --- /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 000000000..44e6779cb --- /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 000000000..cc5dc66a3 --- /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 000000000..856f2b6c3 --- /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 000000000..20f8d044f --- /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 000000000..8cf31343c --- /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 000000000..a0254e560 --- /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/signup/+server.ts b/workbench/sveltekit/src/routes/api/signup/+server.ts new file mode 100644 index 000000000..8e76aaf3a --- /dev/null +++ b/workbench/sveltekit/src/routes/api/signup/+server.ts @@ -0,0 +1,12 @@ +import { json, type RequestHandler } from '@sveltejs/kit'; +import { start } from 'workflow/api'; +import { handleUserSignup } from '../../../../workflows/user-signup'; + +export const GET: RequestHandler = async ({ + request, +}: { + request: Request; +}) => { + const run = await start(handleUserSignup, ['test@example.com']); + return json(run.runId); +}; 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 000000000..98efaa98d --- /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 000000000..b6dd6670c --- /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 000000000..5d665261c --- /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 000000000..e3898cb22 --- /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 000000000..aede7b139 --- /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 000000000..3323945e6 --- /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 000000000..75feb6fee --- /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 000000000..49404a981 --- /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 000000000..a7eb151e6 --- /dev/null +++ b/workbench/sveltekit/workflows/99_e2e.ts @@ -0,0 +1 @@ +../../example/workflows/99_e2e.ts \ No newline at end of file diff --git a/workbench/sveltekit/workflows/user-signup.ts b/workbench/sveltekit/workflows/user-signup.ts new file mode 100644 index 000000000..173c7196e --- /dev/null +++ b/workbench/sveltekit/workflows/user-signup.ts @@ -0,0 +1,43 @@ +import { createWebhook, sleep } from 'workflow'; + +export async function handleUserSignup(email: string) { + 'use workflow'; + + const user = await createUser(email); + await sendWelcomeEmail(user); + + await sleep('5s'); + + const webhook = createWebhook(); + await sendOnboardingEmail(user, webhook.url); + + await webhook; + console.log('Webhook Resolved'); + + return { userId: user.id, status: 'onboarded' }; +} + +async function createUser(email: string) { + 'use step'; + + console.log(`Creating a new user with email: ${email}`); + + return { id: crypto.randomUUID(), email }; +} + +async function sendWelcomeEmail(user: { id: string; email: string }) { + 'use step'; + + console.log(`Sending welcome email to user: ${user.id}`); +} + +async function sendOnboardingEmail( + user: { id: string; email: string }, + callback: string +) { + 'use step'; + + console.log(`Sending onboarding email to user: ${user.id}`); + + console.log(`Click this link to resolve the webhook: ${callback}`); +}