Local Adapters
In-memory storage and transport adapters for tests and local development.
@runlane/local-adapters exports the in-memory storage and transport primitives used by local lanes and package tests. Use it when you need direct adapter access, not when you want the ready-to-use local lane.
import { createLocalStorage, createLocalTransport } from '@runlane/local-adapters'
const storage = createLocalStorage()
const transport = createLocalTransport()Both factories take no options. Each call creates a new adapter instance; local storage owns fresh in-memory state, and local transport is stateless between publish calls.
When To Use It
- core/runtime tests that need to compose a lane without importing
@runlane/lane-local - adapter contract tests that need a simple in-memory peer
- local experiments where you want to inspect storage and transport separately
For application code and examples, prefer createLocalLane() from @runlane/lane-local. The lane package wires these adapters together and reports combined capabilities.
If you do need a lane while staying at the adapter layer, compose the adapters with createLane():
import { createLane } from '@runlane/contracts'
import { createLocalStorage, createLocalTransport } from '@runlane/local-adapters'
const lane = createLane({
name: 'local-contract-test',
storage: createLocalStorage(),
transport: createLocalTransport(),
})Process-Local State
Local adapters are intentionally in-memory. They preserve Runlane storage and transport contracts inside one process, but they are not production durability:
- process exit loses all adapter state
- two calls to
createLocalStorage()do not share runs, events, cursors, or outbox rows - two processes cannot construct equivalent local adapters and reach the same state
- memory grows with retained history until you prune it or discard the process
@runlane/lane-local uses these same adapters to create a complete local lane. In a runlane dev workflow, the long-running dev process owns the process-local runtime and matching stateful CLI commands proxy into that process. Direct adapter instances do not provide that bridge.
The package depends on @runlane/contracts, not @runlane/core. Worker loops, schedules, retries, releases, cancellation, operator APIs, and task execution stay in core; local adapters only implement the storage and transport contracts.
Storage Adapter
createLocalStorage() returns a storage adapter named local-memory-storage.
| Capability | Value | What that means locally |
|---|---|---|
claimsScheduleOccurrences | true | Due schedule fires are claimed in memory with expiring claim tokens. |
durableState | false | Committed state does not survive process loss. |
enforcesIdempotency | true | Active and retained idempotency owners are tracked in memory. |
enforcesQueueConcurrency | true | Bounded queue capacity is checked against in-memory run state. |
enforcesSingleton | true | Active singleton keys are tracked in memory. |
leasesRuns | true | Workers claim, heartbeat, and release leases through storage methods. |
persistsOutbox | true | Delivery requests create outbox rows in memory. |
processLocalState | true | Only the adapter instance's process can access its state. |
prunesRuns | true | Terminal run retention can be pruned through pruneRuns(). |
readsRunHistory | true | Operator reads and run-event history are available with keyset pagination. |
Local storage keeps these in-memory indexes:
- materialized run records
- append-only event history per run
- retained idempotency owners
- active singleton owners
- schedule occurrences
- outbox messages
- pagination and prune cursors
Core supplies projected run records when it appends events. Local storage validates that the events, environment, queue, expected sequence, projected sequence, idempotency policy, and delivery intent agree before mutating state. The write path stores the event records, projected run, storage-owned key indexes, and outbox rows together; stale event sequences and stale lease or outbox ownership fail without partially mutating state.
All records crossing the adapter boundary are defensively copied. Mutating a command object after a write, or mutating a returned run, event, lease, outbox message, schedule occurrence, page, or cursor input, must not mutate storage-owned state.
Outbox And Pruning
Local storage persists an outbox row for every accepted run.delivery_requested event. claimOutboxMessages() returns due pending, failed, or expired-claim messages in deterministic publish order. markOutboxMessagesPublished(), markOutboxMessagesFailed(), and markOutboxMessagesDeadLettered() require the current claim token; stale publisher ownership fails with a storage conflict.
pruneRuns() deletes terminal runs older than the requested cutoff. It defaults to all terminal statuses when no status filter is supplied, accepts a bounded limit, and returns a cursor when more matching rows remain. Pruning removes the selected run records, event history, retained idempotency owners, and matching outbox rows. It does not delete active runs.
This is useful for exercising operator retention flows locally. It does not prove production index design, table compaction, migration behavior, or database locking.
Transport Adapter
createLocalTransport() returns a transport adapter named local-memory-transport.
| Capability | Value | What that means locally |
|---|---|---|
durableDelivery | false | Published wakeups are not stored by transport and do not survive process loss. |
messageGrouping | false | The transport does not group messages for provider FIFO semantics. |
nativeDelay | false | Delays are represented by storage due times and maintenance, not transport timers. |
orderedDelivery | false | The transport does not provide ordering guarantees. |
publishWakeups() validates the publish command and immediately returns one Published outcome per attempted wakeup. It does not enqueue messages for a worker, does not preserve wakeups for later delivery, and does not model provider acknowledgments, redrive, grouping, ordering, or native delay. Durable wakeup recovery in local development comes from storage outbox rows and the runtime tick() path, not from the in-memory transport.
Local Lane Relationship
createLocalLane() from @runlane/lane-local composes createLocalStorage() and createLocalTransport() with createLane(). Its only option is name, defaulting to local; it does not expose capability overrides such as productionDurable.
Use createLocalLane() with createRunlane() when you want a local backend for application tests or examples. Use @runlane/local-adapters directly when you are proving adapter behavior, composing a custom test lane, or exercising storage and transport methods without the core runtime.
Conformance Role
@runlane/local-adapters is the reusable local primitive. Its storage adapter runs storage conformance, and its transport adapter runs transport conformance. @runlane/lane-local composes those adapters and runs lane composition conformance.
Core runtime behavior is tested in @runlane/core against these same local adapters. @runlane/lane-local also has integration tests that pair createLocalLane() with createRunlane() to prove the full local development experience.
Do not move worker, schedule, operator, retry, release, or cancellation implementations into local adapters or the local lane package. Those remain runtime concerns over any conforming lane.