npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

build-temporal-workflow

v0.4.0

Published

[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)

Downloads

1,089

Readme

build-temporal-workflow

MIT License

A drop-in replacement for @temporalio/worker's bundleWorkflowCode that swaps Webpack for esbuild (or Bun.build). Same API, same output, 9-11x faster builds, 94% less memory. Includes multi-queue orchestration, bundle size analysis, determinism checking with source-mapped violations, workflow export validation, Ed25519 bundle signing, CI/CD integration, and a plugin system.

Documentation

Why This Library Exists

The bundleWorkflowCode function in @temporalio/worker does two things:

  1. it resolves your workflow code's dependency graph, and
  2. it concatenates everything into a single CJS file that can run inside Temporal's V8 isolate.

That's it. There's no code splitting, no asset pipeline, no HMR, no loader ecosystem to support. It's a straightforward bundling job.

Webpack is an super-capable tool, but its generality is a liability here. It parses its own configuration schema, initializes a plugin system, builds a module graph through its own resolution algorithm, and runs multiple optimization passes—all for a bundle that must not be minified to preserve workflow determinism.

esbuild does the same job way faster because it was designed from the ground up for speed: single-pass architecture, Go's compile-time optimizations, and zero configuration overhead. This library wraps esbuild with the same plugin hooks that Temporal needs—forbidden module detection, determinism policy enforcement, module stub injection—and produces output that is structurally identical to what Webpack generates.

The practical impact shows up in three places:

  1. Test suites: If your tests create Workers (and they should—integration tests catch real bugs), each test pays the bundling cost. A suite with 20 Worker-creating tests goes from ~10s of bundling overhead to ~1s with esbuild, or ~0.6s with Bun.build. Add the in-memory cache and the second through twentieth tests pay ~0ms.
  2. Development iteration: Watch mode with esbuild's incremental rebuild is nearly instant. Webpack's watch mode works but carries the same per-build overhead.
  3. CI pipelines: Faster bundling means faster deployments. The memory savings also matter in constrained CI environments where you're running multiple jobs on shared runners.

This library is a drop-in replacement. Same function signature, same output shape, same WorkflowBundle type. Swap the import and your Workers keep working.

Installation

npm install build-temporal-workflow
# or
bun add build-temporal-workflow

Quick Start

Replace Temporal's bundleWorkflowCode with this package's version:

// Before
import { bundleWorkflowCode } from '@temporalio/worker';

// After
import { bundleWorkflowCode } from 'build-temporal-workflow';

The API is compatible, so in most cases you can swap the import and everything just works:

import { Worker } from '@temporalio/worker';
import { bundleWorkflowCode } from 'build-temporal-workflow';

const bundle = await bundleWorkflowCode({
  workflowsPath: require.resolve('./workflows'),
});

const worker = await Worker.create({
  workflowBundle: bundle,
  taskQueue: 'my-task-queue',
  // ...
});

Features

Better Error Messages

When a forbidden module is imported (like fs or http), this bundler shows the complete dependency chain:

Error: Forbidden module 'fs' found in workflow bundle

Dependency chain:
  workflows.ts
    → utils/file-helper.ts
      → node_modules/some-lib/index.js
        → fs (forbidden)

Hint: Move file operations to Activities, which run outside the workflow sandbox.

Watch Mode

Automatically rebuild when source files change:

bundle-temporal-workflow build ./src/workflows.ts -o ./dist/bundle.js --watch

Or programmatically:

import { watchWorkflowCode } from 'build-temporal-workflow';

const handle = await watchWorkflowCode(
  { workflowsPath: './src/workflows' },
  (bundle, error) => {
    if (error) {
      console.error('Build failed:', error);
    } else {
      console.log('Rebuilt!', bundle.code.length, 'bytes');
      // Hot-reload worker...
    }
  },
);

// Later, stop watching
await handle.stop();

Bundle Caching

Cache bundles in memory for dramatically faster test suites:

import { getCachedBundle } from 'build-temporal-workflow';

// First call builds the bundle (~50ms)
const bundle = await getCachedBundle({
  workflowsPath: require.resolve('./workflows'),
});

// Subsequent calls return cached bundle (~0ms)
const sameBundleAgain = await getCachedBundle({
  workflowsPath: require.resolve('./workflows'),
});

Cache is automatically invalidated when workflow files change.

Pre-built Bundle Loading

Build bundles in CI and load them at runtime without rebuilding:

import { loadBundle } from 'build-temporal-workflow';

const { bundle, warnings } = loadBundle({
  path: './dist/workflow-bundle.js',
  expectedSdkVersion: '1.14.0',
});

if (warnings?.length) {
  console.warn('Bundle warnings:', warnings);
}

const worker = await Worker.create({
  workflowBundle: bundle,
  taskQueue: 'my-queue',
});

Replay Safety Analysis

Detect non-deterministic patterns that will break workflow replay:

import { analyzeReplaySafety } from 'build-temporal-workflow';

const result = await analyzeReplaySafety({
  workflowsPath: './src/workflows',
});

for (const violation of result.violations) {
  console.warn(`${violation.file}:${violation.line} - ${violation.pattern}`);
  console.warn(`  ${violation.reason}`);
  console.warn(`  Fix: ${violation.suggestion}`);
}

Detects patterns like:

  • Date.now()—Use workflow.currentTime() instead
  • Math.random()—Use workflow.random() instead
  • setTimeout/setInterval—Use workflow.sleep() instead
  • fetch/axios—Move to Activities
  • crypto.randomBytes()—Use workflow.uuid4() or move to Activities

Cross-Runtime Support

Bundle workflows written for Deno or Bun:

const bundle = await bundleWorkflowCode({
  workflowsPath: './src/workflows.ts',
  inputFlavor: 'deno', // or 'bun' or 'auto'
  denoConfigPath: './deno.json', // For import maps
});

Supports:

  • Deno's npm: specifiers
  • URL imports (with caching)
  • Import maps
  • Bun's bun: builtins

TypeScript Path Aliases

Use TypeScript path aliases like @/utils in your workflow code:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@utils/*": ["./src/utils/*"]
    }
  }
}
// In your workflow code
import { helper } from '@/utils/helper';
import { format } from '@utils/format';

Enable path alias resolution by setting tsconfigPath:

const bundle = await bundleWorkflowCode({
  workflowsPath: './src/workflows.ts',
  tsconfigPath: true, // Auto-detect tsconfig.json
});

// Or specify a path explicitly
const bundle = await bundleWorkflowCode({
  workflowsPath: './src/workflows.ts',
  tsconfigPath: './tsconfig.json',
});

This works with both the esbuild and Bun.build backends.

Static File Imports

Import Markdown, TOML, YAML, and text files directly in your workflow code using opt-in plugins. Files are read and embedded at build time:

import { bundleWorkflowCode } from 'build-temporal-workflow';
import { textLoader, tomlLoader, yamlLoader } from 'build-temporal-workflow/plugins';

const bundle = await bundleWorkflowCode({
  workflowsPath: './src/workflows.ts',
  buildOptions: {
    plugins: [textLoader(), tomlLoader(), yamlLoader()],
  },
});

Then in your workflow code:

import readme from './README.md'; // string
import config from './config.toml'; // parsed object
import data from './data.yaml'; // parsed object
import notes from './notes.txt'; // string

| Plugin | Default Extensions | Import Result | | ---------------- | ------------------ | ------------------------------- | | textLoader | .txt, .md | String (file contents) | | markdownLoader | .md | String (file contents) | | tomlLoader | .toml | Parsed object (via smol-toml) | | yamlLoader | .yaml, .yml | Parsed object (via yaml) |

Each plugin can be imported individually or all at once:

// Individual imports
import { textLoader } from 'build-temporal-workflow/plugins/text';
import { markdownLoader } from 'build-temporal-workflow/plugins/markdown';
import { tomlLoader } from 'build-temporal-workflow/plugins/toml';
import { yamlLoader } from 'build-temporal-workflow/plugins/yaml';

// Or import all from the plugins module
import { textLoader, tomlLoader, yamlLoader } from 'build-temporal-workflow/plugins';

Custom extensions are supported:

// Handle additional extensions
textLoader({ extensions: ['.txt', '.md', '.text', '.ascii'] });
yamlLoader({ extensions: ['.yaml', '.yml', '.eyaml'] });

For TypeScript support, add the type declarations to your tsconfig.json:

{
  "compilerOptions": {
    "types": ["build-temporal-workflow/types"]
  }
}

Or use a triple-slash directive:

/// <reference types="build-temporal-workflow/types" />

Workflow Manifests

Generate manifests for debugging and validation:

import { generateManifest, compareManifests } from 'build-temporal-workflow';

const manifest = generateManifest({
  workflowsPath: './src/workflows',
  bundleCode: bundle.code,
});

console.log(manifest.workflows);
// [{ name: 'orderWorkflow', sourceHash: 'abc123', line: 42 }, ...]

// Compare manifests to detect changes
const diff = compareManifests(oldManifest, newManifest);
console.log(diff.added); // New workflows
console.log(diff.removed); // Deleted workflows
console.log(diff.changed); // Modified workflows

Performance

Measured on Apple M1 Max, Node v24.3.0, Bun 1.3.2. The @temporalio/worker column is the baseline (Webpack). All times are mean with 95% confidence intervals. 10 runs, 3 warmup, outliers filtered.

Build Time

| Fixture | @temporalio/worker | esbuild (Node) | Bun.build (Bun) | | -------------------- | -----------------: | -------------------: | -------------------: | | Small (~5 modules) | 543ms ± 41ms | 59ms ± 7ms (9x) | 29ms ± 5ms (19x) | | Medium (~20 modules) | 499ms ± 12ms | 49ms ± 8ms (10x) | 25ms ± 5ms (20x) | | Large (~50+ modules) | 537ms ± 31ms | 57ms ± 8ms (9x) | 30ms ± 4ms (18x) | | Heavy dependencies | 630ms ± 105ms | 55ms ± 5ms (11x) | 32ms ± 2ms (20x) |

Memory Usage (Peak Heap)

| Fixture | @temporalio/worker | esbuild (Node) | Savings | | -------------------- | -----------------: | -------------: | -----------: | | Small (~5 modules) | 52.25 MB | 3.03 MB | 94% less | | Medium (~20 modules) | 51.71 MB | 3.08 MB | 94% less | | Large (~50+ modules) | 54.02 MB | 3.49 MB | 94% less | | Heavy dependencies | 52.04 MB | 2.82 MB | 95% less |

To use the Bun bundler backend:

const bundle = await bundleWorkflowCode({
  workflowsPath: './src/workflows.ts',
  bundler: 'bun', // explicitly use Bun.build
});

Run benchmarks yourself:

# Quick benchmark (small fixture only)
bun run benchmark:quick

# Full benchmark suite (10 runs, 3 warmup)
bun run benchmark:full

# Custom options
bun run benchmark -r 15 -w 5 -o markdown --file BENCHMARK.md

# Disable outlier filtering
bun run benchmark --no-filter-outliers

# Compare esbuild vs Bun.build (requires Bun runtime)
bun run benchmark:bun

The benchmark suite includes statistical analysis with 95% confidence intervals, outlier detection (IQR method), and significance testing (Welch's t-test).

Build Tool Integration

Vite Plugin

Import workflow bundles directly in your Vite application using a query parameter:

// vite.config.ts
import { temporalWorkflow } from 'build-temporal-workflow/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [temporalWorkflow()],
});

Then import workflows with the ?workflow query parameter:

// src/worker.ts
import { Worker } from '@temporalio/worker';
import bundle from './workflows?workflow';

const worker = await Worker.create({
  workflowBundle: bundle,
  taskQueue: 'my-queue',
});

The plugin also supports import attributes:

import bundle from './workflows' with { type: 'workflow' };

Vite Plugin Options

temporalWorkflow({
  // Custom query parameter (default: 'workflow')
  identifier: 'temporal',

  // Pass options to bundleWorkflowCode
  bundleOptions: {
    sourceMap: 'external',
    ignoreModules: ['some-lib'],
  },
});

In development mode, the plugin caches bundles and automatically rebuilds when workflow files change.

Bun Plugin

Import workflow bundles directly when using Bun:

// Register as a runtime plugin (e.g., in a preload script)
import { temporalWorkflow } from 'build-temporal-workflow/bun';

Bun.plugin(temporalWorkflow());

Then import workflows with the ?workflow query parameter:

// src/worker.ts
import { Worker } from '@temporalio/worker';
import bundle from './workflows?workflow';

const worker = await Worker.create({
  workflowBundle: bundle,
  taskQueue: 'my-queue',
});

The plugin also works with Bun.build:

import { temporalWorkflow } from 'build-temporal-workflow/bun';

await Bun.build({
  entrypoints: ['./src/worker.ts'],
  outdir: './dist',
  plugins: [temporalWorkflow()],
});

Bun Plugin Options

temporalWorkflow({
  // Custom query parameter (default: 'workflow')
  identifier: 'temporal',

  // Pass options to bundleWorkflowCode
  bundleOptions: {
    sourceMap: 'external',
    ignoreModules: ['some-lib'],
    buildOptions: {
      plugins: [yamlLoader()], // Add file loader plugins
    },
  },
});

The plugin caches bundles by path to avoid redundant builds.

API Reference

Primary APIs

| Function | Description | | -------------------------------------- | -------------------------------------------------- | | bundleWorkflowCode(options) | Bundle workflow code for use with Temporal Worker | | watchWorkflowCode(options, callback) | Watch for changes and rebuild automatically | | getCachedBundle(options) | Get a bundle, using in-memory cache when possible | | loadBundle(options) | Load a pre-built bundle from disk | | analyzeReplaySafety(options) | Detect non-deterministic patterns in workflow code | | validateBundle(bundle, options) | Validate a bundle's structure and version | | generateManifest(options) | Generate a manifest of workflow exports | | WorkflowCodeBundler | Class-based API with build contexts and watch mode | | createConsoleLogger() | Create a console-based logger for development |

Multi-Queue & Activity Bundling

| Function | Description | Docs | | ---------------------------------- | ----------------------------------------- | ----------------------------------------------------------- | | bundleMultipleWorkflows(options) | Bundle workflows for multiple task queues | Multi-Queue Builds | | bundleActivityCode(options) | Bundle activity implementations | Multi-Queue Builds | | watchTemporalCode(options) | Coordinated watch across queues | Multi-Queue Builds |

Analysis & Validation

| Function | Description | Docs | | ---------------------------------------------- | ------------------------------------ | --------------------------------------------------------------- | | analyzeSize(bundle, budget?) | Bundle size analysis with budgets | Bundle Analysis | | compareBundle(prev, current) | Compare two bundles for changes | Bundle Analysis | | mapViolationsToSource(violations, sourceMap) | Map violations to original source | Determinism Checking | | analyzeHistorySize(code) | Detect unbounded history growth | Determinism Checking | | validateWorkflowExports(path) | Validate workflow function exports | Workflow Validation | | validateActivityTypes(path) | Validate activity type serialization | Workflow Validation | | checkWorkflowBoundaries(path) | Enforce package boundaries | Workflow Validation |

CI/CD & Signing

| Function | Description | Docs | | ----------------------------------- | -------------------------- | --------------------------------------------------------- | | generateCIReport(bundle) | CI-friendly build report | CI/CD Integration | | formatGitHubAnnotations(report) | GitHub Actions annotations | CI/CD Integration | | verifyDeterministicBuild(options) | Verify reproducible builds | CI/CD Integration | | signBundle(bundle, privateKey) | Sign a bundle with Ed25519 | Bundle Signing | | verifyBundle(signedBundle) | Verify a signed bundle | Bundle Signing | | generateSigningKeyPair() | Generate Ed25519 key pair | Bundle Signing |

Plugins & TypeScript

| Function | Description | Docs | | ------------------------------------------ | ----------------------------- | ------------------------------------------------------------------- | | createPlugin(name, configure, priority?) | Create a bundler plugin | Plugin System | | mergePlugins(...arrays) | Merge and deduplicate plugins | Plugin System | | typeCheckWorkflows(path, options?) | TypeScript type checking | TypeScript Integration | | generateWorkflowDeclarations(path, out) | Generate .d.ts files | TypeScript Integration | | bundleForTesting(options) | Test bundle with mocks | Testing | | checkSdkCompatibility(version?) | Check SDK version compat | SDK Compatibility |

CLI

bundle-temporal-workflow <command> [options]

Commands

| Command | Description | | ---------------- | ------------------------------------------------- | | build <path> | Bundle workflow code for use with Temporal Worker | | analyze <path> | Analyze bundle composition and dependencies | | check <path> | Build and validate against size budgets | | verify <path> | Verify build determinism (reproducible builds) | | sign <path> | Sign a bundle with Ed25519 | | keygen | Generate a new Ed25519 signing key pair | | doctor | Validate environment and SDK compatibility |

Build Options

| Flag | Description | | ------------------------- | --------------------------------------------- | | -o, --output <file> | Output file path (default: stdout) | | -s, --source-map <mode> | Source map mode: inline, external, none | | -m, --mode <mode> | Build mode: development, production | | -i, --ignore <module> | Ignore a module (can be repeated) | | -w, --watch | Watch for changes and rebuild | | --interceptor <path> | Add interceptor module (can be repeated) | | --payload-converter <p> | Path to custom payload converter | | --failure-converter <p> | Path to custom failure converter | | --json | Output result as JSON | | --budget <size> | Size budget (e.g., 500KB, 1MB) | | --ci | CI-friendly output mode | | --strict | Strict validation (fail on warnings) | | --private-key <path> | Ed25519 private key for signing | | --public-key <path> | Ed25519 public key for verification | | -v, --verbose | Enable verbose logging |

Examples

# Bundle workflows
bundle-temporal-workflow build ./src/workflows.ts -o ./dist/bundle.js

# Production build with external source map
bundle-temporal-workflow build ./src/workflows.ts -o ./dist/bundle.js --mode production --source-map external

# Analyze bundle composition
bundle-temporal-workflow analyze ./src/workflows.ts

# Check against a size budget
bundle-temporal-workflow check ./src/workflows.ts --budget 500KB --strict

# Verify reproducible builds
bundle-temporal-workflow verify ./src/workflows.ts

# Generate signing keys
bundle-temporal-workflow keygen

# Sign a bundle
bundle-temporal-workflow sign ./dist/bundle.js --private-key ./keys/private.key

# Check environment
bundle-temporal-workflow doctor

Configuration

BundleOptions

| Option | Type | Default | Description | | ---------------------------- | ------------------------------------- | --------------- | ----------------------------------- | | workflowsPath | string | required | Path to workflows file or directory | | mode | 'development' \| 'production' | 'development' | Build mode | | sourceMap | 'inline' \| 'external' \| 'none' | 'inline' | Source map mode | | ignoreModules | string[] | [] | Modules to exclude from bundle | | workflowInterceptorModules | string[] | [] | Interceptor module paths | | payloadConverterPath | string | - | Custom payload converter | | failureConverterPath | string | - | Custom failure converter | | logger | Logger | - | Logger for build output | | report | boolean | true | Include metadata in bundle | | inputFlavor | 'node' \| 'deno' \| 'bun' \| 'auto' | 'auto' | Input flavor | | denoConfigPath | string | - | Path to deno.json | | importMapPath | string | - | Path to import map | | tsconfigPath | string \| boolean | - | Path to tsconfig.json for aliases | | treeShaking | boolean | true | Eliminate dead code in dependencies | | bundler | 'esbuild' \| 'bun' \| 'auto' | 'auto' | Bundler backend to use | | buildOptions | esbuild.BuildOptions | - | Additional esbuild options | | plugins | BundlerPlugin[] | [] | Bundler plugins |

Enforced esbuild Options

These options are enforced and cannot be overridden to preserve workflow type inference and determinism:

| Option | Value | Reason | | ----------- | ------- | ------------------------------------ | | bundle | true | Required for workflow isolation | | format | 'cjs' | Temporal's sandbox requires CommonJS | | minify | false | Preserves workflow function names | | splitting | false | Not supported in workflow sandbox | | keepNames | true | Required for workflow type inference |

Troubleshooting

"Forbidden module 'X' found in workflow bundle"

Workflow code runs in a sandbox without access to Node.js APIs. If you're importing a module that uses Node APIs:

  1. Move to Activities—Network calls, file I/O, and other side effects should be in Activities, not Workflows
  2. Use ignoreModules—If a module is only used for types or is tree-shaken away, add it to ignoreModules
  3. Check the dependency chain—The error message shows how the forbidden module was imported

"Dynamic import found"

Dynamic import() is not allowed in workflows because the module resolved at runtime may differ between original execution and replay. Replace with static imports or move the logic to Activities.

Bundle validation fails

If validateBundleStructure fails, check that:

  1. The bundle was built with this package
  2. The bundle wasn't corrupted during write
  3. The bundle contains the required __TEMPORAL__ global

Contributing

# Install dependencies
bun install

# Run tests
bun test

# Run linter
bun run lint

# Run type checker
bun run typecheck

# Run benchmarks
bun run benchmark

License

MIT