survos/record-store

Provider-neutral contracts and models for application-backend record stores (Grist, Quickbase, ...). Framework-agnostic: no Symfony kernel, no container.

Maintainers

Package info

github.com/survos/record-store

pkg:composer/survos/record-store

Transparency log

Fund package maintenance!

kbond

Statistics

Installs: 18

Dependents: 5

Suggesters: 0

Stars: 0

Open Issues: 0

2.26.1 2026-08-27 12:26 UTC

This package is auto-updated.

Last update: 2026-08-27 12:27:23 UTC


README

Provider-neutral contracts and models for application-backend record stores — Grist, Quickbase, and anything else that looks like "tables of records behind an HTTP API".

Framework-agnostic on purpose: no Symfony kernel, no container, no dependencies beyond PHP itself. That is what lets the same code run inside a Symfony application and inside a WordPress plugin.

composer require survos/record-store

What is here

This package has no adapters and talks to nothing. It defines the shape that adapters implement:

Contract\RecordStoreAdapterInterface the whole surface: schema + read + write
Contract\SchemaReaderInterface schema(ApplicationReference): ApplicationSchema
Contract\RecordReaderInterface query(TableReference, RecordQuery): RecordPage
Contract\RecordWriterInterface upsert(TableReference, UpsertRequest): WriteResult
Contract\AdapterFactoryInterface supports(driver), create(ConnectionConfiguration)
Registry\RecordStoreRegistry resolves a named connection and table to an adapter

Plus the value objects the contracts speak in: Record, RecordPage, RecordQuery, RecordSort, SortDirection, UpsertRequest, WriteResult, FieldSchema, FieldType, TableReference, TableSchema, ApplicationReference, ApplicationSchema, ConnectionConfiguration, ProviderCapability.

The point of it

Two backends, one interface:

use Survos\RecordStore\Contract\RecordStoreAdapterInterface;
use Survos\RecordStore\Model\{Record, RecordQuery, TableReference, UpsertRequest};

function publishBio(RecordStoreAdapterInterface $store, TableReference $table, int|string $id, string $bio): void
{
    $store->upsert($table, new UpsertRequest(
        records: [new Record(fields: ['Bio' => $bio], id: $id)],
    ));
}

That function works unchanged against Grist and against Quickbase. Choosing between them is configuration, not code — which is the entire reason this package exists separately from the adapters.

Capabilities, not assumptions

Backends differ, and pretending otherwise produces surprises. ProviderCapability lets an adapter declare what it actually supports, and Exception\UnsupportedRecordStoreOperation is thrown rather than silently approximated when something is asked for that a backend cannot do.

$adapter->capabilities();   // ProviderCapability[] — SchemaRead, RecordRead, RecordWrite, RecordUpsert
$adapter->provider();       // 'grist', 'quickbase', …

Implementations

Writing another adapter means implementing RecordStoreAdapterInterface and an AdapterFactoryInterface. Nothing else here needs to change.