@jcbuisson/express-x-plugins 5.0.3 → 5.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -158,20 +158,23 @@ export function createOfflineElectricModel({ db, service, modelName, url, primar
|
|
|
158
158
|
|
|
159
159
|
async function reconcileSnapshot(rows) {
|
|
160
160
|
await retryLeader(() => db.transaction(async tx => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
161
|
+
// Send the snapshot once instead of making two worker round trips per row.
|
|
162
|
+
const incoming = json(rows)
|
|
163
|
+
await tx.query(`DELETE FROM electric_mutation_queue AS pending
|
|
164
|
+
USING jsonb_array_elements($2::jsonb) AS incoming(data)
|
|
165
|
+
WHERE pending.model_name = $1
|
|
166
|
+
AND pending.row_id = incoming.data->>$3
|
|
167
|
+
AND pending.acknowledged_version <= (incoming.data->>'version')::numeric`,
|
|
168
|
+
[modelName, incoming, primaryKey])
|
|
169
|
+
await tx.query(`INSERT INTO electric_local_rows (model_name, row_id, data)
|
|
170
|
+
SELECT $1, incoming.data->>$3, incoming.data
|
|
171
|
+
FROM jsonb_array_elements($2::jsonb) AS incoming(data)
|
|
172
|
+
WHERE NOT COALESCE((incoming.data->>'deleted')::boolean, false)
|
|
173
|
+
AND NOT EXISTS (SELECT 1 FROM electric_mutation_queue AS pending
|
|
174
|
+
WHERE pending.model_name = $1 AND pending.row_id = incoming.data->>$3)
|
|
175
|
+
ON CONFLICT (model_name, row_id) DO UPDATE SET data = EXCLUDED.data
|
|
176
|
+
WHERE electric_local_rows.data IS DISTINCT FROM EXCLUDED.data`,
|
|
177
|
+
[modelName, incoming, primaryKey])
|
|
175
178
|
const activeIds = rows.filter(row => !row.deleted).map(row => String(row[primaryKey]))
|
|
176
179
|
await tx.query(`DELETE FROM electric_local_rows AS local
|
|
177
180
|
WHERE local.model_name = $1 AND NOT (local.row_id = ANY($2::text[]))
|
|
@@ -66,3 +66,43 @@ test('offline writes stay local until the acknowledged Electric version arrives'
|
|
|
66
66
|
await db.close()
|
|
67
67
|
}
|
|
68
68
|
})
|
|
69
|
+
|
|
70
|
+
test('large snapshots use three queries and preserve bigint versions and tombstones', async () => {
|
|
71
|
+
const db = new PGlite()
|
|
72
|
+
const { createOfflineElectricModel } = await import('../src/electric-offline-model.mjs')
|
|
73
|
+
let queries = 0
|
|
74
|
+
const countedDb = {
|
|
75
|
+
exec: (...args) => db.exec(...args),
|
|
76
|
+
query: (...args) => db.query(...args),
|
|
77
|
+
transaction: callback => db.transaction(tx => callback({
|
|
78
|
+
query: (...args) => { queries++; return tx.query(...args) },
|
|
79
|
+
})),
|
|
80
|
+
}
|
|
81
|
+
const model = createOfflineElectricModel({
|
|
82
|
+
db: countedDb, service: {}, modelName: 'range', primaryKey: 'uid',
|
|
83
|
+
ShapeStreamClass: Stream, ShapeClass: Shape, ownsSync: true,
|
|
84
|
+
network: { onLine: true }, retryMs: 60_000,
|
|
85
|
+
})
|
|
86
|
+
try {
|
|
87
|
+
await model.prepare()
|
|
88
|
+
model.start()
|
|
89
|
+
await model.waitForIdle()
|
|
90
|
+
const rows = Array.from({ length: 100 }, () => ({
|
|
91
|
+
uid: crypto.randomUUID(), label: 'shared', version: '9007199254740993', deleted: false,
|
|
92
|
+
}))
|
|
93
|
+
Shape.current.emit({ rows })
|
|
94
|
+
await model.waitForIdle()
|
|
95
|
+
assert.equal(queries, 3)
|
|
96
|
+
assert.equal((await model.findMany()).length, 100)
|
|
97
|
+
assert.equal((await model.findMany())[0].version, '9007199254740993')
|
|
98
|
+
queries = 0
|
|
99
|
+
Shape.current.emit({ rows: [{ ...rows[0], deleted: true }, { ...rows[1], label: 'changed' }] })
|
|
100
|
+
await model.waitForIdle()
|
|
101
|
+
assert.equal(queries, 3)
|
|
102
|
+
assert.deepEqual(await model.findMany(), [{ ...rows[1], label: 'changed' }])
|
|
103
|
+
} finally {
|
|
104
|
+
model.stop()
|
|
105
|
+
await model.waitForIdle()
|
|
106
|
+
await db.close()
|
|
107
|
+
}
|
|
108
|
+
})
|