Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e22631a
feat(examples): add wa-sqlite OPFS persistence demo to offline-transa…
kevin-dp Mar 16, 2026
515fc6c
fix(examples): add Vite alias for db-sqlite-persisted-collection-core
kevin-dp Mar 16, 2026
8b6032d
Fix some type errors
kevin-dp Mar 16, 2026
6a9d96d
ci: apply automated fixes
autofix-ci[bot] Mar 16, 2026
ed79891
feat: add persisted sync metadata RFC
samwillis Mar 17, 2026
7a31dfc
feat: add persisted sync metadata implementation plan
samwillis Mar 17, 2026
a9e43d4
docs: refine persisted sync metadata design docs
samwillis Mar 17, 2026
df97fb6
feat: implement persisted sync metadata support
samwillis Mar 17, 2026
95aaff0
ci: apply automated fixes
autofix-ci[bot] Mar 17, 2026
7591ee1
fix: align persisted metadata writes with sync startup
samwillis Mar 17, 2026
8e32e88
chore: remove persisted sync metadata docs from branch
samwillis Mar 17, 2026
1959807
fix: tighten persisted sync metadata restart behavior
samwillis Mar 17, 2026
985ade1
feat: complete persisted sync metadata coverage
samwillis Mar 17, 2026
fbb87e3
ci: apply automated fixes
autofix-ci[bot] Mar 17, 2026
9c5c4e6
fix: encode replay deltas with persisted serializer
samwillis Mar 17, 2026
a1a2f2e
ci: apply automated fixes
autofix-ci[bot] Mar 17, 2026
53740ff
fix: harden persisted startup and resume metadata handling
samwillis Mar 19, 2026
d989651
chore: add changeset for persisted metadata follow-ups
samwillis Mar 19, 2026
7c9dbad
Merge remote-tracking branch 'origin/main' into persisted-sync-metada…
samwillis Mar 19, 2026
615e599
ci: apply automated fixes
autofix-ci[bot] Mar 19, 2026
ac8b5d9
fix(electron-persistence): bridge metadata RPC methods across IPC
samwillis Mar 19, 2026
6488f72
ci: apply automated fixes
autofix-ci[bot] Mar 19, 2026
52eaac4
Merge remote-tracking branch 'origin/main' into persisted-sync-metadata
samwillis Mar 19, 2026
badc1c5
chore(expo-e2e): add metro-runtime lockfile entry
samwillis Mar 20, 2026
c0b43b7
Merge remote-tracking branch 'origin/persisted-sync-metadata' into pe…
samwillis Mar 20, 2026
4f3df54
fix(lockfile): drop stale metro-runtime specifier from expo e2e app
samwillis Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/persisted-metadata-followups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@tanstack/db': patch
'@tanstack/db-sqlite-persisted-collection-core': patch
'@tanstack/electric-db-collection': patch
---

fix(persistence): harden persisted startup, truncate metadata semantics, and resume identity matching

- Restore persisted wrapper `markReady` fallback behavior so startup failures do not leave collections stuck in loading state
- Replace load cancellation reference identity tracking with deterministic load keys for `loadSubset` / `unloadSubset`
- Document intentional truncate behavior where collection-scoped metadata writes are preserved across truncate transactions
- Tighten SQLite `applied_tx` migration handling to only ignore duplicate-column add errors
- Stabilize Electric shape identity serialization so persisted resume compatibility does not depend on object key insertion order
48 changes: 48 additions & 0 deletions packages/db-electron-sqlite-persisted-collection/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ type ElectronMainPersistenceAdapter = PersistenceAdapter<
ElectronPersistedRow,
ElectronPersistedKey
> & {
loadCollectionMetadata?: (
collectionId: string,
) => Promise<Array<{ key: string; value: unknown }>>
scanRows?: (
collectionId: string,
options?: { metadataOnly?: boolean },
) => Promise<
Array<{
key: ElectronPersistedKey
value: ElectronPersistedRow
metadata?: unknown
}>
>
pullSince?: (
collectionId: string,
fromRowVersion: number,
Expand Down Expand Up @@ -110,6 +123,41 @@ async function executeRequestAgainstAdapter(
}
}

case `loadCollectionMetadata`: {
if (!adapter.loadCollectionMetadata) {
throw new InvalidPersistedCollectionConfigError(
`loadCollectionMetadata is not supported by the configured electron persistence adapter`,
)
}
const result = await adapter.loadCollectionMetadata(request.collectionId)
return {
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
requestId: request.requestId,
method: request.method,
ok: true,
result,
}
}

case `scanRows`: {
if (!adapter.scanRows) {
throw new InvalidPersistedCollectionConfigError(
`scanRows is not supported by the configured electron persistence adapter`,
)
}
const result = await adapter.scanRows(
request.collectionId,
request.payload.options,
)
return {
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
requestId: request.requestId,
method: request.method,
ok: true,
result,
}
}

case `applyCommittedTx`: {
await adapter.applyCommittedTx(request.collectionId, request.payload.tx)
return {
Expand Down
14 changes: 14 additions & 0 deletions packages/db-electron-sqlite-persisted-collection/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export type ElectronPersistenceResolution = {

export type ElectronPersistenceMethod =
| `loadSubset`
| `loadCollectionMetadata`
| `scanRows`
| `applyCommittedTx`
| `ensureIndex`
| `markIndexRemoved`
Expand All @@ -30,6 +32,12 @@ export type ElectronPersistencePayloadMap = {
options: LoadSubsetOptions
ctx?: { requiredIndexSignatures?: ReadonlyArray<string> }
}
loadCollectionMetadata: {}
scanRows: {
options?: {
metadataOnly?: boolean
}
}
applyCommittedTx: {
tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>
}
Expand All @@ -48,6 +56,12 @@ export type ElectronPersistencePayloadMap = {

export type ElectronPersistenceResultMap = {
loadSubset: Array<{ key: ElectronPersistedKey; value: ElectronPersistedRow }>
loadCollectionMetadata: Array<{ key: string; value: unknown }>
scanRows: Array<{
key: ElectronPersistedKey
value: ElectronPersistedRow
metadata?: unknown
}>
applyCommittedTx: null
ensureIndex: null
markIndexRemoved: null
Expand Down
29 changes: 29 additions & 0 deletions packages/db-electron-sqlite-persisted-collection/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ type ElectronRendererResolvedAdapter<
T extends object,
TKey extends string | number = string | number,
> = PersistedCollectionPersistence<T, TKey>[`adapter`] & {
loadCollectionMetadata: (
collectionId: string,
) => Promise<Array<{ key: string; value: unknown }>>
scanRows: (
collectionId: string,
options?: { metadataOnly?: boolean },
) => Promise<Array<{ key: TKey; value: T; metadata?: unknown }>>
pullSince: (
collectionId: string,
fromRowVersion: number,
Expand Down Expand Up @@ -202,6 +209,28 @@ function createResolvedRendererAdapter<
resolution,
)
},
loadCollectionMetadata: async (
collectionId: string,
): Promise<Array<{ key: string; value: unknown }>> => {
return executeRequest(
`loadCollectionMetadata`,
collectionId,
{},
resolution,
)
},
scanRows: async (
collectionId: string,
options?: { metadataOnly?: boolean },
): Promise<Array<{ key: TKey; value: T; metadata?: unknown }>> => {
const result = await executeRequest(
`scanRows`,
collectionId,
{ options },
resolution,
)
return result as Array<{ key: TKey; value: T; metadata?: unknown }>
},
ensureIndex: async (
collectionId: string,
signature: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ test(`renderer persistence requires invoke transport`, () => {
latestRowVersion: 0,
},
})
case `loadCollectionMetadata`:
return Promise.resolve({
v: 1,
requestId: request.requestId,
method: request.method,
ok: true,
result: [],
})
case `scanRows`:
return Promise.resolve({
v: 1,
requestId: request.requestId,
method: request.method,
ok: true,
result: [],
})
default:
return Promise.resolve({
v: 1,
Expand Down
Loading
Loading