diff --git a/.changeset/poor-beds-push.md b/.changeset/poor-beds-push.md new file mode 100644 index 000000000..6278d56fb --- /dev/null +++ b/.changeset/poor-beds-push.md @@ -0,0 +1,8 @@ +--- +"@tanstack/db-collections": patch +"@tanstack/db-example-react-todo": patch +"@tanstack/db": patch +--- + +- [Breaking change for the Electric Collection]: Use numbers for txid +- misc type fixes diff --git a/docs/overview.md b/docs/overview.md index f13a065b4..c30ed7713 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -575,9 +575,6 @@ insert([ { text: "Buy groceries", completed: false }, { text: "Walk dog", completed: false }, ]) - -// Insert with custom key -insert({ text: "Buy groceries" }, { key: "grocery-task" }) ``` ##### `update` diff --git a/examples/react/todo/src/App.tsx b/examples/react/todo/src/App.tsx index 2021a1155..04ff3f3b1 100644 --- a/examples/react/todo/src/App.tsx +++ b/examples/react/todo/src/App.tsx @@ -158,7 +158,7 @@ const createTodoCollection = (type: CollectionType) => { } = transaction.mutations[0].modified const response = await api.todos.create(modified) - return { txid: String(response.txid) } + return { txid: response.txid } }, onUpdate: async ({ transaction }) => { const txids = await Promise.all( @@ -166,11 +166,11 @@ const createTodoCollection = (type: CollectionType) => { const { original, changes } = mutation const response = await api.todos.update(original.id, changes) - return { txid: String(response.txid) } + return response.txid }) ) - return { txid: String(txids[0]!.txid) } + return { txid: txids } }, onDelete: async ({ transaction }) => { const txids = await Promise.all( @@ -178,11 +178,11 @@ const createTodoCollection = (type: CollectionType) => { const { original } = mutation const response = await api.todos.delete(original.id) - return { txid: String(response.txid) } + return response.txid }) ) - return { txid: String(txids[0]!.txid) } + return { txid: txids } }, }) ) @@ -265,18 +265,18 @@ const createConfigCollection = (type: CollectionType) => { onInsert: async ({ transaction }) => { const modified = transaction.mutations[0].modified const response = await api.config.create(modified) - return { txid: String(response.txid) } + return { txid: response.txid } }, onUpdate: async ({ transaction }) => { const txids = await Promise.all( transaction.mutations.map(async (mutation) => { const { original, changes } = mutation const response = await api.config.update(original.id, changes) - return { txid: String(response.txid) } + return response.txid }) ) - return { txid: String(txids[0]) } + return { txid: txids } }, }) ) @@ -302,18 +302,18 @@ const createConfigCollection = (type: CollectionType) => { onInsert: async ({ transaction }) => { const modified = transaction.mutations[0].modified const response = await api.config.create(modified) - return { txid: String(response.txid) } + return { txid: response.txid } }, onUpdate: async ({ transaction }) => { const txids = await Promise.all( transaction.mutations.map(async (mutation) => { const { original, changes } = mutation const response = await api.config.update(original.id, changes) - return { txid: String(response.txid) } + return response.txid }) ) - return { txid: String(txids[0]) } + return { txid: txids } }, }) ) diff --git a/examples/react/todo/src/api/server.ts b/examples/react/todo/src/api/server.ts index 536b2cdc2..3dd1e9390 100644 --- a/examples/react/todo/src/api/server.ts +++ b/examples/react/todo/src/api/server.ts @@ -8,6 +8,7 @@ import { validateUpdateTodo, } from "../db/validation" import type { Express } from "express" +import type { Txid } from "@tanstack/db-collections" // Create Express app const app: Express = express() @@ -23,15 +24,19 @@ app.get(`/api/health`, (req, res) => { }) // Generate a transaction ID -async function generateTxId(tx: any): Promise { - const result = await tx`SELECT txid_current() as txid` +async function generateTxId(tx: any): Promise { + // The ::xid cast strips off the epoch, giving you the raw 32-bit value + // that matches what PostgreSQL sends in logical replication streams + // (and then exposed through Electric which we'll match against + // in the client). + const result = await tx`SELECT pg_current_xact_id()::xid::text as txid` const txid = result[0]?.txid if (txid === undefined) { throw new Error(`Failed to get transaction ID`) } - return String(txid) + return parseInt(txid, 10) } // ===== TODOS API ===== @@ -75,7 +80,7 @@ app.post(`/api/todos`, async (req, res) => { try { const todoData = validateInsertTodo(req.body) - let txid!: string + let txid!: Txid const newTodo = await sql.begin(async (tx) => { txid = await generateTxId(tx) @@ -102,7 +107,7 @@ app.put(`/api/todos/:id`, async (req, res) => { const { id } = req.params const todoData = validateUpdateTodo(req.body) - let txid!: string + let txid!: Txid const updatedTodo = await sql.begin(async (tx) => { txid = await generateTxId(tx) @@ -139,7 +144,7 @@ app.delete(`/api/todos/:id`, async (req, res) => { try { const { id } = req.params - let txid!: string + let txid!: Txid await sql.begin(async (tx) => { txid = await generateTxId(tx) @@ -210,7 +215,7 @@ app.post(`/api/config`, async (req, res) => { console.log(`POST /api/config`, req.body) const configData = validateInsertConfig(req.body) - let txid!: string + let txid!: Txid const newConfig = await sql.begin(async (tx) => { txid = await generateTxId(tx) @@ -237,7 +242,7 @@ app.put(`/api/config/:id`, async (req, res) => { const { id } = req.params const configData = validateUpdateConfig(req.body) - let txid!: string + let txid!: Txid const updatedConfig = await sql.begin(async (tx) => { txid = await generateTxId(tx) @@ -274,7 +279,7 @@ app.delete(`/api/config/:id`, async (req, res) => { try { const { id } = req.params - let txid!: string + let txid!: Txid await sql.begin(async (tx) => { txid = await generateTxId(tx) diff --git a/examples/react/todo/src/api/write-to-pg.ts b/examples/react/todo/src/api/write-to-pg.ts deleted file mode 100644 index aee885093..000000000 --- a/examples/react/todo/src/api/write-to-pg.ts +++ /dev/null @@ -1,115 +0,0 @@ -import type postgres from "postgres" -import type { PendingMutation } from "@tanstack/react-db" - -/** - * Get the table name from the relation metadata - */ -function getTableName(relation?: Array): string { - if (!relation || relation.length < 2) { - throw new Error(`could not find the table name`) - } - - // The table name is typically the second element in the relation array - // e.g. ['public', 'todos'] -> 'todos' - return relation[1]! -} - -/** - * Process an array of PendingMutations and write to the database - */ -export async function processMutations( - sql: postgres.Sql>, - pendingMutations: Array -): Promise { - return await sql.begin(async (tx) => { - // Get the transaction ID - const result = await tx`SELECT txid_current() as txid` - const txid = result[0]?.txid - - if (txid === undefined) { - throw new Error(`Failed to get transaction ID`) - } - - // Process each mutation in order - for (const mutation of pendingMutations) { - // Get the table name from the relation metadata - const tableName = getTableName( - mutation.syncMetadata.relation as Array | undefined - ) - - // Get the primary key columns from metadata - const primaryKey = (mutation.syncMetadata.primaryKey as - | Array - | undefined) || [`id`] - - // Process based on operation type - switch (mutation.type) { - case `insert`: { - const columns = Object.keys(mutation.modified) - const values = Object.values(mutation.modified) - const placeholders = values.map((_, i) => `$${i + 1}`).join(`, `) - - await tx.unsafe( - `INSERT INTO ${tableName} (${columns.join(`, `)}) VALUES (${placeholders})`, - values - ) - break - } - - case `update`: { - // Build SET clause - const setColumns = Object.keys(mutation.changes) - const setValues = Object.values(mutation.changes) - const setClause = setColumns - .map((col, i) => `${col} = $${i + 1}`) - .join(`, `) - - // Build WHERE clause for primary key columns starting after SET values - const whereClause = primaryKey - .map((column, i) => `${column} = $${i + setValues.length + 1}`) - .join(` AND `) - - // Combine all values - const allValues = [ - ...setValues, - ...primaryKey.map( - (k) => (mutation.original as Record)[k] - ), - ] - - await tx.unsafe( - `UPDATE ${tableName} - SET ${setClause} - WHERE ${whereClause}`, - allValues - ) - break - } - - case `delete`: { - // Build WHERE clause for primary key columns - const whereClause = primaryKey - .map((column, i) => `${column} = $${i + 1}`) - .join(` AND `) - - // Extract primary key values in same order as columns - const primaryKeyValues = primaryKey.map( - (k) => (mutation.original as Record)[k] - ) - - await tx.unsafe( - `DELETE FROM ${tableName} - WHERE ${whereClause}`, - primaryKeyValues - ) - break - } - - default: - throw new Error(`Unknown operation type: ${mutation.type}`) - } - } - - return Number(txid) - }) -} diff --git a/packages/db-collections/package.json b/packages/db-collections/package.json index f9e764332..57cb7543c 100644 --- a/packages/db-collections/package.json +++ b/packages/db-collections/package.json @@ -3,13 +3,15 @@ "description": "A collection for (aspirationally) every way of loading your data", "version": "0.0.21", "dependencies": { + "@standard-schema/spec": "^1.0.0", "@tanstack/db": "workspace:*", "@tanstack/query-core": "^5.75.7", - "@standard-schema/spec": "^1.0.0", - "@tanstack/store": "^0.7.0" + "@tanstack/store": "^0.7.0", + "debug": "^4.4.1" }, "devDependencies": { "@electric-sql/client": "1.0.0", + "@types/debug": "^4.1.12", "@vitest/coverage-istanbul": "^3.0.9" }, "exports": { diff --git a/packages/db-collections/src/electric.ts b/packages/db-collections/src/electric.ts index 78dd2fcb8..9205fcd3f 100644 --- a/packages/db-collections/src/electric.ts +++ b/packages/db-collections/src/electric.ts @@ -4,6 +4,7 @@ import { isControlMessage, } from "@electric-sql/client" import { Store } from "@tanstack/store" +import DebugModule from "debug" import type { CollectionConfig, DeleteMutationFnParams, @@ -21,6 +22,13 @@ import type { ShapeStreamOptions, } from "@electric-sql/client" +const debug = DebugModule.debug(`ts/db:electric`) + +/** + * Type representing a transaction ID in Electric SQL + */ +export type Txid = number + // The `InferSchemaOutput` and `ResolveType` are copied from the `@tanstack/db` package // but we modified `InferSchemaOutput` slightly to restrict the schema output to `Row` // This is needed in order for `GetExtensions` to be able to infer the parser extensions type from the schema @@ -77,11 +85,11 @@ export interface ElectricCollectionConfig< /** * Optional asynchronous handler function called before an insert operation - * Must return an object containing a txid string or array of txids + * Must return an object containing a txid number or array of txids * @param params Object containing transaction and collection information * @returns Promise resolving to an object with txid or txids * @example - * // Basic Electric insert handler - MUST return { txid: string } + * // Basic Electric insert handler - MUST return { txid: number } * onInsert: async ({ transaction }) => { * const newItem = transaction.mutations[0].modified * const result = await api.todos.create({ @@ -125,15 +133,15 @@ export interface ElectricCollectionConfig< */ onInsert?: ( params: InsertMutationFnParams> - ) => Promise<{ txid: string | Array }> + ) => Promise<{ txid: Txid | Array }> /** * Optional asynchronous handler function called before an update operation - * Must return an object containing a txid string or array of txids + * Must return an object containing a txid number or array of txids * @param params Object containing transaction and collection information * @returns Promise resolving to an object with txid or txids * @example - * // Basic Electric update handler - MUST return { txid: string } + * // Basic Electric update handler - MUST return { txid: number } * onUpdate: async ({ transaction }) => { * const { original, changes } = transaction.mutations[0] * const result = await api.todos.update({ @@ -173,15 +181,15 @@ export interface ElectricCollectionConfig< */ onUpdate?: ( params: UpdateMutationFnParams> - ) => Promise<{ txid: string | Array }> + ) => Promise<{ txid: Txid | Array }> /** * Optional asynchronous handler function called before a delete operation - * Must return an object containing a txid string or array of txids + * Must return an object containing a txid number or array of txids * @param params Object containing transaction and collection information * @returns Promise resolving to an object with txid or txids * @example - * // Basic Electric delete handler - MUST return { txid: string } + * // Basic Electric delete handler - MUST return { txid: number } * onDelete: async ({ transaction }) => { * const mutation = transaction.mutations[0] * const result = await api.todos.delete({ @@ -230,7 +238,7 @@ export interface ElectricCollectionConfig< */ onDelete?: ( params: DeleteMutationFnParams> - ) => Promise<{ txid: string | Array }> + ) => Promise<{ txid: Txid | Array }> } function isUpToDateMessage>( @@ -242,14 +250,14 @@ function isUpToDateMessage>( // Check if a message contains txids in its headers function hasTxids>( message: Message -): message is Message & { headers: { txids?: Array } } { +): message is Message & { headers: { txids?: Array } } { return `txids` in message.headers && Array.isArray(message.headers.txids) } /** * Type for the awaitTxId utility function */ -export type AwaitTxIdFn = (txId: string, timeout?: number) => Promise +export type AwaitTxIdFn = (txId: Txid, timeout?: number) => Promise /** * Electric collection utilities type @@ -272,7 +280,7 @@ export function electricCollectionOptions< TSchema extends StandardSchemaV1 = never, TFallback extends Row = Row, >(config: ElectricCollectionConfig) { - const seenTxids = new Store>(new Set([])) + const seenTxids = new Store>(new Set([])) const sync = createElectricSync>( config.shapeOptions, { @@ -282,22 +290,20 @@ export function electricCollectionOptions< /** * Wait for a specific transaction ID to be synced - * @param txId The transaction ID to wait for as a string + * @param txId The transaction ID to wait for as a number * @param timeout Optional timeout in milliseconds (defaults to 30000ms) * @returns Promise that resolves when the txId is synced */ const awaitTxId: AwaitTxIdFn = async ( - txId: string, + txId: Txid, timeout: number = 30000 ): Promise => { - if (typeof txId !== `string`) { + debug(`awaitTxId called with txid %d`, txId) + if (typeof txId !== `number`) { throw new TypeError( - `Expected string in awaitTxId, received ${typeof txId}` + `Expected number in awaitTxId, received ${typeof txId}` ) } - if (!/^\d+$/.test(txId)) { - throw new Error(`txId must contain only numbers`) - } const hasTxid = seenTxids.state.has(txId) if (hasTxid) return true @@ -310,6 +316,7 @@ export function electricCollectionOptions< const unsubscribe = seenTxids.subscribe(() => { if (seenTxids.state.has(txId)) { + debug(`awaitTxId found match for txid %o`, txId) clearTimeout(timeoutId) unsubscribe() resolve(true) @@ -328,7 +335,7 @@ export function electricCollectionOptions< // Runtime check (that doesn't follow type) // eslint-disable-next-line const handlerResult = (await config.onInsert!(params)) ?? {} - const txid = (handlerResult as { txid?: string | Array }).txid + const txid = (handlerResult as { txid?: Txid | Array }).txid if (!txid) { throw new Error( @@ -356,7 +363,7 @@ export function electricCollectionOptions< // Runtime check (that doesn't follow type) // eslint-disable-next-line const handlerResult = (await config.onUpdate!(params)) ?? {} - const txid = (handlerResult as { txid?: string | Array }).txid + const txid = (handlerResult as { txid?: Txid | Array }).txid if (!txid) { throw new Error( @@ -426,7 +433,7 @@ export function electricCollectionOptions< function createElectricSync>( shapeOptions: ShapeStreamOptions>, options: { - seenTxids: Store> + seenTxids: Store> } ): SyncConfig { const { seenTxids } = options @@ -470,7 +477,7 @@ function createElectricSync>( signal: abortController.signal, }) let transactionStarted = false - const newTxids = new Set() + const newTxids = new Set() unsubscribeStream = stream.subscribe((messages: Array>) => { let hasUpToDate = false @@ -521,7 +528,10 @@ function createElectricSync>( // Always commit txids when we receive up-to-date, regardless of transaction state seenTxids.setState((currentTxids) => { - const clonedSeen = new Set(currentTxids) + const clonedSeen = new Set(currentTxids) + if (newTxids.size > 0) { + debug(`new txids synced from pg %O`, Array.from(newTxids)) + } newTxids.forEach((txid) => clonedSeen.add(txid)) newTxids.clear() return clonedSeen diff --git a/packages/db-collections/src/index.ts b/packages/db-collections/src/index.ts index c1790506d..b74f5f8d8 100644 --- a/packages/db-collections/src/index.ts +++ b/packages/db-collections/src/index.ts @@ -2,6 +2,7 @@ export { electricCollectionOptions, type ElectricCollectionConfig, type ElectricCollectionUtils, + type Txid, } from "./electric" export { queryCollectionOptions, diff --git a/packages/db-collections/tests/electric.test-d.ts b/packages/db-collections/tests/electric.test-d.ts index 3061e1181..f66396f28 100644 --- a/packages/db-collections/tests/electric.test-d.ts +++ b/packages/db-collections/tests/electric.test-d.ts @@ -115,21 +115,21 @@ describe(`Electric collection type resolution tests`, () => { expectTypeOf( params.transaction.mutations[0].modified ).toEqualTypeOf() - return Promise.resolve({ txid: `test` }) + return Promise.resolve({ txid: 1 }) }, onUpdate: (params) => { // Verify that the mutation value has the correct type expectTypeOf( params.transaction.mutations[0].modified ).toEqualTypeOf() - return Promise.resolve({ txid: `test` }) + return Promise.resolve({ txid: 1 }) }, onDelete: (params) => { // Verify that the mutation value has the correct type expectTypeOf( params.transaction.mutations[0].original ).toEqualTypeOf() - return Promise.resolve({ txid: `test` }) + return Promise.resolve({ txid: 1 }) }, }) diff --git a/packages/db-collections/tests/electric.test.ts b/packages/db-collections/tests/electric.test.ts index d794798b8..8c6fc0823 100644 --- a/packages/db-collections/tests/electric.test.ts +++ b/packages/db-collections/tests/electric.test.ts @@ -225,7 +225,7 @@ describe(`Electric Integration`, () => { // Tests for txid tracking functionality describe(`txid tracking`, () => { it(`should track txids from incoming messages`, async () => { - const testTxid = `123` + const testTxid = 123 // Send a message with a txid subscriber([ @@ -242,13 +242,12 @@ describe(`Electric Integration`, () => { }, ]) - // awaitTxId throws if you pass it a number - // @ts-expect-error - await expect(collection.utils.awaitTxId(123)).rejects.toThrow( - `Expected string in awaitTxId, received number` - ) - await expect(collection.utils.awaitTxId(`123ab`)).rejects.toThrow( - `txId must contain only numbers` + // awaitTxId throws if you pass it a string + await expect( + // @ts-expect-error + collection.utils.awaitTxId(`123`) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[TypeError: Expected number in awaitTxId, received string]` ) // The txid should be tracked and awaitTxId should resolve immediately @@ -256,8 +255,8 @@ describe(`Electric Integration`, () => { }) it(`should track multiple txids`, async () => { - const txid1 = `100` - const txid2 = `200` + const txid1 = 100 + const txid2 = 200 // Send a message with multiple txids subscriber([ @@ -281,7 +280,7 @@ describe(`Electric Integration`, () => { it(`should reject with timeout when waiting for unknown txid`, async () => { // Set a short timeout for the test - const unknownTxid = `0` + const unknownTxid = 0 const shortTimeout = 100 // Attempt to await a txid that hasn't been seen with a short timeout @@ -294,7 +293,7 @@ describe(`Electric Integration`, () => { }) it(`should resolve when a txid arrives after awaitTxId is called`, async () => { - const laterTxid = `1000` + const laterTxid = 1000 // Start waiting for a txid that hasn't arrived yet const promise = collection.utils.awaitTxId(laterTxid, 1000) @@ -331,10 +330,10 @@ describe(`Electric Integration`, () => { it(`should simulate the complete flow`, async () => { // Create a fake backend store to simulate server-side storage const fakeBackend = { - data: new Map(), + data: new Map(), // Simulates persisting data to a backend and returning a txid - persist: (mutations: Array>): Promise => { - const txid = String(Date.now()) + persist: (mutations: Array>): Promise => { + const txid = Math.floor(Math.random() * 10000) // Store the changes with the txid mutations.forEach((mutation) => { @@ -347,7 +346,7 @@ describe(`Electric Integration`, () => { return Promise.resolve(txid) }, // Simulates the server sending sync messages with txids - simulateSyncMessage: (txid: string) => { + simulateSyncMessage: (txid: number) => { // Create messages for each item in the store that has this txid const messages: Array> = [] @@ -428,9 +427,9 @@ describe(`Electric Integration`, () => { describe(`Direct persistence handlers`, () => { it(`should pass through direct persistence handlers to collection options`, () => { // Create mock handlers - const onInsert = vi.fn().mockResolvedValue({ txid: `123` }) - const onUpdate = vi.fn().mockResolvedValue({ txid: `456` }) - const onDelete = vi.fn().mockResolvedValue({ txid: `789` }) + const onInsert = vi.fn().mockResolvedValue({ txid: 123 }) + const onUpdate = vi.fn().mockResolvedValue({ txid: 456 }) + const onDelete = vi.fn().mockResolvedValue({ txid: 789 }) const config = { id: `test-handlers`, @@ -489,15 +488,13 @@ describe(`Electric Integration`, () => { ) }) - // We no longer need to test for invalid txid format since we're using strings directly - it(`should simulate complete flow with direct persistence handlers`, async () => { // Create a fake backend store to simulate server-side storage const fakeBackend = { - data: new Map(), + data: new Map(), // Simulates persisting data to a backend and returning a txid - persist: (mutations: Array>): Promise => { - const txid = String(Date.now()) + persist: (mutations: Array>): Promise => { + const txid = Math.floor(Math.random() * 10000) // Store the changes with the txid mutations.forEach((mutation) => { @@ -508,7 +505,7 @@ describe(`Electric Integration`, () => { return Promise.resolve(txid) }, // Simulates the server sending sync messages with txids - simulateSyncMessage: (txid: string) => { + simulateSyncMessage: (txid: number) => { // Create messages for each item in the store that has this txid const messages: Array> = [] @@ -671,7 +668,7 @@ describe(`Electric Integration`, () => { value: { id: 1, name: `Test` }, headers: { operation: `insert`, - txids: [`100`, `200`], + txids: [100, 200], }, }, { @@ -680,7 +677,7 @@ describe(`Electric Integration`, () => { ]) // Verify txids are tracked - await expect(testCollection.utils.awaitTxId(`100`)).resolves.toBe(true) + await expect(testCollection.utils.awaitTxId(100)).resolves.toBe(true) // Cleanup collection await testCollection.cleanup() @@ -968,14 +965,14 @@ describe(`Electric Integration`, () => { { headers: { control: `up-to-date`, - txids: [`300`, `400`], + txids: [300, 400], }, }, ]) // Txids should be tracked (converted to strings internally) - await expect(testCollection.utils.awaitTxId(`300`)).resolves.toBe(true) - await expect(testCollection.utils.awaitTxId(`400`)).resolves.toBe(true) + await expect(testCollection.utils.awaitTxId(300)).resolves.toBe(true) + await expect(testCollection.utils.awaitTxId(400)).resolves.toBe(true) }) }) }) diff --git a/packages/db-collections/tests/local-only.test.ts b/packages/db-collections/tests/local-only.test.ts index 862b8352d..8f0721261 100644 --- a/packages/db-collections/tests/local-only.test.ts +++ b/packages/db-collections/tests/local-only.test.ts @@ -415,7 +415,7 @@ describe(`LocalOnly Collection`, () => { it(`should allow adding more items after initial data`, () => { const initialItems: Array = [{ id: 100, name: `Initial Item` }] - const testCollection = createCollection( + const testCollection = createCollection( localOnlyCollectionOptions({ id: `test-initial-plus-more`, getKey: (item: TestItem) => item.id, diff --git a/packages/db-collections/tests/test-setup.ts b/packages/db-collections/tests/test-setup.ts index b9e762299..5df5eb209 100644 --- a/packages/db-collections/tests/test-setup.ts +++ b/packages/db-collections/tests/test-setup.ts @@ -1 +1 @@ -import "@testing-library/jest-dom/vitest" +// import "@testing-library/jest-dom/vitest" diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts index d13dea691..eef509047 100644 --- a/packages/db/src/types.ts +++ b/packages/db/src/types.ts @@ -71,7 +71,7 @@ export interface PendingMutation< syncMetadata: Record createdAt: Date updatedAt: Date - collection: Collection + collection: Collection } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6ad78751..2834086e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,7 +91,7 @@ importers: version: 6.3.5(@types/node@22.16.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) vitest: specifier: ^3.0.9 - version: 3.2.4(@types/node@22.16.1)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.16.1)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) zod: specifier: ^3.24.2 version: 3.25.76 @@ -210,7 +210,7 @@ importers: devDependencies: '@vitest/coverage-istanbul': specifier: ^3.0.9 - version: 3.2.4(vitest@3.2.4(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) packages/db-collections: dependencies: @@ -226,6 +226,9 @@ importers: '@tanstack/store': specifier: ^0.7.0 version: 0.7.2 + debug: + specifier: ^4.4.1 + version: 4.4.1 typescript: specifier: '>=4.7' version: 5.8.3 @@ -233,9 +236,12 @@ importers: '@electric-sql/client': specifier: 1.0.0 version: 1.0.0 + '@types/debug': + specifier: ^4.1.12 + version: 4.1.12 '@vitest/coverage-istanbul': specifier: ^3.0.9 - version: 3.2.4(vitest@3.2.4(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) packages/react-db: dependencies: @@ -263,7 +269,7 @@ importers: version: 0.0.6 '@vitest/coverage-istanbul': specifier: ^3.0.9 - version: 3.2.4(vitest@3.2.4(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) react: specifier: ^19.0.0 version: 19.1.0 @@ -285,7 +291,7 @@ importers: version: 5.2.4(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) '@vitest/coverage-istanbul': specifier: ^3.0.9 - version: 3.2.4(vitest@3.2.4(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) vue: specifier: ^3.5.13 version: 3.5.17(typescript@5.8.3) @@ -1891,6 +1897,9 @@ packages: '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -1918,6 +1927,9 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -6652,6 +6664,10 @@ snapshots: dependencies: '@types/node': 22.16.1 + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} '@types/estree@1.0.6': {} @@ -6682,6 +6698,8 @@ snapshots: '@types/mime@1.3.5': {} + '@types/ms@2.1.0': {} + '@types/node@12.20.55': {} '@types/node@20.19.5': @@ -6902,7 +6920,7 @@ snapshots: vite: 7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) vue: 3.5.17(typescript@5.8.3) - '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))': + '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@istanbuljs/schema': 0.1.3 debug: 4.4.1 @@ -6914,7 +6932,7 @@ snapshots: magicast: 0.3.5 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -10119,7 +10137,7 @@ snapshots: tsx: 4.20.3 yaml: 2.8.0 - vitest@3.2.4(@types/node@22.16.1)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.16.1)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 @@ -10145,6 +10163,7 @@ snapshots: vite-node: 3.2.4(@types/node@22.16.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.12 '@types/node': 22.16.1 happy-dom: 18.0.1 jsdom: 26.1.0 @@ -10162,7 +10181,7 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.11)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 @@ -10188,6 +10207,7 @@ snapshots: vite-node: 3.2.4(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.12 '@types/node': 24.0.11 happy-dom: 18.0.1 jsdom: 26.1.0