Skip to main content

Vercel Workflow

  • vercel/workflow
  • @workflow/vitest
  • @workflow/vite
  • @workflow/world-postgres
  • @workflow/world
  • 社区
    • @workflow-worlds/turso
    • @fantasticfour/world-nats-jetstream
tip
  • 推荐 *.workflow.ts 命名
function workflow() {
'use workflow';
// 不可用
// fs, path, http, https, net, dns, child_process, cluster, os, stream, crypto
// fetch -> `import { fetch } from "workflow"`
// setTimeout, setInterval, setImmediate, clear* -> sleep
// Buffer -> Uint8Array
//
// 可用但固定
// Math.random
// Date / Date.now() / new Date()
// crypto.getRandomValues(), crypto.randomUUID()
// crypto.subtle.digest()
//
// WebAPI 可用
// Headers
// TextEncoder / TextDecoder
// URL / URLSearchParams
// Request / Response
// console
// structuredClone
// atob / btoa
}
function step() {
'use step';
// 完整 Node 环境
}

Vitest

  • workflowTransformPlugin
    • 处理 use workflow, use step
import { workflow } from '@workflow/vitest';

export default defineConfig({
plugins: [workflow()],
});
// globalSetup
await buildWorkflowTests();

beforeAll(async () => {
await setupWorkflowTests();
});

afterAll(async () => {
await teardownWorkflowTests();
});

另外的配置方式

import { workflowTransformPlugin } from '@workflow/rollup';
import { defineConfig } from 'vitest/config';

export default defineConfig({
plugins: [workflowTransformPlugin()],
test: {
include: ['src/workflow/**/*.test.ts'],
globalSetup: './vitest.workflow.global-setup.ts',
setupFiles: ['./vitest.workflow.setup.ts'],
},
});
import { buildWorkflowTests } from '@workflow/vitest';

export async function setup() {
await buildWorkflowTests();
}
import { setupWorkflowTests, teardownWorkflowTests } from '@workflow/vitest';
import { afterAll } from 'vitest';

await setupWorkflowTests();

afterAll(async () => {
await teardownWorkflowTests();
});

Transform

  • @workflow/serde
  • Internal
    • registerStepFunction
    • workflowEntrypoint

Workflow

export async function orderFood(input: Input) {
'use workflow';
return doSomething(input);
}
export async function orderFood(input: Input) {
return doSomething(input);
}

orderFood.workflowId = 'workflow//./src/order-food//orderFood';

globalThis.__private_workflows.set(orderFood.workflowId, orderFood);
const run = await start(orderFood, [input]);
const workflowMetadata = {
workflowId: 'workflow//app//orderFood',
};

const run = await start(workflowMetadata, [input], {
world,
});

packages/core/src/runtime/start.ts

const workflowName = workflow?.workflowId;

if (!workflowName) {
throw new WorkflowRuntimeError("'start' received an invalid workflow function...");
}

Step

export async function charge(amount: number) {
'use step';
return paymentProvider.charge(amount);
}

Proxy

export const charge = globalThis[Symbol.for('WORKFLOW_USE_STEP')]('step//./src/payment//charge');

Bundle

async function charge(amount: number) {
return paymentProvider.charge(amount);
}

const StepRegistrySymbol = Symbol.for('@workflow/core//registeredSteps');

const registry = (globalThis[StepRegistrySymbol] ??= new Map());

registry.set('step//./src/payment//charge', charge);

charge.stepId = 'step//./src/payment//charge';