Runlane
Reference

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 local storage without an external transport and reports composed capabilities.

If you do need a lane while staying at the adapter layer, compose local storage with explicit transport delivery:

import { createLane, LaneDeliveryMode } from '@runlane/contracts'
import { createLocalStorage, createLocalTransport } from '@runlane/local-adapters'

const lane = createLane({
  delivery: { mode: LaneDeliveryMode.Transport, transport: createLocalTransport() },
  name: 'local-contract-test',
  storage: createLocalStorage(),
})

Pass delivery: { mode: LaneDeliveryMode.StoragePolling } when the test should exercise storage polling without a transport.

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 local storage 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.

CapabilityValueWhat that means locally
claimsScheduleOccurrencestrueDue schedule fires are claimed in memory with expiring claim tokens.
durableStatefalseCommitted state does not survive process loss.
durableStepstrueDurable step completions are checkpointed in the adapter instance.
exportsObservationstrueObservation records and exporter checkpoints are kept in memory.
enforcesIdempotencytrueActive and retained idempotency owners are tracked in memory.
enforcesQueueConcurrencytrueBounded queue capacity is checked against in-memory run state.
enforcesSingletontrueActive singleton keys are tracked in memory.
leasesRunstrueWorkers claim, heartbeat, and release leases through storage methods.
persistsOutboxtrueDelivery requests can create outbox rows in memory when the command asks.
processLocalStatetrueOnly the adapter instance's process can access its state.
prunesRunstrueTerminal run retention can be pruned through pruneRuns().
readsRunHistorytrueOperator reads and run-event history are available with keyset pagination.

Local storage keeps these in-memory indexes:

  • materialized run records
  • durable step completions
  • durable observation records and exporter checkpoints
  • append-only event history per run
  • released wait conditions and running attempt deadlines
  • 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, outbox rows, and observation records together; stale event sequences and stale lease or outbox ownership fail without partially mutating state.

The local observation stream mirrors the production storage contract for deterministic tests and local exporters. Run event records and first-time durable step completions become sanitized observation records with stable ids and opaque string stream positions. scanObservationRecords(), getObservationCheckpoint(), and advanceObservationCheckpoint() operate only inside the adapter instance; process exit loses both the stream and checkpoint state. Checkpoints can only advance to cursors returned from existing records in the same environment.

All records crossing the adapter boundary are defensively copied. Mutating a command object after a write, or mutating a returned run, event, durable step, 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 accepted run.delivery_requested events when the command requests delivery outbox persistence. Transport delivery lanes request it; storage-polling lanes do not. 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.

CapabilityValueWhat that means locally
durableDeliveryfalsePublished wakeups are not stored by transport and do not survive process loss.
messageGroupingfalseThe transport does not group messages for provider FIFO semantics.
nativeDelayfalseDelays are represented by storage due times and maintenance, not transport timers.
orderedDeliveryfalseThe 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. When a custom test lane includes this transport, recovery 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() without a transport. 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 local storage with storage polling 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.

On this page