Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
3 changes: 3 additions & 0 deletions packages/hono/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# workflow/hono

The docs have moved! Refer to them [here](https://useworkflow.dev/)
39 changes: 39 additions & 0 deletions packages/hono/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@workflow/hono",
"version": "4.0.0-beta.1",
"description": "Hono 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/hono"
},
"exports": {
".": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "tsc --build --clean && rm -rf dist"
},
"dependencies": {
"@swc/core": "1.11.24",
"@workflow/cli": "workspace:*",
"@workflow/core": "workspace:*",
"@workflow/swc-plugin": "workspace:*",
"hono": "^4.9.10",
"workflow": "4.0.1-beta.4"
},
"devDependencies": {
"@types/node": "catalog:",
"@workflow/tsconfig": "workspace:*"
}
}
58 changes: 58 additions & 0 deletions packages/hono/src/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import { BaseBuilder } from '@workflow/cli/dist/lib/builders/base-builder.js';
import { VercelBuildOutputAPIBuilder } from '@workflow/cli/dist/lib/builders/vercel-build-output-api.js';
import type { WorkflowConfig } from '@workflow/cli/dist/lib/config/types.js';

const CommonBuildOptions = {
dirs: ['workflows'],
buildTarget: 'next' as const, // unused in base
stepsBundlePath: '', // unused in base
workflowsBundlePath: '', // unused in base
webhookBundlePath: '', // unused in base
};

export class LocalBuilder extends BaseBuilder {
#outDir: string;

constructor(config: Partial<WorkflowConfig>) {
const workingDir = process.cwd();
const outDir = join(workingDir, '.workflow');
super({
...CommonBuildOptions,
...config,
workingDir,
clientBundlePath: join(workingDir, '_workflows.js'),
});
this.#outDir = outDir;
}

override async build(): Promise<void> {
const inputFiles = await this.getInputFiles();
await mkdir(this.#outDir, { recursive: true });

await this.createWorkflowsBundle({
outfile: join(this.#outDir, 'workflows.mjs'),
bundleFinalOutput: false,
format: 'esm',
inputFiles,
});

await this.createStepsBundle({
outfile: join(this.#outDir, 'steps.mjs'),
externalizeNonSteps: true,
format: 'esm',
inputFiles,
});

await this.createWebhookBundle({
outfile: join(this.#outDir, 'webhook.mjs'),
bundle: false,
});

this.buildClientLibrary();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.buildClientLibrary();
await this.buildClientLibrary();

The buildClientLibrary() call is missing the await keyword, causing the build method to potentially return before the client library is finished building. This creates a race condition.

View Details

Analysis

Missing await in LocalBuilder.build() causes race condition with client library generation

What fails: LocalBuilder.build() completes and returns before buildClientLibrary() finishes writing the client library file to disk. Callers awaiting build() may immediately try to access the client library before it's been written, causing file not found errors.

How to reproduce:

// In any code using LocalBuilder
const builder = new LocalBuilder(config);
await builder.build();
// At this point, the client library file may not exist yet because buildClientLibrary() is not awaited
const clientLib = fs.readFileSync(config.clientBundlePath, 'utf-8');

Result: Potential FileNotFoundError when trying to access the client library file immediately after build() completes.

Expected: The build() method should not resolve until all async operations, including the client library generation, are complete. Per the method signature async build(): Promise<void>, callers expect a fully completed build when the promise resolves.

Evidence:

  • buildClientLibrary() is defined as protected async buildClientLibrary(): Promise<void> in BaseBuilder
  • The same operation is correctly awaited in StandaloneBuilder: await this.buildClientLibrary();
  • The same operation is correctly awaited in VercelBuildOutputAPIBuilder: await this.buildClientLibrary();
  • All other async operations in LocalBuilder.build() are properly awaited (lines 34, 41, 48)

}
}

// TODO: Implement Vercel builder
export class VercelBuilder extends VercelBuildOutputAPIBuilder {}
50 changes: 50 additions & 0 deletions packages/hono/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import type { WorkflowConfig } from '@workflow/cli/dist/lib/config/types';
import { Hono } from 'hono';
import { LocalBuilder } from './builder';

async function loadBundle(outdir: string, filename: string) {
const path = join(process.cwd(), outdir, filename);
const module = await import(pathToFileURL(path).href);
const handler = module.POST;
return { handler };
}

export async function createWorkflowRoutes(
options?: Partial<WorkflowConfig>
): Promise<Hono> {
options ??= {};
const app = new Hono();
const outDir = '.workflow';

const isVercelDeploy = process.env.VERCEL === '1';

if (isVercelDeploy) {
// TODO: Implement Vercel builder
} else {
await new LocalBuilder(options).build();

// Load bundles
const stepsModule = await loadBundle(outDir, 'steps.mjs');
const workflowsModule = await loadBundle(outDir, 'workflows.mjs');
const webhookModule = await loadBundle(outDir, 'webhook.mjs');

// Register routes directly on one app
app.post('/v1/step', async (c) => {
return await stepsModule.handler(c.req.raw);
});

app.post('/v1/flow', async (c) => {
return await workflowsModule.handler(c.req.raw);
});

app.all('/v1/webhook/:token', async (c) => {
const handler = webhookModule.handler;
if (!handler) return c.text('Method not supported', 405);
return await handler(c.req.raw);
});
}

return app;
}
12 changes: 12 additions & 0 deletions packages/hono/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"]
}
Loading