Files
7838d00f57 feat: connector workbench (#641)
* wip: connector workbench

* wip: improve workbench

* feat(workbench): improve UI with sidebar, routing, and syntax highlighting

- Replace connector dropdown with Slack-style sidebar for connector selection
- Add URL-based routing with react-router-dom for browser navigation support
- Add JSON syntax highlighting with react-syntax-highlighter
- Rework State tab layout to list/detail view with filtering
- Update terminology from "create" to "instantiate" for connectors
- Fix duplicate selection bug in lists (ID generation collision)
- Unify data table row heights across tabs
- Increase code block font sizes for readability
- Hide navigation tabs on homepage when no connector selected
- Make CW logo clickable to return to home view

Co-authored-by: Cursor <[email protected]>

* fix(workbench): use textarea for PEM/certificate config fields

Fields containing 'privateKey', 'certificate', or 'pem' in their name
now use a textarea instead of single-line input, preserving newlines
in PEM content that would otherwise be stripped.

Co-authored-by: Cursor <[email protected]>

* fix(workbench): address PR review issues

- Fix data race on idCounter using atomic.Int64
- Fix path traversal vulnerability in introspect.go
- Fix pointer comparison for DefaultAsset in baseline.go

Co-authored-by: Cursor <[email protected]>

* feat(workbench): add generic connector server for integration testing

Expose the generic connector REST API from the workbench so staging
services can install a "generic connector" pointing to the local
workbench, enabling end-to-end integration testing.

- Add /generic/* routes (accounts, balances, beneficiaries, transactions)
- Return cached data from storage instead of making fresh API calls
- Add connector selection and optional API key authentication
- Add UI panel in Debug tab for configuration

Co-authored-by: Cursor <[email protected]>

* fix(workbench): use internal registry to avoid pkg dependency

Change imports from pkg/registry to internal/connectors/plugins/registry
so the workbench can be merged before the pkg refactoring. The internal
registry will automatically delegate to pkg/registry once that refactor
lands.

Also adds light/dark mode toggle to the UI with system preference detection.

Co-authored-by: Cursor <[email protected]>

* fix(workbench): address PR review feedback

- Add ctx.Done() checks in fetch loops to exit early on client disconnect
- Refactor generic server to get connector directly instead of via context,
  providing clearer data flow as suggested in review

Co-authored-by: Cursor <[email protected]>

* fix(workbench): include built UI files for embed directive

The //go:embed ui/dist/* directive requires files to exist at build time.
Add exception to .gitignore and commit the built UI distribution files.

Co-authored-by: Cursor <[email protected]>

* fix(workbench): address golangci-lint errors

- Remove unused globalDebugTransport variable
- Remove unused parseIntParam function
- Fix printf format for models.TaskType (use %v instead of %s)
- Add error return value handling for various function calls

Co-authored-by: Cursor <[email protected]>

* fix: remove .vite cache folder from git

Co-authored-by: Cursor <[email protected]>

* fix(workbench): fix remaining errcheck issue

Co-authored-by: Cursor <[email protected]>

* fix(workbench): fix all remaining lint issues

- Add error handling for Body.Close() calls
- Remove unnecessary fmt.Sprintf wrapper
- Simplify hex character check condition

Co-authored-by: Cursor <[email protected]>

* fix(workbench): fix errcheck in cmd/workbench.go

Co-authored-by: Cursor <[email protected]>

* chore: replace ui dist build artifacts with placeholder

The //go:embed directive needs at least one file to exist.
Instead of committing the entire built UI, commit only a
.gitkeep placeholder. The server has fallback code that
displays instructions when the full UI isn't available.

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
2026-02-12 18:29:17 +00:00

7.3 KiB

Connector Workbench

A lightweight development environment for building and testing payment connectors without requiring Temporal, PostgreSQL, or other heavy infrastructure.

Why?

Running the full payments stack locally requires Docker, Temporal, PostgreSQL, and more - which can be slow and resource-intensive. The workbench provides a stripped-down alternative that lets you:

  • Develop connectors faster - No Docker, no database, just run and test
  • Debug easily - See all HTTP traffic, plugin calls, and state changes
  • Iterate quickly - Hot reload-friendly, instant feedback

How It Works

The workbench is an alternative entry point to the same binary:

payments binary
    │
    ├── payments server      ← Full production mode (Temporal + PostgreSQL)
    │
    └── payments workbench   ← Lightweight dev mode (in-memory)

Architecture

┌─────────────────────────────────────────────────────────────────┐
│  Workbench                                                       │
│                                                                 │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │   Engine    │    │   Storage   │    │  DebugStore │         │
│  │ (in-memory) │    │ (in-memory) │    │  (captures) │         │
│  └──────┬──────┘    └─────────────┘    └─────────────┘         │
│         │                                                       │
│         ▼                                                       │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  Plugin (e.g., wise, stripe)                            │   │
│  │  - Loaded via registry                                   │   │
│  │  - Same code as production                               │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

The In-Memory Engine

In production, connector operations are orchestrated by Temporal workflows:

Temporal Workflow
    └─→ Schedule activity → Retry on failure → Persist state

The workbench replaces this with direct function calls:

// engine.go - simplified orchestration
func (e *Engine) RunOneCycle(ctx context.Context) error {
    for _, task := range *tree {
        e.executeTask(ctx, task, nil)  // Direct call, no Temporal
    }
}

This means:

  • No Temporal needed - Tasks execute synchronously
  • No PostgreSQL needed - State stored in memory
  • Same connector code - Plugin interface is identical

Connector Loading

Connectors self-register via init():

// In wise/plugin.go
func init() {
    registry.RegisterPlugin("wise", connector.PluginTypePSP, func(...) {
        return New(name, logger, config)
    }, ...)
}

The workbench asks the registry for a connector by name:

// In workbench.go
plugin, err := registry.GetPlugin(connectorID, logger, "wise", name, config)

Usage

Basic Usage

# Run with inline config
payments workbench --provider=wise --config='{"apiKey":"your-api-key"}'

# Run with config file
payments workbench --provider=stripe --config-file=./stripe-config.json

# List available providers
payments workbench --list-providers

Options

Flag Description Default
--provider, -p Connector provider name (required) -
--config, -c Connector config as JSON string -
--config-file, -f Path to config JSON file -
--listen HTTP server address 127.0.0.1:8080
--auto-poll Enable automatic polling false
--poll-interval Polling interval 30s
--page-size Page size for fetch operations 25

Web UI

Once running, open http://localhost:8080 in your browser to access the debug UI:

  • Debug Log - All plugin calls, HTTP requests, state changes
  • Tasks - Workflow task tree and execution status
  • Data - Fetched accounts, payments, balances
  • HTTP Traffic - Captured requests/responses to PSP APIs

HTTP API

# Install connector (initializes workflow)
curl -X POST http://localhost:8080/api/install

# Run one fetch cycle
curl -X POST http://localhost:8080/api/cycle

# Get fetched data
curl http://localhost:8080/api/accounts
curl http://localhost:8080/api/payments
curl http://localhost:8080/api/balances

# Get debug info
curl http://localhost:8080/api/debug

Key Components

File Purpose
workbench.go Main orchestrator, lifecycle management
engine.go In-memory Temporal replacement
storage.go In-memory data storage
debug.go Debug info capture
transport.go HTTP traffic interception
server.go HTTP API and UI serving
tasks.go Task tree tracking

Relation to pkg/connector

The workbench imports from internal/models (canonical types) because it's a platform tool, not a connector. Connectors themselves should import from pkg/connector (the public API):

┌─────────────────────┐
│  pkg/connector      │  ← Public API for connectors
│  (type aliases)     │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  internal/models    │  ← Canonical types
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  tools/workbench    │  ← Uses internal/models (platform tool)
└─────────────────────┘

Example Session

# 1. Start workbench with wise connector
$ payments workbench -p wise -f ./wise-config.json
INFO HTTP debug transport installed
INFO Connector workbench starting
INFO Web UI available at http://127.0.0.1:8080

# 2. In another terminal, install and run
$ curl -X POST localhost:8080/api/install
{"status":"installed","tasks":3}

$ curl -X POST localhost:8080/api/cycle
{"status":"completed","accounts":5,"payments":23}

# 3. Check the web UI for detailed debug info
# Open http://localhost:8080

Limitations

  • No persistence - Data is lost on restart (by design)
  • No webhooks - Webhook endpoints are mocked
  • Single connector - One connector per workbench instance
  • No retries - Errors fail immediately (easier to debug)

These limitations are intentional - the workbench is for development, not production.