tanstack-db-pglite 1.3.4 → 1.3.6

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/README.md CHANGED
@@ -1,25 +1,34 @@
1
1
  # tanstack-db-pglite
2
2
 
3
- A seamless integration between [TanStack DB](https://tanstack.com/db) and [PGLite](https://github.com/electric-sql/pglite) with [Drizzle ORM](https://orm.drizzle.team/) for browser-based database management.
3
+ A seamless integration between [TanStack DB](https://tanstack.com/db) and [PGLite](https://github.com/electric-sql/pglite) with optional [Drizzle ORM](https://orm.drizzle.team/) support for browser-based database management.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install tanstack-db-pglite @tanstack/db drizzle-orm @electric-sql/pglite
8
+ npm install tanstack-db-pglite @tanstack/db @electric-sql/pglite
9
9
  ```
10
10
 
11
- > **Note:** `@tanstack/db` and `drizzle-orm` are peer dependencies and must be installed separately.
11
+ > **Note:** `@tanstack/db` and `@electric-sql/pglite` are peer dependencies. `drizzle-orm` is an optional peer dependency required only when using `drizzleCollectionOptions`.
12
12
 
13
- ## Quick Start
13
+ ## APIs
14
+
15
+ This package exports two collection option creators:
16
+
17
+ - `drizzleCollectionOptions` — uses Drizzle ORM on top of PGlite
18
+ - `sqlCollectionOptions` — uses raw SQL queries directly on PGlite
19
+
20
+ Both follow the TanStack DB [collection options creator](https://tanstack.com/db/latest/docs/guides/collection-options-creator) pattern.
21
+
22
+ ## Quick Start (Drizzle)
14
23
 
15
24
  ```typescript
16
- import { PGLite } from '@electric-sql/pglite'
25
+ import { PGlite } from '@electric-sql/pglite'
17
26
  import { createCollection } from '@tanstack/react-db'
18
27
  import { drizzle } from 'drizzle-orm/pglite'
19
28
  import { drizzleCollectionOptions } from 'tanstack-db-pglite'
20
29
  import { chats } from '~/drizzle'
21
30
 
22
- const pglite = new PGLite()
31
+ const pglite = new PGlite()
23
32
  const db = drizzle(pglite)
24
33
 
25
34
  export const chatsCollection = createCollection(drizzleCollectionOptions({
@@ -27,26 +36,23 @@ export const chatsCollection = createCollection(drizzleCollectionOptions({
27
36
  table: chats,
28
37
  primaryColumn: chats.id,
29
38
  prepare: async () => {
30
- // Prepare your database before starting the collection (e.g., run migrations)
31
39
  await waitForMigrations()
32
40
  },
33
- sync: async ({ collection, write }) => {
34
- // Send some data to your backend to sync and receive the response
35
- const sync = await syncWithCloud(
36
- collection.toArray.map(c => ({
37
- id: c.id,
38
- updatedAt: c.updatedAt
39
- }))
40
- )
41
-
42
- sync.forEach((item) => {
43
- if (item.type === 'delete') {
44
- write({ type: 'delete', value: collection.get(item.value)! })
45
- }
46
- else {
47
- write(item)
48
- }
49
- })
41
+ sync: async ({ begin, commit, write, markReady }) => {
42
+ const eventSource = new EventSource('/api/chats/sync')
43
+
44
+ eventSource.onmessage = (event) => {
45
+ const item = JSON.parse(event.data)
46
+ begin()
47
+ write(item)
48
+ commit()
49
+ }
50
+
51
+ eventSource.addEventListener('ready', () => markReady())
52
+
53
+ return () => {
54
+ eventSource.close()
55
+ }
50
56
  },
51
57
  onInsert: async (params) => {
52
58
  await saveInCloud(params)
@@ -59,3 +65,110 @@ export const chatsCollection = createCollection(drizzleCollectionOptions({
59
65
  },
60
66
  }))
61
67
  ```
68
+
69
+ ## Quick Start (Raw SQL)
70
+
71
+ ```typescript
72
+ import { PGlite } from '@electric-sql/pglite'
73
+ import { createCollection } from '@tanstack/react-db'
74
+ import { sqlCollectionOptions } from 'tanstack-db-pglite'
75
+ import { z } from 'zod'
76
+
77
+ const pglite = new PGlite()
78
+
79
+ const chatSchema = z.object({
80
+ id: z.string(),
81
+ name: z.string(),
82
+ updatedAt: z.string(),
83
+ })
84
+
85
+ export const chatsCollection = createCollection(sqlCollectionOptions({
86
+ db: pglite,
87
+ tableName: 'chats',
88
+ primaryKeyColumn: 'id',
89
+ schema: chatSchema,
90
+ prepare: async () => {
91
+ await pglite.query(`
92
+ CREATE TABLE IF NOT EXISTS chats (
93
+ id TEXT PRIMARY KEY,
94
+ name TEXT NOT NULL,
95
+ "updatedAt" TEXT NOT NULL
96
+ )
97
+ `)
98
+ },
99
+ sync: async ({ begin, commit, write, markReady }) => {
100
+ const eventSource = new EventSource('/api/chats/sync')
101
+
102
+ eventSource.onmessage = (event) => {
103
+ const item = JSON.parse(event.data)
104
+ begin()
105
+ write(item)
106
+ commit()
107
+ }
108
+
109
+ eventSource.addEventListener('ready', () => markReady())
110
+
111
+ return () => {
112
+ eventSource.close()
113
+ }
114
+ },
115
+ }))
116
+ ```
117
+
118
+ ## Options
119
+
120
+ ### Common Options
121
+
122
+ | Option | Type | Description |
123
+ |--------|------|-------------|
124
+ | `startSync` | `boolean` | Whether to run the `sync` callback automatically on startup. Defaults to `true`. When `false`, use `collection.utils.runSync()` to trigger manually. |
125
+ | `prepare` | `() => Promise<unknown> \| unknown` | Runs before the initial data load (e.g., run migrations). |
126
+ | `sync` | `(params) => Promise<(() => void) \| void>` | Sync callback receiving `write`, `markReady`, `collection`, and `metadata`. Return a cleanup function to close subscriptions. |
127
+ | `rowUpdateMode` | `'partial' \| 'full'` | Whether sync updates contain partial changes or full row replacements. |
128
+ | `onInsert` | `(params) => Promise<void>` | Called when a row is inserted optimistically. Persist to your backend here. |
129
+ | `onUpdate` | `(params) => Promise<void>` | Called when a row is updated optimistically. Persist to your backend here. |
130
+ | `onDelete` | `(params) => Promise<void>` | Called when a row is deleted optimistically. Persist to your backend here. |
131
+
132
+ ### `drizzleCollectionOptions` Specific
133
+
134
+ | Option | Type | Description |
135
+ |--------|------|-------------|
136
+ | `db` | `PgliteDatabase` | Drizzle PGlite database instance. |
137
+ | `table` | `PgTable` | Drizzle table definition. |
138
+ | `primaryColumn` | `IndexColumn` | The primary key column from the table. |
139
+
140
+ ### `sqlCollectionOptions` Specific
141
+
142
+ | Option | Type | Description |
143
+ |--------|------|-------------|
144
+ | `db` | `PGlite \| PGliteWorker` | PGlite instance (or worker). |
145
+ | `tableName` | `string` | SQL table name. |
146
+ | `primaryKeyColumn` | `string` | Name of the primary key column. |
147
+ | `schema` | `StandardSchemaV1` | A Standard Schema (e.g., Zod) for the row type. |
148
+ | `getKey` | `(row) => string` | Custom key extractor. Defaults to `row[primaryKeyColumn]`. |
149
+
150
+ ## Utilities
151
+
152
+ Both adapters expose a `utils` object on the collection:
153
+
154
+ ```typescript
155
+ // Manually trigger sync (cleans up any previous sync, then re-syncs)
156
+ await chatsCollection.utils.runSync()
157
+ ```
158
+
159
+ This is useful when `startSync: false` and you want to control when sync starts (e.g., after authentication).
160
+
161
+ ## Sync Callback
162
+
163
+ The `sync` callback receives:
164
+
165
+ - **`write(message)`** — writes a change to both PGlite and the TanStack DB collection. Accepts `{ type: 'insert', value }`, `{ type: 'update', value }`, or `{ type: 'delete', key }`.
166
+ - **`markReady()`** — signals that the initial data is loaded and the collection is ready for queries. Must be called once.
167
+ - **`collection`** — reference to the collection instance.
168
+ - **`metadata`** — persisted sync metadata API for storing resume tokens, cursors, etc.
169
+
170
+ Return a cleanup function to close long-lived connections (WebSocket, EventSource, etc.) when the collection is destroyed or `runSync()` is called again.
171
+
172
+ ## License
173
+
174
+ MIT
package/dist/drizzle.d.ts CHANGED
@@ -19,7 +19,7 @@ export declare function drizzleCollectionOptions<Table extends PgTable>({ startS
19
19
  table: Table;
20
20
  primaryColumn: IndexColumn;
21
21
  rowUpdateMode?: 'partial' | 'full';
22
- sync?: (params: Pick<SyncParams<Table>, 'write' | 'collection' | 'markReady' | 'metadata'>) => Promise<(() => void) | void>;
22
+ sync?: (params: SyncParams<Table>) => Promise<(() => void) | void>;
23
23
  prepare?: () => Promise<unknown> | unknown;
24
24
  onInsert?: (params: InsertMutationFnParams<Table['$inferSelect'], string>) => Promise<void>;
25
25
  onUpdate?: (params: UpdateMutationFnParams<Table['$inferSelect'], string>) => Promise<void>;
package/dist/drizzle.js CHANGED
@@ -12,6 +12,7 @@ import { createSelectSchema } from 'drizzle-zod';
12
12
  export function drizzleCollectionOptions({ startSync = true, ...config }) {
13
13
  // Sync params can be null while running PGLite migrations
14
14
  const { promise: syncParams, resolve: resolveSyncParams } = Promise.withResolvers();
15
+ let syncCleanup;
15
16
  // eslint-disable-next-line ts/no-explicit-any
16
17
  async function onDrizzleInsert(data, tx) {
17
18
  // @ts-expect-error drizzle types
@@ -88,12 +89,13 @@ export function drizzleCollectionOptions({ startSync = true, ...config }) {
88
89
  const key = 'key' in message ? message.key : params.collection.getKeyFromItem(message.value);
89
90
  await onDrizzleDelete([key]);
90
91
  }
91
- params.begin();
92
92
  params.write(message);
93
- params.commit();
94
93
  },
95
94
  collection: params.collection,
96
95
  markReady: params.markReady,
96
+ begin: params.begin,
97
+ commit: params.commit,
98
+ truncate: params.truncate,
97
99
  ...(params.metadata && { metadata: params.metadata }),
98
100
  });
99
101
  };
@@ -105,7 +107,6 @@ export function drizzleCollectionOptions({ startSync = true, ...config }) {
105
107
  ...(config.rowUpdateMode && { rowUpdateMode: config.rowUpdateMode }),
106
108
  sync: (params) => {
107
109
  resolveSyncParams(params);
108
- let cleanup;
109
110
  (async () => {
110
111
  await config.prepare?.();
111
112
  // @ts-expect-error drizzle types
@@ -118,7 +119,7 @@ export function drizzleCollectionOptions({ startSync = true, ...config }) {
118
119
  if (startSync) {
119
120
  const result = await sync();
120
121
  if (typeof result === 'function') {
121
- cleanup = result;
122
+ syncCleanup = result;
122
123
  }
123
124
  }
124
125
  else {
@@ -126,7 +127,8 @@ export function drizzleCollectionOptions({ startSync = true, ...config }) {
126
127
  }
127
128
  })();
128
129
  return () => {
129
- cleanup?.();
130
+ syncCleanup?.();
131
+ syncCleanup = undefined;
130
132
  };
131
133
  },
132
134
  },
@@ -167,9 +169,14 @@ export function drizzleCollectionOptions({ startSync = true, ...config }) {
167
169
  if (!config.sync) {
168
170
  throw new Error('Sync is not defined');
169
171
  }
172
+ syncCleanup?.();
173
+ syncCleanup = undefined;
170
174
  const params = await syncParams;
171
175
  await params.collection.stateWhenReady();
172
- await sync();
176
+ const result = await sync();
177
+ if (typeof result === 'function') {
178
+ syncCleanup = result;
179
+ }
173
180
  },
174
181
  },
175
182
  };
package/dist/sql.js CHANGED
@@ -12,6 +12,7 @@ function quoteId(name) {
12
12
  * until `markReady()` is called.
13
13
  */
14
14
  export function sqlCollectionOptions({ startSync = true, ...config }) {
15
+ let syncCleanup;
15
16
  const table = quoteId(config.tableName);
16
17
  const primaryKey = quoteId(config.primaryKeyColumn);
17
18
  const getKey = config.getKey ?? ((row) => String(row[config.primaryKeyColumn]));
@@ -92,7 +93,6 @@ export function sqlCollectionOptions({ startSync = true, ...config }) {
92
93
  ...(config.rowUpdateMode && { rowUpdateMode: config.rowUpdateMode }),
93
94
  sync: (params) => {
94
95
  resolveSyncParams(params);
95
- let cleanup;
96
96
  (async () => {
97
97
  await config.prepare?.();
98
98
  const rows = await runSelect(config.db);
@@ -104,7 +104,7 @@ export function sqlCollectionOptions({ startSync = true, ...config }) {
104
104
  if (startSync) {
105
105
  const result = await sync();
106
106
  if (typeof result === 'function') {
107
- cleanup = result;
107
+ syncCleanup = result;
108
108
  }
109
109
  }
110
110
  else {
@@ -112,7 +112,8 @@ export function sqlCollectionOptions({ startSync = true, ...config }) {
112
112
  }
113
113
  })();
114
114
  return () => {
115
- cleanup?.();
115
+ syncCleanup?.();
116
+ syncCleanup = undefined;
116
117
  };
117
118
  },
118
119
  },
@@ -152,7 +153,10 @@ export function sqlCollectionOptions({ startSync = true, ...config }) {
152
153
  }
153
154
  const params = await syncParams;
154
155
  await params.collection.stateWhenReady();
155
- await sync();
156
+ const result = await sync();
157
+ if (typeof result === 'function') {
158
+ syncCleanup = result;
159
+ }
156
160
  },
157
161
  },
158
162
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tanstack-db-pglite",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "author": "Valerii Strilets",
5
5
  "license": "MIT",
6
6
  "repository": {