Skip to main content

Store adapters overview

Store adapters provide pluggable backends for data persistence. Reqon includes a few built-in adapters and lets you supply your own.

Available adapters

AdapterDescriptionBest for
memoryIn-memory hash mapTesting, temporary data
fileJSON file storage in .reqon-data/Local development
postgrestPostgreSQL via PostgREST or SupabaseProduction
sqlFalls back to a file store in development mode; otherwise needs a PostgREST backendSee below
nosqlNot implemented; falls back to a file store in development modeSee below

sql and nosql aren't standalone database adapters. With development mode on (--dev), both write to local JSON files. Without it, sql only works if you wire up a PostgREST backend for it, and nosql has no implementation at all. There's no MongoDB or DynamoDB adapter. For production storage, use postgrest.

Quick start

mission DataSync {
// Define stores
store cache: memory("cache")
store data: file("my-data")

action Process {
get "/items"

// Write to a store
store response -> data { key: .id }
}
}

Store interface

All adapters implement this interface (see Custom adapters for the full version):

interface StoreAdapter {
// Read
get(key: string): Promise<Record<string, unknown> | null>;
list(filter?: StoreFilter): Promise<Record<string, unknown>[]>;
count(filter?: StoreFilter): Promise<number>;

// Write
set(key: string, value: Record<string, unknown>): Promise<void>;
update(key: string, value: Partial<Record<string, unknown>>): Promise<void>;

// Delete
delete(key: string): Promise<void>;
clear(): Promise<void>;
}

interface StoreFilter {
where?: Record<string, unknown>; // equality match
limit?: number;
offset?: number;
}

Writing data

Basic store

store response -> myStore

Without a key:, the store key falls back to record.id. If the record has no id (or it's empty), the step throws rather than inventing a key, so re-runs don't silently duplicate data.

With key

store response -> myStore { key: .id }

Upsert mode

Insert or update based on the key:

store response -> myStore { key: .id, upsert: true }

Partial update

Deep-merge into the existing record. partial: true behaves the same as upsert at runtime:

store response -> myStore { key: .id, partial: true }

Reading data

In for loops

for item in myStore {
// Process each item
}

With filtering

for item in myStore where .status == "active" {
// Process active items
}

Multiple conditions

for item in myStore where .status == "pending" and .priority > 5 {
// Process high-priority pending items
}

Cross-store joins

for order in orders {
for customer in customers where .id == order.customerId {
// Join data from multiple stores
}
}

Choosing an adapter

Development

// Use file for local development
store data: file("my-data")

Testing

// Use memory for tests
store testData: memory("test")

Production

// Use PostgREST for production
store data: postgrest("items")

A postgrest store needs connection options that aren't expressed in the DSL. See PostgREST store for how to wire them up.

Exporting data

Via the CLI

reqon mission.reqon --output ./output.json

This writes a single JSON file containing every store's contents, keyed by store name:

{
"customers": [ /* ... */ ],
"orders": [ /* ... */ ],
"products": [ /* ... */ ]
}

Programmatically

import { execute } from 'reqon-dsl';

const result = await execute(source);

for (const [name, store] of result.stores) {
const items = await store.list();
console.log(`${name}: ${items.length} items`);
}

Custom and production stores

There's no --store-config flag. To use a PostgREST store or a custom backend, supply a configured adapter at runtime through the stores option, keyed by the store name from the mission:

import { createStore, fromFile } from 'reqon-dsl';

const data = createStore({
type: 'postgrest',
name: 'items',
postgrest: { url: 'https://project.supabase.co/rest/v1', apiKey: 'your-anon-key' },
});

await fromFile('mission.reqon', { stores: { data } });

See PostgREST store and Custom adapters for details.

Best practices

Use descriptive names

// Good
store activeCustomers: file("active-customers")
store pendingInvoices: file("pending-invoices")

// Avoid
store data1: file("data1")
store temp: file("temp")

Provide stable keys

// Good: explicit key
store response -> items { key: .id }

// Relies on record.id, and throws if it's missing
store response -> items

Use upsert for syncs

// For incremental syncs
store response -> items { key: .id, upsert: true }

Match the adapter to the use case

Use caseRecommended
Unit testsmemory
Local devfile
CI/CDfile or memory
Productionpostgrest

Custom adapters

See Custom adapters for implementing your own store adapter.