mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
[e2e] Remove /api/hook and /api/test-health-check endpoints, call APIs directly
- Remove debug logging from bench.bench.ts - Remove awaitReturnValue() wrapper, use run.returnValue directly in benchmarks - Add changeset for Nitro builder manifest fix - Refactor hookWorkflow tests to call getHookByToken()/resumeHook() directly (pass hook object to resumeHook to avoid duplicate lookups) - Refactor queue-based health check test to call healthCheck() directly - Assert specific error message for invalid hook token test - Remove /api/hook and /api/test-health-check endpoints from all workbench apps (nextjs-turbopack, nextjs-webpack, vite, hono, express, fastify, sveltekit, astro, nuxt, nitro-v2, nitro-v3, nest, example)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@workflow/nitro": patch
|
||||
---
|
||||
|
||||
Fix Nitro prod builds: use a physical handler file with inlined manifest content instead of a virtual module with `readFileSync` that referenced an absolute build-machine path
|
||||
@@ -14,15 +14,6 @@ if (!deploymentUrl) {
|
||||
throw new Error('`DEPLOYMENT_URL` environment variable is not set');
|
||||
}
|
||||
|
||||
console.log('[bench] deploymentUrl:', deploymentUrl);
|
||||
console.log('[bench] isLocalDeployment:', isLocalDeployment());
|
||||
console.log('[bench] WORKFLOW_VERCEL_ENV:', process.env.WORKFLOW_VERCEL_ENV);
|
||||
console.log('[bench] VERCEL_DEPLOYMENT_ID:', process.env.VERCEL_DEPLOYMENT_ID);
|
||||
console.log(
|
||||
'[bench] WORKFLOW_TARGET_WORLD:',
|
||||
process.env.WORKFLOW_TARGET_WORLD
|
||||
);
|
||||
|
||||
// Configure the World for the bench runner process (same as e2e tests)
|
||||
if (isLocalDeployment()) {
|
||||
process.env.WORKFLOW_LOCAL_BASE_URL = deploymentUrl;
|
||||
@@ -31,19 +22,12 @@ if (isLocalDeployment()) {
|
||||
const isNextJs = appName.includes('nextjs') || appName.includes('next-');
|
||||
const dataDirName = isNextJs ? '.next/workflow-data' : '.workflow-data';
|
||||
process.env.WORKFLOW_LOCAL_DATA_DIR = path.join(appPath, dataDirName);
|
||||
console.log(
|
||||
'[bench] configured local world, dataDir:',
|
||||
process.env.WORKFLOW_LOCAL_DATA_DIR
|
||||
);
|
||||
} else if (process.env.WORKFLOW_VERCEL_ENV) {
|
||||
if (!process.env.VERCEL_DEPLOYMENT_ID) {
|
||||
throw new Error(
|
||||
'VERCEL_DEPLOYMENT_ID is required for Vercel benchmarks but is not set'
|
||||
);
|
||||
}
|
||||
console.log('[bench] configured for Vercel world');
|
||||
} else {
|
||||
console.log('[bench] no special world configuration');
|
||||
}
|
||||
|
||||
// Manifest type and helpers (same as e2e tests)
|
||||
@@ -61,22 +45,15 @@ let cachedManifest: WorkflowManifest | null = null;
|
||||
async function fetchManifest(): Promise<WorkflowManifest> {
|
||||
if (cachedManifest) return cachedManifest;
|
||||
const url = new URL('/.well-known/workflow/v1/manifest.json', deploymentUrl);
|
||||
console.log('[bench] fetching manifest from:', url.toString());
|
||||
const res = await fetch(url, {
|
||||
headers: getProtectionBypassHeaders(),
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
redirect: 'follow',
|
||||
});
|
||||
console.log('[bench] manifest response status:', res.status, 'url:', res.url);
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`Failed to fetch manifest: ${res.status} ${text}`);
|
||||
}
|
||||
cachedManifest = (await res.json()) as WorkflowManifest;
|
||||
console.log(
|
||||
'[bench] manifest loaded, workflows:',
|
||||
Object.keys(cachedManifest.workflows).join(', ')
|
||||
);
|
||||
return cachedManifest;
|
||||
}
|
||||
|
||||
@@ -133,27 +110,6 @@ const bufferedTimings: Map<
|
||||
{ run: any; extra?: { firstByteTimeMs?: number; slurpTimeMs?: number } }[]
|
||||
> = new Map();
|
||||
|
||||
/**
|
||||
* Await run.returnValue with a timeout to prevent benchmarks from hanging.
|
||||
*/
|
||||
async function awaitReturnValue<T>(
|
||||
run: Run<T>,
|
||||
timeoutMs = 120_000
|
||||
): Promise<T> {
|
||||
const timeout = new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(
|
||||
`run.returnValue timed out after ${timeoutMs}ms for run ${run.runId}`
|
||||
)
|
||||
),
|
||||
timeoutMs
|
||||
)
|
||||
);
|
||||
return Promise.race([run.returnValue, timeout]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect run timing metadata from a completed run.
|
||||
*/
|
||||
@@ -328,14 +284,8 @@ describe('Workflow Performance Benchmarks', () => {
|
||||
bench(
|
||||
'workflow with no steps',
|
||||
async () => {
|
||||
console.log('[bench] resolving workflow metadata...');
|
||||
const wf = await benchWf('noStepsWorkflow');
|
||||
console.log('[bench] calling start() with workflowId:', wf.workflowId);
|
||||
const run = await start(wf, [42]);
|
||||
console.log('[bench] start() returned, runId:', run.runId);
|
||||
console.log('[bench] awaiting returnValue...');
|
||||
await awaitReturnValue(run);
|
||||
console.log('[bench] returnValue resolved');
|
||||
const run = await start(await benchWf('noStepsWorkflow'), [42]);
|
||||
await run.returnValue;
|
||||
const timings = await getRunTimings(run);
|
||||
stageTiming('workflow with no steps', timings);
|
||||
},
|
||||
@@ -346,7 +296,7 @@ describe('Workflow Performance Benchmarks', () => {
|
||||
'workflow with 1 step',
|
||||
async () => {
|
||||
const run = await start(await benchWf('oneStepWorkflow'), [100]);
|
||||
await awaitReturnValue(run);
|
||||
await run.returnValue;
|
||||
const timings = await getRunTimings(run);
|
||||
stageTiming('workflow with 1 step', timings);
|
||||
},
|
||||
@@ -374,7 +324,7 @@ describe('Workflow Performance Benchmarks', () => {
|
||||
const run = await start(await benchWf('sequentialStepsWorkflow'), [
|
||||
count,
|
||||
]);
|
||||
await awaitReturnValue(run);
|
||||
await run.returnValue;
|
||||
const timings = await getRunTimings(run);
|
||||
stageTiming(name, timings);
|
||||
},
|
||||
@@ -386,7 +336,7 @@ describe('Workflow Performance Benchmarks', () => {
|
||||
'workflow with stream',
|
||||
async () => {
|
||||
const run = await start(await benchWf('streamWorkflow'), []);
|
||||
const value = await awaitReturnValue(run);
|
||||
const value = await run.returnValue;
|
||||
const timings = await getRunTimings(run);
|
||||
// Consume the entire stream and track:
|
||||
// - firstByteTimeMs: time from workflow start to first byte
|
||||
@@ -446,7 +396,7 @@ describe('Workflow Performance Benchmarks', () => {
|
||||
name,
|
||||
async () => {
|
||||
const run = await start(await benchWf(workflow), [count]);
|
||||
await awaitReturnValue(run);
|
||||
await run.returnValue;
|
||||
const timings = await getRunTimings(run);
|
||||
stageTiming(name, timings);
|
||||
},
|
||||
|
||||
@@ -3,7 +3,14 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { afterAll, assert, beforeAll, describe, expect, test } from 'vitest';
|
||||
import type { Run } from '../src/runtime';
|
||||
import { getRun, start } from '../src/runtime';
|
||||
import {
|
||||
getHookByToken,
|
||||
getRun,
|
||||
getWorld,
|
||||
healthCheck,
|
||||
resumeHook,
|
||||
start,
|
||||
} from '../src/runtime';
|
||||
import {
|
||||
cliHealthJson,
|
||||
cliInspectJson,
|
||||
@@ -323,51 +330,37 @@ describe('e2e', () => {
|
||||
|
||||
const run = await start(await e2e('hookWorkflow'), [token, customData]);
|
||||
|
||||
// Wait a few seconds so that the webhook is registered.
|
||||
// Wait a few seconds so that the hook is registered.
|
||||
// TODO: make this more efficient when we add subscription support.
|
||||
await new Promise((resolve) => setTimeout(resolve, 5_000));
|
||||
|
||||
const hookUrl = new URL('/api/hook', deploymentUrl);
|
||||
|
||||
let res = await fetch(hookUrl, {
|
||||
method: 'POST',
|
||||
headers: getProtectionBypassHeaders(),
|
||||
body: JSON.stringify({ token, data: { message: 'one' } }),
|
||||
// Look up the hook and resume it with the first payload
|
||||
let hook = await getHookByToken(token);
|
||||
expect(hook.runId).toBe(run.runId);
|
||||
await resumeHook(hook, {
|
||||
message: 'one',
|
||||
customData: (hook.metadata as any)?.customData,
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
let body = await res.json();
|
||||
expect(body.runId).toBe(run.runId);
|
||||
|
||||
// Invalid token test
|
||||
res = await fetch(hookUrl, {
|
||||
method: 'POST',
|
||||
headers: getProtectionBypassHeaders(),
|
||||
body: JSON.stringify({ token: 'invalid' }),
|
||||
});
|
||||
// NOTE: For Nitro apps (Vite, Hono, etc.) in dev mode, status 404 does some
|
||||
// unexpected stuff and could return a Vite SPA fallback or can cause a Hono route to hang.
|
||||
// This is because Nitro passes the 404 requests to the dev server to handle.
|
||||
expect(res.status).toBeOneOf([404, 422]);
|
||||
body = await res.json();
|
||||
expect(body).toBeNull();
|
||||
await expect(getHookByToken('invalid')).rejects.toThrow(/not found/i);
|
||||
|
||||
res = await fetch(hookUrl, {
|
||||
method: 'POST',
|
||||
headers: getProtectionBypassHeaders(),
|
||||
body: JSON.stringify({ token, data: { message: 'two' } }),
|
||||
// Resume with second payload
|
||||
hook = await getHookByToken(token);
|
||||
expect(hook.runId).toBe(run.runId);
|
||||
await resumeHook(hook, {
|
||||
message: 'two',
|
||||
customData: (hook.metadata as any)?.customData,
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
body = await res.json();
|
||||
expect(body.runId).toBe(run.runId);
|
||||
|
||||
res = await fetch(hookUrl, {
|
||||
method: 'POST',
|
||||
headers: getProtectionBypassHeaders(),
|
||||
body: JSON.stringify({ token, data: { message: 'three', done: true } }),
|
||||
// Resume with third (final) payload
|
||||
hook = await getHookByToken(token);
|
||||
expect(hook.runId).toBe(run.runId);
|
||||
await resumeHook(hook, {
|
||||
message: 'three',
|
||||
done: true,
|
||||
customData: (hook.metadata as any)?.customData,
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
body = await res.json();
|
||||
expect(body.runId).toBe(run.runId);
|
||||
|
||||
const returnValue = await run.returnValue;
|
||||
expect(returnValue).toBeInstanceOf(Array);
|
||||
@@ -961,20 +954,13 @@ describe('e2e', () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5_000));
|
||||
|
||||
// Send payload to first workflow
|
||||
const hookUrl = new URL('/api/hook', deploymentUrl);
|
||||
let res = await fetch(hookUrl, {
|
||||
method: 'POST',
|
||||
headers: getProtectionBypassHeaders(),
|
||||
body: JSON.stringify({
|
||||
token,
|
||||
data: { message: 'test-message-1', customData },
|
||||
}),
|
||||
let hook = await getHookByToken(token);
|
||||
expect(hook.runId).toBe(run1.runId);
|
||||
await resumeHook(hook, {
|
||||
message: 'test-message-1',
|
||||
customData: (hook.metadata as any)?.customData,
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
let body = await res.json();
|
||||
expect(body.runId).toBe(run1.runId);
|
||||
|
||||
// Get first workflow result
|
||||
const run1Result = await run1.returnValue;
|
||||
expect(run1Result).toMatchObject({
|
||||
@@ -993,19 +979,13 @@ describe('e2e', () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5_000));
|
||||
|
||||
// Send payload to second workflow using same token
|
||||
res = await fetch(hookUrl, {
|
||||
method: 'POST',
|
||||
headers: getProtectionBypassHeaders(),
|
||||
body: JSON.stringify({
|
||||
token,
|
||||
data: { message: 'test-message-2', customData },
|
||||
}),
|
||||
hook = await getHookByToken(token);
|
||||
expect(hook.runId).toBe(run2.runId);
|
||||
await resumeHook(hook, {
|
||||
message: 'test-message-2',
|
||||
customData: (hook.metadata as any)?.customData,
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
body = await res.json();
|
||||
expect(body.runId).toBe(run2.runId);
|
||||
|
||||
// Get second workflow result
|
||||
const run2Result = await run2.returnValue;
|
||||
expect(run2Result).toMatchObject({
|
||||
@@ -1059,16 +1039,11 @@ describe('e2e', () => {
|
||||
expect(run2Data.status).toBe('failed');
|
||||
|
||||
// Now send a payload to complete workflow 1
|
||||
const hookUrl = new URL('/api/hook', deploymentUrl);
|
||||
const res = await fetch(hookUrl, {
|
||||
method: 'POST',
|
||||
headers: getProtectionBypassHeaders(),
|
||||
body: JSON.stringify({
|
||||
token,
|
||||
data: { message: 'test-concurrent', customData },
|
||||
}),
|
||||
const hook = await getHookByToken(token);
|
||||
await resumeHook(hook, {
|
||||
message: 'test-concurrent',
|
||||
customData: (hook.metadata as any)?.customData,
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
// Verify workflow 1 completed successfully
|
||||
const run1Result = await run1.returnValue;
|
||||
@@ -1250,35 +1225,19 @@ describe('e2e', () => {
|
||||
'health check (queue-based) - workflow and step endpoints respond to health check messages',
|
||||
{ timeout: 60_000 },
|
||||
async () => {
|
||||
// NOTE: This tests the queue-based health check using healthCheck() function.
|
||||
// This approach bypasses Vercel Deployment Protection by sending messages
|
||||
// Tests the queue-based health check using healthCheck() directly.
|
||||
// This bypasses Vercel Deployment Protection by sending messages
|
||||
// through the Queue infrastructure rather than direct HTTP.
|
||||
const url = new URL('/api/test-health-check', deploymentUrl);
|
||||
const world = getWorld();
|
||||
|
||||
// Test workflow endpoint health check
|
||||
const workflowRes = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...getProtectionBypassHeaders(),
|
||||
},
|
||||
body: JSON.stringify({ endpoint: 'workflow', timeout: 30000 }),
|
||||
const workflowResult = await healthCheck(world, 'workflow', {
|
||||
timeout: 30000,
|
||||
});
|
||||
expect(workflowRes.status).toBe(200);
|
||||
const workflowResult = await workflowRes.json();
|
||||
expect(workflowResult.healthy).toBe(true);
|
||||
|
||||
// Test step endpoint health check
|
||||
const stepRes = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...getProtectionBypassHeaders(),
|
||||
},
|
||||
body: JSON.stringify({ endpoint: 'step', timeout: 30000 }),
|
||||
});
|
||||
expect(stepRes.status).toBe(200);
|
||||
const stepResult = await stepRes.json();
|
||||
const stepResult = await healthCheck(world, 'step', { timeout: 30000 });
|
||||
expect(stepResult.healthy).toBe(true);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
|
||||
export const POST: APIRoute = async ({ request }: { request: Request }) => {
|
||||
const { token, data } = await request.json();
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
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 Response.json(null, { status: 404 });
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return Response.json(hook);
|
||||
};
|
||||
|
||||
export const prerender = false;
|
||||
@@ -1,32 +0,0 @@
|
||||
// This route tests the queue-based health check functionality
|
||||
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return Response.json(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return Response.json(
|
||||
{
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const prerender = false;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
|
||||
export const POST = async (request: Request) => {
|
||||
const { token, data } = await request.json();
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
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 Response.json(null, { status: 404 });
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return Response.json(hook);
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
// This route tests the queue-based health check functionality
|
||||
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return Response.json(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return Response.json(
|
||||
{
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import express from 'express';
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
// Side-effect import to keep _workflows in Nitro's dependency graph for HMR
|
||||
import '../_workflows.js';
|
||||
|
||||
@@ -10,54 +8,6 @@ app.use(express.json());
|
||||
app.use(express.text({ type: 'text/*' }));
|
||||
app.use(express.raw({ type: 'application/octet-stream' }));
|
||||
|
||||
app.post('/api/hook', async (req, res) => {
|
||||
const { token, data } = JSON.parse(req.body);
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
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 422 assuming it's the "invalid" token test case
|
||||
// NOTE: Need to return 422 because Nitro passes 404 requests to the dev server to handle.
|
||||
return res.status(422).json(null);
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return res.json(hook);
|
||||
});
|
||||
|
||||
app.post('/api/test-health-check', async (req, res) => {
|
||||
// This route tests the queue-based health check functionality
|
||||
try {
|
||||
const { endpoint = 'workflow', timeout = 30000 } = req.body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return res.status(500).json({
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/test-direct-step-call', async (req, res) => {
|
||||
// This route tests calling step functions directly outside of any workflow context
|
||||
// After the SWC compiler changes, step functions in client mode have their directive removed
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
import Fastify from 'fastify';
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
// Side-effect import to keep _workflows in Nitro's dependency graph for HMR
|
||||
import '../_workflows.js';
|
||||
|
||||
@@ -50,52 +48,6 @@ server.get('/', async (req, reply) => {
|
||||
return reply.type('text/html').send(html);
|
||||
});
|
||||
|
||||
server.post('/api/hook', async (req: any, reply) => {
|
||||
const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
||||
const { token, data } = body;
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
try {
|
||||
hook = await getHookByToken(token);
|
||||
console.log('hook', hook);
|
||||
} catch (error) {
|
||||
console.log('error during getHookByToken', error);
|
||||
return reply.code(422).send(null);
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return hook;
|
||||
});
|
||||
|
||||
server.post('/api/test-health-check', async (req: any, reply) => {
|
||||
// This route tests the queue-based health check functionality
|
||||
try {
|
||||
const { endpoint = 'workflow', timeout = 30000 } = req.body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return reply.send(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return reply.code(500).send({
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
server.post('/api/test-direct-step-call', async (req: any, reply) => {
|
||||
// This route tests calling step functions directly outside of any workflow context
|
||||
// After the SWC compiler changes, step functions in client mode have their directive removed
|
||||
|
||||
@@ -1,63 +1,9 @@
|
||||
import { Hono } from 'hono';
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
// Side-effect import to keep _workflows in Nitro's dependency graph for HMR
|
||||
import '../_workflows.js';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.post('/api/hook', async ({ req }) => {
|
||||
const { token, data } = await req.json();
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
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 422 assuming it's the "invalid" token test case
|
||||
// NOTE: Need to return 422 because Nitro passes 404 requests to the dev server to handle.
|
||||
return Response.json(null, { status: 422 });
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return Response.json(hook);
|
||||
});
|
||||
|
||||
app.post('/api/test-health-check', async ({ req }) => {
|
||||
// This route tests the queue-based health check functionality
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return Response.json(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return Response.json(
|
||||
{
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/test-direct-step-call', async ({ req }) => {
|
||||
// This route tests calling step functions directly outside of any workflow context
|
||||
// After the SWC compiler changes, step functions in client mode have their directive removed
|
||||
|
||||
@@ -1,77 +1,7 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpCode,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
|
||||
@Controller('api')
|
||||
export class AppController {
|
||||
@Post('hook')
|
||||
async resumeWorkflowHook(
|
||||
@Body() body: { token: string; data: any } | string,
|
||||
@Res() res: Response
|
||||
) {
|
||||
// Handle body as string (when Content-Type is not application/json) or object
|
||||
const parsedBody = typeof body === 'string' ? JSON.parse(body) : body;
|
||||
const { token, data } = parsedBody;
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
try {
|
||||
hook = await getHookByToken(token);
|
||||
console.log('hook', hook);
|
||||
} catch (error) {
|
||||
console.log('error during getHookByToken', error);
|
||||
// Return 422 for invalid token with null body (matching other workbench apps)
|
||||
return res.status(HttpStatus.UNPROCESSABLE_ENTITY).json(null);
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(hook);
|
||||
}
|
||||
|
||||
@Post('test-health-check')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async testHealthCheck(@Body() body: { endpoint?: string; timeout?: number }) {
|
||||
// This route tests the queue-based health check functionality
|
||||
try {
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint as 'workflow' | 'step', {
|
||||
timeout,
|
||||
});
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
throw new HttpException(
|
||||
{
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('test-direct-step-call')
|
||||
async invokeStepDirectly(@Body() body: { x: number; y: number }) {
|
||||
// This route tests calling step functions directly outside of any workflow context
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../../../example/api/hook.ts
|
||||
@@ -1,30 +0,0 @@
|
||||
// This route tests the queue-based health check functionality
|
||||
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return Response.json(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return Response.json(
|
||||
{
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
|
||||
export const POST = async (request: Request) => {
|
||||
const { token, data } = await request.json();
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
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 Response.json(null, { status: 404 });
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return Response.json(hook);
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
// This route tests the queue-based health check functionality
|
||||
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return Response.json(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return Response.json(
|
||||
{
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { defineEventHandler, readRawBody } from 'h3';
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readRawBody(event);
|
||||
const { token, data } = JSON.parse(body as string);
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
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 Response.json(null, { status: 404 });
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return Response.json(hook);
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
// This route tests the queue-based health check functionality
|
||||
|
||||
import { defineEventHandler, readRawBody } from 'h3';
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const rawBody = await readRawBody(event);
|
||||
const body = rawBody ? JSON.parse(rawBody) : {};
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return {
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
|
||||
export default async ({ req }: { req: Request }) => {
|
||||
const { token, data } = await req.json();
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
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 Response.json(null, { status: 404 });
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return Response.json(hook);
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
// This route tests the queue-based health check functionality
|
||||
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
|
||||
export default async ({ req }: { req: Request }) => {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return Response.json(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return Response.json(
|
||||
{
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { defineEventHandler, readRawBody } from 'h3';
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readRawBody(event);
|
||||
const { token, data } = JSON.parse(body as string);
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
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 Response.json(null, { status: 404 });
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return Response.json(hook);
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
// This route tests the queue-based health check functionality
|
||||
|
||||
import { defineEventHandler, readRawBody } from 'h3';
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const rawBody = await readRawBody(event);
|
||||
const body = rawBody ? JSON.parse(rawBody) : {};
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return {
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
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<ReturnType<typeof getHookByToken>>;
|
||||
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);
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
// This route tests the queue-based health check functionality
|
||||
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return Response.json(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return Response.json(
|
||||
{
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import { getHookByToken, resumeHook } from 'workflow/api';
|
||||
|
||||
export default async ({ req }: { req: Request }) => {
|
||||
const { token, data } = await req.json();
|
||||
|
||||
let hook: Awaited<ReturnType<typeof getHookByToken>>;
|
||||
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 422 assuming it's the "invalid" token test case
|
||||
// NOTE: Need to return 422 because Nitro passes 404 requests to the dev server to handle.
|
||||
return Response.json(null, { status: 422 });
|
||||
}
|
||||
|
||||
await resumeHook(hook.token, {
|
||||
...data,
|
||||
// @ts-expect-error metadata is not typed
|
||||
customData: hook.metadata?.customData,
|
||||
});
|
||||
|
||||
return Response.json(hook);
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
// This route tests the queue-based health check functionality
|
||||
|
||||
import { getWorld, healthCheck } from 'workflow/runtime';
|
||||
|
||||
export default async ({ req }: { req: Request }) => {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { endpoint = 'workflow', timeout = 30000 } = body;
|
||||
|
||||
console.log(
|
||||
`Testing queue-based health check for endpoint: ${endpoint}, timeout: ${timeout}ms`
|
||||
);
|
||||
|
||||
const world = getWorld();
|
||||
const result = await healthCheck(world, endpoint, { timeout });
|
||||
|
||||
console.log(`Health check result:`, result);
|
||||
|
||||
return Response.json(result);
|
||||
} catch (error) {
|
||||
console.error('Health check test failed:', error);
|
||||
return Response.json(
|
||||
{
|
||||
healthy: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user