D1Journal
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.
Extends
Section titled “Extends”RelationalJournal
Constructors
Section titled “Constructors”Constructor
Section titled “Constructor”new D1Journal(
options?):D1Journal
Defined in: src/persistence/journals/D1Journal.ts:38
Parameters
Section titled “Parameters”options?
Section titled “options?”D1JournalOptions = {}
Returns
Section titled “Returns”D1Journal
Overrides
Section titled “Overrides”RelationalJournal.constructor
Methods
Section titled “Methods”append()
Section titled “append()”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.
Type Parameters
Section titled “Type Parameters”E
Parameters
Section titled “Parameters”persistenceId
Section titled “persistenceId”string
events
Section titled “events”readonly E[]
expectedSeq
Section titled “expectedSeq”number
readonly string[]
Returns
Section titled “Returns”Promise<PersistentEvent<E>[]>
Inherited from
Section titled “Inherited from”RelationalJournal.append
close()
Section titled “close()”close():
Promise<void>
Defined in: src/persistence/LazyStore.ts:66
Returns
Section titled “Returns”Promise<void>
Inherited from
Section titled “Inherited from”RelationalJournal.close
delete()
Section titled “delete()”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.
Parameters
Section titled “Parameters”persistenceId
Section titled “persistenceId”string
number
Returns
Section titled “Returns”Promise<void>
Inherited from
Section titled “Inherited from”RelationalJournal.delete
highestSeq()
Section titled “highestSeq()”highestSeq(
persistenceId):Promise<number>
Defined in: src/persistence/relational/RelationalJournal.ts:186
Current highest sequence number for persistenceId — 0 if no events exist.
Parameters
Section titled “Parameters”persistenceId
Section titled “persistenceId”string
Returns
Section titled “Returns”Promise<number>
Inherited from
Section titled “Inherited from”RelationalJournal.highestSeq
persistenceIds()
Section titled “persistenceIds()”persistenceIds():
Promise<string[]>
Defined in: src/persistence/relational/RelationalJournal.ts:211
Persistence IDs currently known to the journal (useful for projections).
Returns
Section titled “Returns”Promise<string[]>
Inherited from
Section titled “Inherited from”RelationalJournal.persistenceIds
persistenceIdsPaginated()
Section titled “persistenceIdsPaginated()”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.
Parameters
Section titled “Parameters”afterPersistenceId
Section titled “afterPersistenceId”string | undefined
number
Returns
Section titled “Returns”Promise<string[]>
Inherited from
Section titled “Inherited from”RelationalJournal.persistenceIdsPaginated
read()
Section titled “read()”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.
Type Parameters
Section titled “Type Parameters”E
Parameters
Section titled “Parameters”persistenceId
Section titled “persistenceId”string
fromSeq
Section titled “fromSeq”number
toSeq?
Section titled “toSeq?”number
Returns
Section titled “Returns”Promise<PersistentEvent<E>[]>
Inherited from
Section titled “Inherited from”RelationalJournal.read
