Zum Inhalt springen
Deutsch

D1Journal

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

Defined in: src/persistence/journals/D1Journal.ts:37

Journal backed by Cloudflare D1 — SQLite at the edge, over D1’s REST API.

Behaviour lives in RelationalJournal, and the SQL is sqliteDialect’s, so the schema is identical to the local SQLite and libSQL backends: a database can move between all three without a migration. This backend cost a client and three constructors, which is the payoff the relational base was built for.

No transactions, by transport. D1’s HTTP API exposes one statement per request — no BEGIN, and no parameterized batch either (that is a Workers binding feature). The append is still correct: optimistic concurrency rests on the events primary key rejecting a racing writer, which sqliteDialect translates into JournalConcurrencyError, not on the transaction. Appends are contiguous from the head, so a losing writer fails on its first insert and writes nothing.

What the missing transaction does cost: if the connection fails partway through a multi-event append, the events already written stay written. The stream is gap-free and the next append continues from the new head, so recovery is consistent — but the caller’s error does not mean “nothing was written”. Single-event appends are unaffected. MongoDB carries the same caveat for the same reason.

Every statement is an HTTPS round-trip to Cloudflare’s API, so this is the slowest backend per operation by a wide margin. It exists for Workers-adjacent deployments where the data must live in D1, not as a general-purpose journal.

  • RelationalJournal

new D1Journal(options?): D1Journal

Defined in: src/persistence/journals/D1Journal.ts:38

D1JournalOptions = {}

D1Journal

RelationalJournal.constructor

append<E>(persistenceId, events, expectedSeq, tags?): Promise<PersistentEvent<E>[]>

Defined in: src/persistence/relational/RelationalJournal.ts:102

Append events to the stream of persistenceId, enforcing optimistic concurrency: the current highest sequence number MUST equal expectedSeq or the call throws JournalConcurrencyError. Returns the written events with their assigned sequence numbers.

E

string

readonly E[]

number

readonly string[]

Promise<PersistentEvent<E>[]>

RelationalJournal.append


close(): Promise<void>

Defined in: src/persistence/LazyStore.ts:66

Promise<void>

RelationalJournal.close


delete(persistenceId, toSeq): Promise<void>

Defined in: src/persistence/relational/RelationalJournal.ts:195

Delete events up to and including toSeq — used when compacting past a snapshot. Only ever a prefix, so what survives is a suffix that read still returns contiguously, and sequence numbers never rewind: highestSeq keeps reporting the high-water mark afterwards.

string

number

Promise<void>

RelationalJournal.delete


highestSeq(persistenceId): Promise<number>

Defined in: src/persistence/relational/RelationalJournal.ts:186

Current highest sequence number for persistenceId — 0 if no events exist.

string

Promise<number>

RelationalJournal.highestSeq


persistenceIds(): Promise<string[]>

Defined in: src/persistence/relational/RelationalJournal.ts:211

Persistence IDs currently known to the journal (useful for projections).

Promise<string[]>

RelationalJournal.persistenceIds


persistenceIdsPaginated(afterPersistenceId, limit): Promise<string[]>

Defined in: src/persistence/relational/RelationalJournal.ts:221

One ascending page of persistence ids: those strictly greater than afterPersistenceId — all of them when it is undefined — capped at limit. Returning fewer than limit means the journal is exhausted.

Optional on purpose. Not every store can enumerate ids in order: DynamoDB reaches partition keys only through a full table scan, and MongoDB’s distinct has no cursor. A journal without a sorted index over its ids omits this, and the query layer falls back to persistenceIds() plus an in-process slice — correct, just not cheaper than the full list. Implement it wherever a sorted key exists; that is what keeps currentPersistenceIdsPaginated from materialising a million rows to hand back the first 256.

Which ascending order is the backend’s business. afterPersistenceId is compared in the same order the page is sorted by, so Postgres’ collated ORDER BY and SQLite’s byte-wise one are both fine — a paginated walk only needs the order to be total and stable within one journal. What a journal must not do is mix two orders across calls, which would make the cursor skip ids.

string | undefined

number

Promise<string[]>

RelationalJournal.persistenceIdsPaginated


read<E>(persistenceId, fromSeq, toSeq?): Promise<PersistentEvent<E>[]>

Defined in: src/persistence/relational/RelationalJournal.ts:168

Return the events in [fromSeq, …, toSeq], ascending by sequence number. toSeq defaults to the current highest sequence number. Both bounds are inclusive — fromSeq is the first event returned, not an “after” cursor.

Ordering and contiguity are part of the contract, and replay enforces them (#122). Consecutive entries must differ by exactly one, every sequenceNr must be a safe integer ≥ 1, and nothing may fall outside the requested window. delete compacts a prefix, never a hole in the middle, so a gap inside the returned slice can only mean a defect — a missing ORDER BY, a half-written append, a store someone else can write. replayState raises JournalIntegrityError instead of folding it, because an actor that recovers from a shuffled or holed stream reaches a state that never existed and then fails every later persist with a JournalConcurrencyError that has no visible cause.

E

string

number

number

Promise<PersistentEvent<E>[]>

RelationalJournal.read