# Language clients

> Implement another Workhorse runtime against the shared SQL protocol and conformance fixtures.

Every language client translates application calls into one shared SQL protocol. PostgreSQL owns
durable behavior, so clients preserve its decisions instead of recreating them in memory.

## Start with the shared contract

The fixtures under `protocol/` define canonical requests, results, transitions, and structured
errors. A client runs every applicable fixture against its test database.

The manifest lists every request, schedule, and interpreter fixture by identifier. Each client
compares that list with the fixture files and its executed cases, so a new or skipped case fails the
suite. The interpreter fixture also keeps matcher types, captured values, normalization, and error
details consistent while each language retains its own implementation.

Those fixtures matter when a producer and worker use different languages, or when deployments run
different package versions. They keep both sides on one durable contract without requiring either
client to understand the other's implementation.

Before a mutation, the client reads `workhorse.schema_version` and the fixture manifest. If the
installed schema or protocol is incompatible, the client stops before it changes durable state.

PostgreSQL decides whether enqueue accepts or coalesces work. It also owns fences, retries,
cancellation, checkpoints, timers, dependencies, child joins, signals, and human decisions.

## Keep application transactions in charge

An enqueue client wraps a connection or transaction that the application owns. The client sends
SQL through that resource, but it does not commit, roll back, close, or retain it. This lets the
application commit its data and the job together:

```python
with psycopg.connect(database_url) as connection:
    with connection.transaction():
        connection.execute("INSERT INTO orders (id) VALUES (%s)", (order_id,))
        Queue(connection).enqueue("order.accepted", {"orderId": order_id})
```

A worker must commit each claim before handler code starts. A runtime may own a pool or a dedicated
connection according to its language conventions. Notification listening needs a connection that
can retain session state. The worker runs application effects outside the claim transaction, then
settles through the fence PostgreSQL issued.

The runtime owns handler registration, bounded concurrency, notifications or polling, heartbeats,
local cancellation signals, telemetry, and graceful drain. A batch runtime may group claimed jobs,
but each member keeps its own fence and lifecycle.

## Translate names, not behavior

The public names follow each language's conventions. These names reach the same protocol boundary:

| Concern              | TypeScript            | Python                   | Go                    |
| -------------------- | --------------------- | ------------------------ | --------------------- |
| Application client   | `Queue`               | `Queue` / `AsyncQueue`   | `Queue`               |
| Operator client      | `Admin`               | `Admin` / `AsyncAdmin`   | `Admin`               |
| Single enqueue       | `Queue.enqueue`       | `Queue.enqueue`          | `Queue.Enqueue`       |
| Batch enqueue        | `Queue.enqueueMany`   | `Queue.enqueue_many`     | `Queue.EnqueueMany`   |
| Schedule sync        | `Queue.syncSchedules` | `Queue.sync_schedules`   | `Queue.SyncSchedules` |
| Cancellation         | `Queue.cancel`        | `Queue.cancel`           | `Queue.Cancel`        |
| Worker runtime       | `Worker`              | `Worker` / `AsyncWorker` | `Worker`              |
| Handler registration | `Worker.handle`       | `Worker.handle`          | `Worker.Handle`       |
| Batch registration   | `Worker.handleBatch`  | `Worker.handle_batch`    | `Worker.HandleBatch`  |
| Run loop             | `Worker.run`          | `Worker.run`             | `Worker.Run`          |
| One dispatch pass    | `Worker.runOnce`      | `Worker.run_once`        | `Worker.RunOnce`      |

If a language needs a different shape, it may adapt resource ownership and async control flow. It
must still send canonical JSON, preserve SQLSTATE errors, honor fences, and pass the shared fixtures.

## Add one capability at a time

Start at the SQL boundary and prove the behavior with the shared fixture. Then expose the public
method, translate its result and error names, and run a clean consumer test against the packaged
client. A repository import can hide missing files or dependencies that users will discover later.

When a capability is added, update its parity registry entry and explain it once in the guide that
owns the concept. This page maps the shared names, while the owning page explains the behavior.

## Continue reading

- [Transactional enqueue](/docs/enqueue) explains why enqueue uses the caller's transaction.
- [Payload contracts](/docs/contracts) explains how clients share payload schemas.
- [Workers](/docs/workers) explains what the worker process supplies.

For exact protocol and ownership rules, use the
[architecture reference](https://github.com/stablemates/workhorse/blob/main/docs/architecture.md#sql-protocol-conformance).
