Skip to content

routier-collection


routier-collection / core/src / ConcurrencyDbPlugin

Class: ConcurrencyDbPlugin

Defined in: core/src/plugins/ConcurrencyDbPlugin.ts:57

Optimistic concurrency as a wrapper plugin — the whole opt-in is one wrap:

ts
class Bank extends DataStore {
    constructor() {
        super(new ConcurrencyDbPlugin(new SqlitePlugin('bank.db')));
    }
}

Nothing is declared on the schema and nothing on the collection builder: the plugin maintains a hidden __version column in the SAME tables/records as the data, entirely below the entity surface. Rows start at version 1; every update is applied ONLY IF the stored version still matches what this store last read (and bumps it); a stale write rejects the save with OptimisticConcurrencyError naming the rows instead of silently overwriting another writer. Recovery is always: re-read, reapply, save again.

How the hidden column exists without schema changes

The wrapper hands the inner plugin an AUGMENTED VIEW of each compiled schema — the same object via prototype delegation, with one synthetic __version property appended to properties. That list is exactly what the storage plugins read to build DDL, INSERT and SELECT column lists, so the column materializes and round-trips through completely unmodified plugin code. Above the wrapper the real schema is untouched, and the datastore's generated deserialize/enrich drop undeclared fields, so __version never reaches an entity a caller holds.

The synthetic property carries from: '__version' on purpose: EphemeralDataPlugin deep-copies query results with structuredClone (rather than the generated clone, which drops undeclared fields) when any property is renamed — which is what lets the hidden column survive reads from the in-process plugins so this wrapper can observe it.

What this store "read"

expected is per store instance: the version this wrapper last saw for the row, from a query result or a persist echo. A row updated WITHOUT ever being read through this store (rare — an attach of a foreign instance) has no expected value and is written unchecked, initializing its token; the row is protected from the next read on.

Enforcement and limits

The conditional check itself is performed by the INNER plugin via the EntityUpdateInfo.concurrency contract — memory, file-system, sqlite and postgresql enforce it (see specs/optimistic-concurrency.md for the not-yet list). Existing SQL tables created before the wrapper was adopted lack the column and need ALTER TABLE ... ADD COLUMN "__version" <number type> — new tables get it from the augmented DDL automatically.

Implements

Constructors

Constructor

new ConcurrencyDbPlugin(plugin): ConcurrencyDbPlugin

Defined in: core/src/plugins/ConcurrencyDbPlugin.ts:67

Parameters

plugin

IDbPlugin

Returns

ConcurrencyDbPlugin

Properties

VERSION_COLUMN

readonly static VERSION_COLUMN: "__version" = "__version"

Defined in: core/src/plugins/ConcurrencyDbPlugin.ts:59

Accessors

databaseName

Get Signature

get databaseName(): string

Defined in: core/src/plugins/ConcurrencyDbPlugin.ts:71

Uniquely identifies the database this plugin talks to, INCLUDING host or path where a bare name would collide — orders.db in two directories is two databases, and mydb on two hosts is two databases. Two instances over the same database must return the same string, in this process and in any other; two over different databases must not.

Used to scope schema subscription channels, so instances of one database (another tab, a worker) see each other's change notifications and unrelated databases holding the same schema do not.

Required rather than optional on purpose. An absent value used to fall back to scoping by schema alone, which shares one channel across every database holding that schema — the exact cross-talk this prevents, arrived at by omission. Requiring it also makes a wrapper that forgets to forward it a compile error rather than a silent regression.

Derive it, never generate it: a random value is unique per PROCESS, not per database, so another tab would never match one and cross-context notifications would stop.

Must not contain credentials — it becomes part of a channel key, so build it from host/port/database rather than returning a connection string.

Returns

string

Uniquely identifies the database this plugin talks to, INCLUDING host or path where a bare name would collide — orders.db in two directories is two databases, and mydb on two hosts is two databases. Two instances over the same database must return the same string, in this process and in any other; two over different databases must not.

Used to scope schema subscription channels, so instances of one database (another tab, a worker) see each other's change notifications and unrelated databases holding the same schema do not.

Required rather than optional on purpose. An absent value used to fall back to scoping by schema alone, which shares one channel across every database holding that schema — the exact cross-talk this prevents, arrived at by omission. Requiring it also makes a wrapper that forgets to forward it a compile error rather than a silent regression.

Derive it, never generate it: a random value is unique per PROCESS, not per database, so another tab would never match one and cross-context notifications would stop.

Must not contain credentials — it becomes part of a channel key, so build it from host/port/database rather than returning a connection string.

Implementation of

IDbPlugin.databaseName

Methods

query()

query<TRoot, TShape>(event, done): void

Defined in: core/src/plugins/ConcurrencyDbPlugin.ts:75

Executes a query operation on the database.

Type Parameters

TRoot

TRoot extends object

TShape

TShape extends unknown = TRoot

Parameters

event

DbPluginQueryEvent<TRoot, TShape>

The query event containing schema, parent, and query operation.

done

PluginEventCallbackResult<ITranslatedValue<TShape>>

Callback with the result or error.

Returns

void

Implementation of

IDbPlugin.query


bulkPersist()

bulkPersist(event, done): void

Defined in: core/src/plugins/ConcurrencyDbPlugin.ts:100

Executes bulk operations (add, update, remove) on the database.

Parameters

event

DbPluginBulkPersistEvent

The bulk operations event containing schema, parent, and changes.

done

PluginEventCallbackPartialResult<BulkPersistResult>

Callback with the result or error.

Returns

void

Implementation of

IDbPlugin.bulkPersist


destroy()

destroy(event, done): void

Defined in: core/src/plugins/ConcurrencyDbPlugin.ts:180

Destroys or cleans up the plugin, closing connections or freeing resources.

Parameters

event

DbPluginEvent

done

PluginEventCallbackResult<never>

Callback with an optional error.

Returns

void

Implementation of

IDbPlugin.destroy

Released under the MIT License.