From 91099d34bc87b3f3ea5cfca8b127b525934d25a6 Mon Sep 17 00:00:00 2001 From: Steve Date: Wed, 12 Nov 2025 22:14:34 -0500 Subject: [PATCH 1/7] Updated useLiveIncrementalQuery hook to maintain object references after applying changes. --- .changeset/healthy-feet-exercise.md | 5 ++ packages/pglite/src/live/index.ts | 105 ++++++++++++++-------------- 2 files changed, 59 insertions(+), 51 deletions(-) create mode 100644 .changeset/healthy-feet-exercise.md diff --git a/.changeset/healthy-feet-exercise.md b/.changeset/healthy-feet-exercise.md new file mode 100644 index 000000000..10e61cfc0 --- /dev/null +++ b/.changeset/healthy-feet-exercise.md @@ -0,0 +1,5 @@ +--- +'@electric-sql/pglite': patch +--- + +Updated useLiveIncrementalQuery hook to maintain object references after applying changes. diff --git a/packages/pglite/src/live/index.ts b/packages/pglite/src/live/index.ts index b698dd587..0a4b02a39 100644 --- a/packages/pglite/src/live/index.ts +++ b/packages/pglite/src/live/index.ts @@ -4,24 +4,21 @@ import type { Results, Transaction, } from '../interface' +import { debounceMutex, formatQuery, uuid } from '../utils.js' import type { - LiveQueryOptions, - LiveIncrementalQueryOptions, + Change, + LiveChanges, LiveChangesOptions, + LiveIncrementalQueryOptions, LiveNamespace, LiveQuery, - LiveChanges, - Change, + LiveQueryOptions, LiveQueryResults, } from './interface' -import { uuid, formatQuery, debounceMutex } from '../utils.js' export type { - LiveNamespace, - LiveQuery, - LiveChanges, - Change, - LiveQueryResults, + Change, LiveChanges, LiveNamespace, + LiveQuery, LiveQueryResults } from './interface.js' const MAX_RETRIES = 5 @@ -326,7 +323,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { ...( await tx.query(` SELECT column_name, data_type, udt_name - FROM information_schema.columns + FROM information_schema.columns WHERE table_name = 'live_query_${id}_view' `) ).rows, @@ -349,63 +346,63 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { curr AS (SELECT LAG("${key}") OVER () as __after__, * FROM live_query_${id}_state${curr}), data_diff AS ( -- INSERT operations: Include all columns - SELECT + SELECT 'INSERT' AS __op__, ${columns - .map( - ({ column_name }) => - `curr."${column_name}" AS "${column_name}"`, - ) - .join(',\n')}, + .map( + ({ column_name }) => + `curr."${column_name}" AS "${column_name}"`, + ) + .join(',\n')}, ARRAY[]::text[] AS __changed_columns__ FROM curr LEFT JOIN prev ON curr.${key} = prev.${key} WHERE prev.${key} IS NULL UNION ALL -- DELETE operations: Include only the primary key - SELECT + SELECT 'DELETE' AS __op__, ${columns - .map(({ column_name, data_type, udt_name }) => { - if (column_name === key) { - return `prev."${column_name}" AS "${column_name}"` - } else { - return `NULL${data_type === 'USER-DEFINED' ? `::${udt_name}` : ``} AS "${column_name}"` - } - }) - .join(',\n')}, + .map(({ column_name, data_type, udt_name }) => { + if (column_name === key) { + return `prev."${column_name}" AS "${column_name}"` + } else { + return `NULL${data_type === 'USER-DEFINED' ? `::${udt_name}` : ``} AS "${column_name}"` + } + }) + .join(',\n')}, ARRAY[]::text[] AS __changed_columns__ FROM prev LEFT JOIN curr ON prev.${key} = curr.${key} WHERE curr.${key} IS NULL UNION ALL -- UPDATE operations: Include only changed columns - SELECT + SELECT 'UPDATE' AS __op__, ${columns - .map(({ column_name, data_type, udt_name }) => - column_name === key - ? `curr."${column_name}" AS "${column_name}"` - : `CASE - WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" + .map(({ column_name, data_type, udt_name }) => + column_name === key + ? `curr."${column_name}" AS "${column_name}"` + : `CASE + WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" THEN curr."${column_name}" ELSE NULL${data_type === 'USER-DEFINED' ? `::${udt_name}` : ``} END AS "${column_name}"`, - ) - .join(',\n')}, + ) + .join(',\n')}, ARRAY(SELECT unnest FROM unnest(ARRAY[${columns - .filter(({ column_name }) => column_name !== key) - .map( - ({ column_name }) => - `CASE - WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" - THEN '${column_name}' - ELSE NULL + .filter(({ column_name }) => column_name !== key) + .map( + ({ column_name }) => + `CASE + WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" + THEN '${column_name}' + ELSE NULL END`, - ) - .join( - ', ', - )}]) WHERE unnest IS NOT NULL) AS __changed_columns__ + ) + .join( + ', ', + )}]) WHERE unnest IS NOT NULL) AS __changed_columns__ FROM curr INNER JOIN prev ON curr.${key} = prev.${key} WHERE NOT (curr IS NOT DISTINCT FROM prev) @@ -440,7 +437,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { await pg.transaction(async (tx) => { // Populate the state table await tx.exec(` - INSERT INTO live_query_${id}_state${stateSwitch} + INSERT INTO live_query_${id}_state${stateSwitch} SELECT * FROM live_query_${id}_view; `) @@ -478,10 +475,10 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { runChangeCallbacks(callbacks, [ ...(reset ? [ - { - __op__: 'RESET' as const, - }, - ] + { + __op__: 'RESET' as const, + }, + ] : []), ...changes!.rows, ]) @@ -576,6 +573,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { : [] const rowsMap: Map = new Map() const afterMap: Map = new Map() + const rowCache = new WeakMap() let lastRows: T[] = [] let firstRun = true @@ -634,7 +632,12 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { break } // Remove the __after__ key from the exposed row - const cleanObj = { ...obj } + const cleanObj = rowCache.get(obj) ?? { ...obj } + + if (!rowCache.has(obj)) { + rowCache.set(obj, cleanObj) + } + delete cleanObj.__after__ rows.push(cleanObj) lastKey = nextKey From 5bd9e7758b632d84653f6603884b6b6f33de8f68 Mon Sep 17 00:00:00 2001 From: Steve Date: Wed, 12 Nov 2025 22:34:46 -0500 Subject: [PATCH 2/7] Updated `live` tests. --- packages/pglite/tests/live.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/pglite/tests/live.test.ts b/packages/pglite/tests/live.test.ts index f955ee9d7..f6837c121 100644 --- a/packages/pglite/tests/live.test.ts +++ b/packages/pglite/tests/live.test.ts @@ -460,6 +460,9 @@ await testEsmCjsAndDTC(async (importType) => { await new Promise((resolve) => eventTarget.addEventListener('change', resolve, { once: true }), ) + + // Check that references haven't changed between updates. + expect(initialResults.rows[0]).toBe(updatedResults.rows[0]) expect(updatedResults.rows).toEqual([ { id: 1, number: 10 }, From fdd0ba36b420f3bf65e36dd0976bd4876ae7e1ab Mon Sep 17 00:00:00 2001 From: Steve Date: Wed, 12 Nov 2025 22:35:52 -0500 Subject: [PATCH 3/7] Fixed formatting. --- packages/pglite/src/live/index.ts | 101 +++++++++++++++--------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/packages/pglite/src/live/index.ts b/packages/pglite/src/live/index.ts index 0a4b02a39..d60a3e5d6 100644 --- a/packages/pglite/src/live/index.ts +++ b/packages/pglite/src/live/index.ts @@ -4,21 +4,24 @@ import type { Results, Transaction, } from '../interface' -import { debounceMutex, formatQuery, uuid } from '../utils.js' import type { - Change, - LiveChanges, - LiveChangesOptions, + LiveQueryOptions, LiveIncrementalQueryOptions, + LiveChangesOptions, LiveNamespace, LiveQuery, - LiveQueryOptions, + LiveChanges, + Change, LiveQueryResults, } from './interface' +import { uuid, formatQuery, debounceMutex } from '../utils.js' export type { - Change, LiveChanges, LiveNamespace, - LiveQuery, LiveQueryResults + LiveNamespace, + LiveQuery, + LiveChanges, + Change, + LiveQueryResults, } from './interface.js' const MAX_RETRIES = 5 @@ -323,7 +326,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { ...( await tx.query(` SELECT column_name, data_type, udt_name - FROM information_schema.columns + FROM information_schema.columns WHERE table_name = 'live_query_${id}_view' `) ).rows, @@ -346,63 +349,63 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { curr AS (SELECT LAG("${key}") OVER () as __after__, * FROM live_query_${id}_state${curr}), data_diff AS ( -- INSERT operations: Include all columns - SELECT + SELECT 'INSERT' AS __op__, ${columns - .map( - ({ column_name }) => - `curr."${column_name}" AS "${column_name}"`, - ) - .join(',\n')}, + .map( + ({ column_name }) => + `curr."${column_name}" AS "${column_name}"`, + ) + .join(',\n')}, ARRAY[]::text[] AS __changed_columns__ FROM curr LEFT JOIN prev ON curr.${key} = prev.${key} WHERE prev.${key} IS NULL UNION ALL -- DELETE operations: Include only the primary key - SELECT + SELECT 'DELETE' AS __op__, ${columns - .map(({ column_name, data_type, udt_name }) => { - if (column_name === key) { - return `prev."${column_name}" AS "${column_name}"` - } else { - return `NULL${data_type === 'USER-DEFINED' ? `::${udt_name}` : ``} AS "${column_name}"` - } - }) - .join(',\n')}, + .map(({ column_name, data_type, udt_name }) => { + if (column_name === key) { + return `prev."${column_name}" AS "${column_name}"` + } else { + return `NULL${data_type === 'USER-DEFINED' ? `::${udt_name}` : ``} AS "${column_name}"` + } + }) + .join(',\n')}, ARRAY[]::text[] AS __changed_columns__ FROM prev LEFT JOIN curr ON prev.${key} = curr.${key} WHERE curr.${key} IS NULL UNION ALL -- UPDATE operations: Include only changed columns - SELECT + SELECT 'UPDATE' AS __op__, ${columns - .map(({ column_name, data_type, udt_name }) => - column_name === key - ? `curr."${column_name}" AS "${column_name}"` - : `CASE - WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" + .map(({ column_name, data_type, udt_name }) => + column_name === key + ? `curr."${column_name}" AS "${column_name}"` + : `CASE + WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" THEN curr."${column_name}" ELSE NULL${data_type === 'USER-DEFINED' ? `::${udt_name}` : ``} END AS "${column_name}"`, - ) - .join(',\n')}, + ) + .join(',\n')}, ARRAY(SELECT unnest FROM unnest(ARRAY[${columns - .filter(({ column_name }) => column_name !== key) - .map( - ({ column_name }) => - `CASE - WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" - THEN '${column_name}' - ELSE NULL + .filter(({ column_name }) => column_name !== key) + .map( + ({ column_name }) => + `CASE + WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" + THEN '${column_name}' + ELSE NULL END`, - ) - .join( - ', ', - )}]) WHERE unnest IS NOT NULL) AS __changed_columns__ + ) + .join( + ', ', + )}]) WHERE unnest IS NOT NULL) AS __changed_columns__ FROM curr INNER JOIN prev ON curr.${key} = prev.${key} WHERE NOT (curr IS NOT DISTINCT FROM prev) @@ -437,7 +440,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { await pg.transaction(async (tx) => { // Populate the state table await tx.exec(` - INSERT INTO live_query_${id}_state${stateSwitch} + INSERT INTO live_query_${id}_state${stateSwitch} SELECT * FROM live_query_${id}_view; `) @@ -475,10 +478,10 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { runChangeCallbacks(callbacks, [ ...(reset ? [ - { - __op__: 'RESET' as const, - }, - ] + { + __op__: 'RESET' as const, + }, + ] : []), ...changes!.rows, ]) @@ -633,11 +636,11 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { } // Remove the __after__ key from the exposed row const cleanObj = rowCache.get(obj) ?? { ...obj } - + if (!rowCache.has(obj)) { rowCache.set(obj, cleanObj) } - + delete cleanObj.__after__ rows.push(cleanObj) lastKey = nextKey From 1d546cb7ad11d73eb02fbe61da5ffda2d66e4d6f Mon Sep 17 00:00:00 2001 From: Steve Date: Wed, 12 Nov 2025 22:52:43 -0500 Subject: [PATCH 4/7] Improved changeset. --- .changeset/healthy-feet-exercise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/healthy-feet-exercise.md b/.changeset/healthy-feet-exercise.md index 10e61cfc0..fd37bf4d4 100644 --- a/.changeset/healthy-feet-exercise.md +++ b/.changeset/healthy-feet-exercise.md @@ -2,4 +2,4 @@ '@electric-sql/pglite': patch --- -Updated useLiveIncrementalQuery hook to maintain object references after applying changes. +Updated live.incrementalQuery to maintain object references across changes. From f63eb2b5eeda6421b23f5fd42e238c522bbc9c83 Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 13 Nov 2025 18:04:00 -0500 Subject: [PATCH 5/7] Bug fix for incrementalQuery overwriting items at same location. --- .changeset/olive-clouds-knock.md | 5 +++ packages/pglite/src/live/index.ts | 29 +++++++------- packages/pglite/src/utils.ts | 48 +++++++++++++++++++++++ packages/pglite/tests/live.test.ts | 63 ++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 .changeset/olive-clouds-knock.md diff --git a/.changeset/olive-clouds-knock.md b/.changeset/olive-clouds-knock.md new file mode 100644 index 000000000..f27ccbc9b --- /dev/null +++ b/.changeset/olive-clouds-knock.md @@ -0,0 +1,5 @@ +--- +'@electric-sql/pglite': patch +--- + +Fixed truncated result set when overwriting incrementalQuery items at same location within a transaction. diff --git a/packages/pglite/src/live/index.ts b/packages/pglite/src/live/index.ts index d60a3e5d6..50ea44f54 100644 --- a/packages/pglite/src/live/index.ts +++ b/packages/pglite/src/live/index.ts @@ -14,7 +14,7 @@ import type { Change, LiveQueryResults, } from './interface' -import { uuid, formatQuery, debounceMutex } from '../utils.js' +import { uuid, DoublyLinkedList, formatQuery, debounceMutex } from '../utils.js' export type { LiveNamespace, @@ -575,7 +575,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { ? [callback] : [] const rowsMap: Map = new Map() - const afterMap: Map = new Map() + const idList = new DoublyLinkedList() const rowCache = new WeakMap() let lastRows: T[] = [] let firstRun = true @@ -595,28 +595,27 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { switch (op) { case 'RESET': rowsMap.clear() - afterMap.clear() + idList.clear() break case 'INSERT': rowsMap.set(obj[key], obj) - afterMap.set(obj.__after__, obj[key]) + + idList.insert(obj[key], obj.__after__) break - case 'DELETE': { - const oldObj = rowsMap.get(obj[key]) + case 'DELETE': rowsMap.delete(obj[key]) - // null is the starting point, we don't delete it as another insert - // may have happened thats replacing it - if (oldObj.__after__ !== null) { - afterMap.delete(oldObj.__after__) - } + + idList.delete(obj[key]) break - } case 'UPDATE': { - const newObj = { ...(rowsMap.get(obj[key]) ?? {}) } + const oldObj = rowsMap.get(obj[key]); + const newObj = { ...(oldObj ?? {}) }; + for (const columnName of changedColumns) { newObj[columnName] = obj[columnName] if (columnName === '__after__') { - afterMap.set(obj.__after__, obj[key]) + idList.delete(obj[key]) + idList.insert(newObj[key], newObj.__after__) } } rowsMap.set(obj[key], newObj) @@ -629,7 +628,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { const rows: T[] = [] let lastKey: any = null for (let i = 0; i < rowsMap.size; i++) { - const nextKey = afterMap.get(lastKey) + const nextKey = idList.getAfter(lastKey) const obj = rowsMap.get(nextKey) if (!obj) { break diff --git a/packages/pglite/src/utils.ts b/packages/pglite/src/utils.ts index 0cbde2f34..53913d57d 100644 --- a/packages/pglite/src/utils.ts +++ b/packages/pglite/src/utils.ts @@ -247,3 +247,51 @@ export function toPostgresName(input: string): string { } return output } + +export class DoublyLinkedList { + #afterMap = new Map(); + #beforeMap = new Map(); + + clear() { + this.#afterMap.clear(); + this.#beforeMap.clear(); + } + + getAfter(afterId: T) { + return this.#afterMap.get(afterId); + } + + insert(id: T, afterId: T) { + const existingNext = this.#afterMap.get(afterId); + if (existingNext !== undefined) { + this.#afterMap.set(id, existingNext); + this.#beforeMap.set(existingNext, id); + } + this.#afterMap.set(afterId, id); + this.#beforeMap.set(id, afterId); + } + + delete(id: T) { + const prevKey = this.#beforeMap.get(id); + const nextKey = this.#afterMap.get(id); + + if (prevKey != null) { + if (nextKey != null) { + this.#afterMap.set(prevKey, nextKey); + this.#beforeMap.set(nextKey, prevKey); + } else { + this.#afterMap.delete(prevKey); + } + } else { + if (nextKey == null) { + this.#afterMap.delete(prevKey!); + } + this.#beforeMap.delete(nextKey!); + } + + this.#afterMap.delete(id); + this.#beforeMap.delete(id); + } + + +} diff --git a/packages/pglite/tests/live.test.ts b/packages/pglite/tests/live.test.ts index f6837c121..6a7e3f232 100644 --- a/packages/pglite/tests/live.test.ts +++ b/packages/pglite/tests/live.test.ts @@ -501,6 +501,69 @@ await testEsmCjsAndDTC(async (importType) => { ]) }) + it('basic live incremental query with overwrites', async () => { + const db = await PGlite.create({ + extensions: { live }, + }) + + await db.exec(` + CREATE TABLE IF NOT EXISTS testTable ( + id INT PRIMARY KEY, + number INT + ); + `) + + await db.exec(` + CREATE TABLE IF NOT EXISTS childTable ( + id INT PRIMARY KEY, + parentId INT + ); + `) + + await db.exec(` + INSERT INTO testTable (id, number) + SELECT i, i*10 AS number FROM generate_series(1, 5) AS t(i); + `) + + let updatedResults + const eventTarget = new EventTarget() + + const { initialResults, unsubscribe } = await db.live.incrementalQuery( + 'SELECT * FROM testTable WHERE number <= 50 ORDER BY number;', + [], + 'id', + (result) => { + updatedResults = result + eventTarget.dispatchEvent(new Event('change')) + }, + ) + + expect(initialResults.rows).toEqual([ + { id: 1, number: 10 }, + { id: 2, number: 20 }, + { id: 3, number: 30 }, + { id: 4, number: 40 }, + { id: 5, number: 50 }, + ]) + + await db.transaction(async (tx) => { + await tx.exec('DELETE FROM testTable WHERE id = 3;') + await tx.exec('INSERT INTO testTable (id, number) VALUES (6, 35);') + }) + + await new Promise((resolve) => + eventTarget.addEventListener('change', resolve, { once: true }), + ) + + expect(updatedResults.rows).toEqual([ + { id: 1, number: 10 }, + { id: 2, number: 20 }, + { id: 6, number: 35 }, + { id: 4, number: 40 }, + { id: 5, number: 50 }, + ]) + }) + it('basic live incremental query with limit 1', async () => { const db = await PGlite.create({ extensions: { live }, From 3add64068bda499c669da7fd103e73be0d692125 Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 13 Nov 2025 20:30:37 -0500 Subject: [PATCH 6/7] Fixed style issues. --- packages/pglite/src/utils.ts | 48 ++++++++++++++---------------- packages/pglite/tests/live.test.ts | 2 +- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/packages/pglite/src/utils.ts b/packages/pglite/src/utils.ts index 53913d57d..c4b7bd62e 100644 --- a/packages/pglite/src/utils.ts +++ b/packages/pglite/src/utils.ts @@ -1,5 +1,5 @@ -import type { PGliteInterface, Transaction } from './interface.js' import { serialize as serializeProtocol } from '@electric-sql/pg-protocol' +import type { PGliteInterface, Transaction } from './interface.js' import { parseDescribeStatementResults } from './parse.js' import { TEXT } from './types.js' @@ -249,49 +249,47 @@ export function toPostgresName(input: string): string { } export class DoublyLinkedList { - #afterMap = new Map(); - #beforeMap = new Map(); + #afterMap = new Map() + #beforeMap = new Map() clear() { - this.#afterMap.clear(); - this.#beforeMap.clear(); + this.#afterMap.clear() + this.#beforeMap.clear() } getAfter(afterId: T) { - return this.#afterMap.get(afterId); + return this.#afterMap.get(afterId) } insert(id: T, afterId: T) { - const existingNext = this.#afterMap.get(afterId); + const existingNext = this.#afterMap.get(afterId) if (existingNext !== undefined) { - this.#afterMap.set(id, existingNext); - this.#beforeMap.set(existingNext, id); + this.#afterMap.set(id, existingNext) + this.#beforeMap.set(existingNext, id) } - this.#afterMap.set(afterId, id); - this.#beforeMap.set(id, afterId); + this.#afterMap.set(afterId, id) + this.#beforeMap.set(id, afterId) } delete(id: T) { - const prevKey = this.#beforeMap.get(id); - const nextKey = this.#afterMap.get(id); + const prevKey = this.#beforeMap.get(id) + const nextKey = this.#afterMap.get(id) - if (prevKey != null) { - if (nextKey != null) { - this.#afterMap.set(prevKey, nextKey); - this.#beforeMap.set(nextKey, prevKey); + if (prevKey !== null && prevKey !== undefined) { + if (nextKey !== null && nextKey !== undefined) { + this.#afterMap.set(prevKey, nextKey) + this.#beforeMap.set(nextKey, prevKey) } else { - this.#afterMap.delete(prevKey); + this.#afterMap.delete(prevKey) } } else { - if (nextKey == null) { - this.#afterMap.delete(prevKey!); + if (nextKey === null || prevKey === undefined) { + this.#afterMap.delete(prevKey!) } - this.#beforeMap.delete(nextKey!); + this.#beforeMap.delete(nextKey!) } - this.#afterMap.delete(id); - this.#beforeMap.delete(id); + this.#afterMap.delete(id) + this.#beforeMap.delete(id) } - - } diff --git a/packages/pglite/tests/live.test.ts b/packages/pglite/tests/live.test.ts index 6a7e3f232..783cc8521 100644 --- a/packages/pglite/tests/live.test.ts +++ b/packages/pglite/tests/live.test.ts @@ -528,7 +528,7 @@ await testEsmCjsAndDTC(async (importType) => { let updatedResults const eventTarget = new EventTarget() - const { initialResults, unsubscribe } = await db.live.incrementalQuery( + const { initialResults } = await db.live.incrementalQuery( 'SELECT * FROM testTable WHERE number <= 50 ORDER BY number;', [], 'id', From 9900549e36f2dfb1652f7a122f614655cfc1d80b Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 13 Nov 2025 20:33:39 -0500 Subject: [PATCH 7/7] Fixed prettier issues. --- packages/pglite/src/live/index.ts | 44 +++++++++++++++--------------- packages/pglite/tests/live.test.ts | 8 +++--- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/pglite/src/live/index.ts b/packages/pglite/src/live/index.ts index 50ea44f54..484999af3 100644 --- a/packages/pglite/src/live/index.ts +++ b/packages/pglite/src/live/index.ts @@ -4,23 +4,23 @@ import type { Results, Transaction, } from '../interface' +import { debounceMutex, DoublyLinkedList, formatQuery, uuid } from '../utils.js' import type { - LiveQueryOptions, - LiveIncrementalQueryOptions, + Change, + LiveChanges, LiveChangesOptions, + LiveIncrementalQueryOptions, LiveNamespace, LiveQuery, - LiveChanges, - Change, + LiveQueryOptions, LiveQueryResults, } from './interface' -import { uuid, DoublyLinkedList, formatQuery, debounceMutex } from '../utils.js' export type { + Change, + LiveChanges, LiveNamespace, LiveQuery, - LiveChanges, - Change, LiveQueryResults, } from './interface.js' @@ -326,7 +326,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { ...( await tx.query(` SELECT column_name, data_type, udt_name - FROM information_schema.columns + FROM information_schema.columns WHERE table_name = 'live_query_${id}_view' `) ).rows, @@ -349,7 +349,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { curr AS (SELECT LAG("${key}") OVER () as __after__, * FROM live_query_${id}_state${curr}), data_diff AS ( -- INSERT operations: Include all columns - SELECT + SELECT 'INSERT' AS __op__, ${columns .map( @@ -363,7 +363,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { WHERE prev.${key} IS NULL UNION ALL -- DELETE operations: Include only the primary key - SELECT + SELECT 'DELETE' AS __op__, ${columns .map(({ column_name, data_type, udt_name }) => { @@ -380,14 +380,14 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { WHERE curr.${key} IS NULL UNION ALL -- UPDATE operations: Include only changed columns - SELECT + SELECT 'UPDATE' AS __op__, ${columns .map(({ column_name, data_type, udt_name }) => column_name === key ? `curr."${column_name}" AS "${column_name}"` - : `CASE - WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" + : `CASE + WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" THEN curr."${column_name}" ELSE NULL${data_type === 'USER-DEFINED' ? `::${udt_name}` : ``} END AS "${column_name}"`, @@ -398,9 +398,9 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { .map( ({ column_name }) => `CASE - WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" - THEN '${column_name}' - ELSE NULL + WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" + THEN '${column_name}' + ELSE NULL END`, ) .join( @@ -440,7 +440,7 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { await pg.transaction(async (tx) => { // Populate the state table await tx.exec(` - INSERT INTO live_query_${id}_state${stateSwitch} + INSERT INTO live_query_${id}_state${stateSwitch} SELECT * FROM live_query_${id}_view; `) @@ -608,9 +608,9 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { idList.delete(obj[key]) break case 'UPDATE': { - const oldObj = rowsMap.get(obj[key]); - const newObj = { ...(oldObj ?? {}) }; - + const oldObj = rowsMap.get(obj[key]) + const newObj = { ...(oldObj ?? {}) } + for (const columnName of changedColumns) { newObj[columnName] = obj[columnName] if (columnName === '__after__') { @@ -635,11 +635,11 @@ const setup = async (pg: PGliteInterface, _emscriptenOpts: any) => { } // Remove the __after__ key from the exposed row const cleanObj = rowCache.get(obj) ?? { ...obj } - + if (!rowCache.has(obj)) { rowCache.set(obj, cleanObj) } - + delete cleanObj.__after__ rows.push(cleanObj) lastKey = nextKey diff --git a/packages/pglite/tests/live.test.ts b/packages/pglite/tests/live.test.ts index 783cc8521..5307f1078 100644 --- a/packages/pglite/tests/live.test.ts +++ b/packages/pglite/tests/live.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' await testEsmCjsAndDTC(async (importType) => { @@ -460,7 +460,7 @@ await testEsmCjsAndDTC(async (importType) => { await new Promise((resolve) => eventTarget.addEventListener('change', resolve, { once: true }), ) - + // Check that references haven't changed between updates. expect(initialResults.rows[0]).toBe(updatedResults.rows[0]) @@ -1358,9 +1358,9 @@ await testEsmCjsAndDTC(async (importType) => { const { initialResults, unsubscribe } = await db.live.query( `SELECT id, - statement + statement FROM testTable - WHERE + WHERE statement ILIKE '%pglite%' ORDER BY id;`, [],