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 +137 -24
- package/dist/drizzle.d.ts +1 -1
- package/dist/drizzle.js +13 -6
- package/dist/sql.js +8 -4
- package/package.json +1 -1
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
|
|
8
|
+
npm install tanstack-db-pglite @tanstack/db @electric-sql/pglite
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
> **Note:** `@tanstack/db` and `drizzle-orm`
|
|
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
|
-
##
|
|
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 {
|
|
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
|
|
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 ({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|