event-sourced-collection 0.0.2 → 0.0.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 +354 -57
- package/dist/browser.d.mts +7 -1
- package/dist/browser.d.mts.map +1 -1
- package/dist/browser.mjs +2 -1
- package/dist/browser.mjs.map +1 -1
- package/dist/index.d.mts +8 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{lazy-singleton-DFrAsYTV.mjs → lazy-singleton-CB50yOVo.mjs} +45 -4
- package/dist/lazy-singleton-CB50yOVo.mjs.map +1 -0
- package/dist/react-native.d.mts +7 -1
- package/dist/react-native.d.mts.map +1 -1
- package/dist/react-native.mjs +2 -1
- package/dist/react-native.mjs.map +1 -1
- package/dist/{types-CAT14Tbj.d.mts → types-BX8dtGOu.d.mts} +17 -3
- package/dist/{types-CAT14Tbj.d.mts.map → types-BX8dtGOu.d.mts.map} +1 -1
- package/package.json +1 -1
- package/dist/lazy-singleton-DFrAsYTV.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -22,36 +22,49 @@ npm install @tanstack/react-native-db-sqlite-persistence
|
|
|
22
22
|
|
|
23
23
|
## Project structure
|
|
24
24
|
|
|
25
|
-
A minimal browser app
|
|
25
|
+
A minimal browser app splits setup by concern. **`collections.ts`** is the single source of truth for types, sync transport, collection registry, indexes, and the `createBrowserEventSourcedDB` call.
|
|
26
26
|
|
|
27
27
|
```
|
|
28
|
-
src/
|
|
29
|
-
├──
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
src/data-access-layer/
|
|
29
|
+
├── collections.ts # types, push/pull, DB init → exports `ensureDb` + `db`
|
|
30
|
+
├── app-settings.ts # seeds settings row, persists syncEnabled, calls setSyncEnabled()
|
|
31
|
+
└── sync-events.ts # thin wrappers around db.sync() / db.manualSync()
|
|
32
|
+
|
|
33
|
+
src/hooks/common/
|
|
34
|
+
├── use-event-sourced-sync.ts # periodic background sync (optional)
|
|
35
|
+
└── use-sync-enabled.ts # reads settings.syncEnabled for hooks/UI
|
|
36
|
+
|
|
37
|
+
src/routes/…/settings/ # UI toggle for background sync (optional)
|
|
38
|
+
src/routes/…/layout.tsx # call ensureAppSettings() once before data UI
|
|
36
39
|
```
|
|
37
40
|
|
|
38
41
|
| File | Role |
|
|
39
42
|
| ---- | ---- |
|
|
40
|
-
| `collections.ts` |
|
|
43
|
+
| `collections.ts` | Row types, collection registry, indexes, `syncEnabled` default, push/pull handlers, lazy singleton (`ensureDb` + `db` proxy) |
|
|
44
|
+
| `app-settings.ts` | Seeds the `"app"` settings row, mirrors `syncEnabled` to `db.setSyncEnabled()` on startup and when the user toggles |
|
|
41
45
|
| `sync-events.ts` | Keeps sync calls out of React; import from buttons, jobs, or hooks |
|
|
42
|
-
| `use-event-sourced-sync.ts` | Polls `manualSync()` on an interval when
|
|
43
|
-
|
|
|
46
|
+
| `use-event-sourced-sync.ts` | Polls `manualSync()` on an interval when DB is ready **and** sync is enabled |
|
|
47
|
+
| `use-sync-enabled.ts` | Live-query helper for `settings.syncEnabled` (disable background sync in UI) |
|
|
48
|
+
| Settings page | Optional UI for `setSyncEnabled()` — lets users opt out of push/pull |
|
|
49
|
+
| App shell | `await ensureAppSettings()` before routes that read/write collections |
|
|
50
|
+
|
|
51
|
+
**Imports in components:** use `db` for reads/writes after startup. Call `ensureDb()` (or `ensureAppSettings()`) once at app mount.
|
|
52
|
+
|
|
53
|
+
**Built-in collections** (always present — do not register these ids):
|
|
54
|
+
|
|
55
|
+
- `db.collections.outbox` — local mutations waiting to upload
|
|
56
|
+
- `db.collections.inbox` — server events pulled to this device
|
|
44
57
|
|
|
45
58
|
Flow:
|
|
46
59
|
|
|
47
60
|
```
|
|
48
|
-
app mount →
|
|
61
|
+
app mount → ensureAppSettings() → ensureDb() + seed settings + setSyncEnabled
|
|
49
62
|
↓
|
|
50
63
|
db.collections.* available
|
|
51
64
|
↓
|
|
52
65
|
useLiveQuery / insert / update / delete
|
|
53
66
|
↓
|
|
54
|
-
|
|
67
|
+
useEventSourcedSync(dbReady && syncEnabled) or manual Sync now
|
|
55
68
|
```
|
|
56
69
|
|
|
57
70
|
## Quick start
|
|
@@ -83,6 +96,7 @@ type AppSettings = {
|
|
|
83
96
|
id: string;
|
|
84
97
|
theme: "light" | "dark";
|
|
85
98
|
language: string;
|
|
99
|
+
syncEnabled: boolean; // persisted preference; mirrored to db.setSyncEnabled()
|
|
86
100
|
};
|
|
87
101
|
```
|
|
88
102
|
|
|
@@ -91,12 +105,12 @@ type AppSettings = {
|
|
|
91
105
|
Keys here must match the `collections` option you pass in step 4. This gives you `db.collections.users`, etc. with full inference.
|
|
92
106
|
|
|
93
107
|
```typescript
|
|
94
|
-
import type { EventSourcedDB } from "event-sourced-collection";
|
|
108
|
+
import type { CollectionDef, EventSourcedDB } from "event-sourced-collection";
|
|
95
109
|
|
|
96
110
|
type AppCollectionDefs = {
|
|
97
|
-
users:
|
|
98
|
-
todos:
|
|
99
|
-
settings:
|
|
111
|
+
users: CollectionDef<User, string>;
|
|
112
|
+
todos: CollectionDef<Todo, string>;
|
|
113
|
+
settings: CollectionDef<AppSettings, string>;
|
|
100
114
|
};
|
|
101
115
|
|
|
102
116
|
export type AppDb = EventSourcedDB<AppCollectionDefs>;
|
|
@@ -136,74 +150,300 @@ async function pullEvents({ since }: { since: number }): Promise<PullResponse> {
|
|
|
136
150
|
}
|
|
137
151
|
```
|
|
138
152
|
|
|
139
|
-
### 4. Create the database (
|
|
153
|
+
### 4. Create the database (`collections.ts`)
|
|
154
|
+
|
|
155
|
+
`collections.ts` is the single source of truth for row types, sync transport, collection registry, indexes, and DB init. Import `db` in components for reads/writes after startup. Call `ensureDb()` (or `ensureAppSettings()`) once at app mount before touching collections.
|
|
140
156
|
|
|
141
|
-
`createBrowserEventSourcedDB` (from `event-sourced-collection/browser`) wires the SQLite platform, registers collections, and
|
|
157
|
+
`createBrowserEventSourcedDB` (from `event-sourced-collection/browser`) wires the SQLite platform, registers collections, and returns a lazy singleton (`ensureDb`) plus a `db` proxy you can import anywhere. **Collections are created here** — there is no separate `createUsersCollection()` call.
|
|
142
158
|
|
|
143
|
-
The `load` callback is where you import
|
|
159
|
+
The `load` callback is where you import platform packages. Keeping those imports in *your* app (not inside the library) means you choose `@tanstack/react-db` vs `@tanstack/db`, and SSR bundles stay clean because `load` runs on first `ensureDb()`.
|
|
160
|
+
|
|
161
|
+
Full reference (matches the example app):
|
|
144
162
|
|
|
145
163
|
```typescript
|
|
164
|
+
import { BasicIndex } from "@tanstack/db";
|
|
146
165
|
import { createBrowserEventSourcedDB } from "event-sourced-collection/browser";
|
|
166
|
+
import type {
|
|
167
|
+
CollectionDef,
|
|
168
|
+
EventSourcedDB,
|
|
169
|
+
OutboundEvent,
|
|
170
|
+
PullResponse,
|
|
171
|
+
PushResponse,
|
|
172
|
+
} from "event-sourced-collection";
|
|
173
|
+
|
|
174
|
+
// --- Row types (one per collection) ---
|
|
175
|
+
|
|
176
|
+
export type User = { id: string; name: string; email: string; createdAt: number };
|
|
177
|
+
export type Todo = {
|
|
178
|
+
id: string;
|
|
179
|
+
userId: string;
|
|
180
|
+
title: string;
|
|
181
|
+
status: "pending" | "complete";
|
|
182
|
+
createdAt: number;
|
|
183
|
+
updatedAt: number;
|
|
184
|
+
};
|
|
185
|
+
// Singleton preferences row — use id "app" (see app-settings.ts)
|
|
186
|
+
export type AppSettings = {
|
|
187
|
+
id: string;
|
|
188
|
+
theme: "light" | "dark";
|
|
189
|
+
language: string;
|
|
190
|
+
syncEnabled: boolean;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
type AppCollectionDefs = {
|
|
194
|
+
users: CollectionDef<User, string>;
|
|
195
|
+
todos: CollectionDef<Todo, string>;
|
|
196
|
+
settings: CollectionDef<AppSettings, string>;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export type AppDb = EventSourcedDB<AppCollectionDefs>;
|
|
200
|
+
|
|
201
|
+
// --- Sync transport (runs when sync is enabled) ---
|
|
147
202
|
|
|
148
|
-
const
|
|
203
|
+
const getAccessToken = () => localStorage.getItem("accessToken") ?? "";
|
|
204
|
+
|
|
205
|
+
async function pushEvents(events: ReadonlyArray<OutboundEvent>): Promise<PushResponse> {
|
|
206
|
+
const response = await fetch("/api/sync/events", {
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${getAccessToken()}` },
|
|
209
|
+
body: JSON.stringify(events),
|
|
210
|
+
});
|
|
211
|
+
if (!response.ok) throw new Error(`Push failed: ${response.status}`);
|
|
212
|
+
return response.json();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function pullEvents({ since }: { since: number }): Promise<PullResponse> {
|
|
216
|
+
const response = await fetch(`/api/sync/events?since=${since}`, {
|
|
217
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${getAccessToken()}` },
|
|
218
|
+
});
|
|
219
|
+
if (!response.ok) throw new Error(`Pull failed: ${response.status}`);
|
|
220
|
+
return response.json();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// --- DB init: lazy singleton; db proxy forwards after ensureDb() ---
|
|
224
|
+
|
|
225
|
+
const { ensureDb, db } = createBrowserEventSourcedDB<AppCollectionDefs>({
|
|
149
226
|
databaseName: "my-app.sqlite",
|
|
150
227
|
debug: import.meta.env.DEV,
|
|
228
|
+
|
|
151
229
|
collections: {
|
|
152
|
-
users: {
|
|
153
|
-
|
|
230
|
+
users: {
|
|
231
|
+
getKey: (user: User) => user.id,
|
|
232
|
+
indexes: [{ select: (user: User) => user.id, indexType: BasicIndex, name: "by-id" }],
|
|
233
|
+
},
|
|
234
|
+
todos: {
|
|
235
|
+
getKey: (todo: Todo) => todo.id,
|
|
236
|
+
indexes: [
|
|
237
|
+
{ select: (todo: Todo) => todo.id, indexType: BasicIndex, name: "by-id" },
|
|
238
|
+
{ select: (todo: Todo) => todo.userId, indexType: BasicIndex, name: "by-user" },
|
|
239
|
+
{ select: (todo: Todo) => todo.status, indexType: BasicIndex, name: "by-status" },
|
|
240
|
+
{ select: (todo: Todo) => todo.title, indexType: BasicIndex, name: "by-title" },
|
|
241
|
+
],
|
|
242
|
+
},
|
|
154
243
|
settings: { getKey: (settings: AppSettings) => settings.id },
|
|
155
244
|
},
|
|
245
|
+
|
|
246
|
+
syncEnabled: true, // initial default; users can toggle at runtime (see app-settings.ts)
|
|
156
247
|
sync: { pushEvents, pullEvents },
|
|
248
|
+
|
|
157
249
|
load: async () => {
|
|
158
250
|
const { createCollection } = await import("@tanstack/react-db");
|
|
159
|
-
const
|
|
160
|
-
|
|
251
|
+
const {
|
|
252
|
+
BrowserCollectionCoordinator,
|
|
253
|
+
createBrowserWASQLitePersistence,
|
|
254
|
+
openBrowserWASQLiteOPFSDatabase,
|
|
255
|
+
persistedCollectionOptions,
|
|
256
|
+
} = await import("@tanstack/browser-db-sqlite-persistence");
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
openBrowserWASQLiteOPFSDatabase,
|
|
260
|
+
createBrowserWASQLitePersistence,
|
|
261
|
+
BrowserCollectionCoordinator,
|
|
262
|
+
createCollection,
|
|
263
|
+
persistedCollectionOptions,
|
|
264
|
+
};
|
|
161
265
|
},
|
|
162
266
|
});
|
|
163
267
|
|
|
164
|
-
export { db, ensureDb
|
|
268
|
+
export { db, ensureDb };
|
|
165
269
|
```
|
|
166
270
|
|
|
167
271
|
The handle is `{ ensureDb, db, close }`:
|
|
168
272
|
|
|
169
|
-
- `ensureDb()` — runs `load` + setup once (deduped
|
|
170
|
-
- `db` —
|
|
171
|
-
- `close()` — disposes the engine and
|
|
273
|
+
- `ensureDb()` — runs `load` + setup once (deduped). Throws outside a browser environment.
|
|
274
|
+
- `db` — proxy that forwards after `ensureDb()`; throws if used before init.
|
|
275
|
+
- `close()` — disposes the engine and SQLite platform; next `ensureDb()` re-initializes.
|
|
172
276
|
|
|
173
|
-
After setup you get
|
|
277
|
+
After setup you get `db.collections.users`, `db.collections.todos`, `db.collections.settings`, plus built-in `outbox` and `inbox` (see [Project structure](#project-structure)).
|
|
174
278
|
|
|
175
|
-
|
|
176
|
-
- `db.collections.todos`
|
|
177
|
-
- `db.collections.settings`
|
|
279
|
+
#### Collection indexes
|
|
178
280
|
|
|
179
|
-
|
|
281
|
+
TanStack DB indexes are **opt-in**. Declare them on each collection in the `collections` registry — the package calls `collection.createIndex(select, { name?, indexType })` for you and keeps them registered across the collection lifecycle (including after SQLite hydration when the collection becomes `ready` again).
|
|
180
282
|
|
|
181
|
-
|
|
182
|
-
|
|
283
|
+
**Requirements**
|
|
284
|
+
|
|
285
|
+
- Import `BasicIndex` from `@tanstack/db` and pass it as `indexType` on every index entry.
|
|
286
|
+
- The `select` callback must return the **same field** you filter, join, or `orderBy` on in `useLiveQuery`.
|
|
287
|
+
- Use `name` when a collection has more than one index.
|
|
288
|
+
|
|
289
|
+
**Shape**
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { BasicIndex } from "@tanstack/db";
|
|
293
|
+
import type { CollectionDef } from "event-sourced-collection";
|
|
294
|
+
|
|
295
|
+
type SavedMovieRef = { movieId: number; title: string; addedAt: number };
|
|
296
|
+
|
|
297
|
+
// In your AppCollectionDefs / collections registry:
|
|
298
|
+
favorites: CollectionDef<SavedMovieRef, number>;
|
|
299
|
+
```
|
|
183
300
|
|
|
184
|
-
|
|
301
|
+
```typescript
|
|
302
|
+
collections: {
|
|
303
|
+
todos: {
|
|
304
|
+
getKey: (todo: Todo) => todo.id,
|
|
305
|
+
indexes: [
|
|
306
|
+
{ select: (todo: Todo) => todo.id, indexType: BasicIndex, name: "by-id" },
|
|
307
|
+
{ select: (todo: Todo) => todo.userId, indexType: BasicIndex, name: "by-user" },
|
|
308
|
+
{ select: (todo: Todo) => todo.status, indexType: BasicIndex, name: "by-status" },
|
|
309
|
+
],
|
|
310
|
+
},
|
|
185
311
|
|
|
186
|
-
|
|
312
|
+
favorites: {
|
|
313
|
+
getKey: (item: SavedMovieRef) => item.movieId,
|
|
314
|
+
indexes: [{ select: (item) => item.movieId, indexType: BasicIndex, name: "by-movie-id" }],
|
|
315
|
+
},
|
|
187
316
|
|
|
188
|
-
|
|
317
|
+
settings: { getKey: (settings: AppSettings) => settings.id },
|
|
318
|
+
},
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Joins** — index the field on the **joined** collection (the right-hand side), not the driving collection:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { eq, useLiveQuery } from "@tanstack/react-db";
|
|
325
|
+
import { db } from "./collections";
|
|
326
|
+
import { moviesCollection } from "./movies-collection";
|
|
327
|
+
|
|
328
|
+
// favorites needs an index on movieId because the join is eq(movie.id, favorite.movieId)
|
|
329
|
+
const { data } = useLiveQuery((q) =>
|
|
330
|
+
q
|
|
331
|
+
.from({ movie: moviesCollection })
|
|
332
|
+
.leftJoin({ favorite: db.collections.favorites }, ({ movie, favorite }) =>
|
|
333
|
+
eq(movie.id, favorite.movieId),
|
|
334
|
+
)
|
|
335
|
+
.select(({ movie, favorite }) => ({ movie, isFavorite: favorite !== undefined })),
|
|
336
|
+
);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Without that index, TanStack DB logs a warning and falls back to loading the entire `favorites` collection for each join.
|
|
340
|
+
|
|
341
|
+
**Filters and sort** — match indexes to your queries:
|
|
342
|
+
|
|
343
|
+
| Query pattern | Index `select` |
|
|
344
|
+
| ------------- | -------------- |
|
|
345
|
+
| `eq(todo.userId, userId)` | `(todo) => todo.userId` |
|
|
346
|
+
| `eq(todo.status, "pending")` | `(todo) => todo.status` |
|
|
347
|
+
| `orderBy(({ todo }) => todo.title)` | `(todo) => todo.title` |
|
|
348
|
+
|
|
349
|
+
**What you do not need**
|
|
350
|
+
|
|
351
|
+
- Do not call `collection.createIndex()` yourself after `ensureDb()` when using this package — declare indexes in `collections` instead.
|
|
352
|
+
- Do not enable `autoIndex: 'eager'` for collections created here; explicit `indexes` + `BasicIndex` is the supported pattern.
|
|
353
|
+
|
|
354
|
+
**Debugging** — after `await ensureDb()`, check that indexes exist:
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
db.collections.favorites.getIndexMetadata();
|
|
358
|
+
// non-empty array means indexes are registered
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### 5. App settings (`app-settings.ts`)
|
|
362
|
+
|
|
363
|
+
Seed a singleton settings row and keep `syncEnabled` in sync with `db.setSyncEnabled()`:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import type { AppSettings } from "./collections";
|
|
367
|
+
import { db, ensureDb } from "./collections";
|
|
368
|
+
|
|
369
|
+
export const APP_SETTINGS_ID = "app";
|
|
370
|
+
|
|
371
|
+
const DEFAULT_APP_SETTINGS: AppSettings = {
|
|
372
|
+
id: APP_SETTINGS_ID,
|
|
373
|
+
theme: "dark",
|
|
374
|
+
language: "en",
|
|
375
|
+
syncEnabled: true,
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
export async function ensureAppSettings(): Promise<AppSettings> {
|
|
379
|
+
const database = await ensureDb();
|
|
380
|
+
const existing = database.collections.settings.get(APP_SETTINGS_ID);
|
|
381
|
+
|
|
382
|
+
if (!existing) {
|
|
383
|
+
await database.collections.settings.insert(DEFAULT_APP_SETTINGS).isPersisted.promise;
|
|
384
|
+
database.setSyncEnabled(DEFAULT_APP_SETTINGS.syncEnabled);
|
|
385
|
+
return DEFAULT_APP_SETTINGS;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const syncEnabled = existing.syncEnabled ?? true;
|
|
389
|
+
|
|
390
|
+
if (existing.syncEnabled === undefined) {
|
|
391
|
+
await database.collections.settings
|
|
392
|
+
.update(APP_SETTINGS_ID, (draft) => {
|
|
393
|
+
draft.syncEnabled = true;
|
|
394
|
+
})
|
|
395
|
+
.isPersisted.promise;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
database.setSyncEnabled(syncEnabled);
|
|
399
|
+
return { ...existing, syncEnabled };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function setSyncEnabled(enabled: boolean): Promise<void> {
|
|
403
|
+
const database = await ensureDb();
|
|
404
|
+
database.setSyncEnabled(enabled);
|
|
405
|
+
|
|
406
|
+
const existing = database.collections.settings.get(APP_SETTINGS_ID);
|
|
407
|
+
if (!existing) {
|
|
408
|
+
await database.collections.settings.insert({ ...DEFAULT_APP_SETTINGS, syncEnabled: enabled })
|
|
409
|
+
.isPersisted.promise;
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
await database.collections.settings
|
|
414
|
+
.update(APP_SETTINGS_ID, (draft) => {
|
|
415
|
+
draft.syncEnabled = enabled;
|
|
416
|
+
})
|
|
417
|
+
.isPersisted.promise;
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### 6. Wire the app shell
|
|
422
|
+
|
|
423
|
+
Call `ensureAppSettings()` once when the app mounts (it calls `ensureDb()` internally):
|
|
189
424
|
|
|
190
425
|
```typescript
|
|
191
426
|
import { useEffect, useState } from "react";
|
|
192
|
-
import {
|
|
427
|
+
import { ensureAppSettings } from "./data-access-layer/app-settings";
|
|
428
|
+
import { useEventSourcedSync } from "./hooks/common/use-event-sourced-sync";
|
|
429
|
+
import { useSyncEnabled } from "./hooks/common/use-sync-enabled";
|
|
193
430
|
|
|
194
431
|
function AppShell() {
|
|
195
|
-
const [
|
|
432
|
+
const [dbReady, setDbReady] = useState(false);
|
|
196
433
|
|
|
197
434
|
useEffect(() => {
|
|
198
|
-
void
|
|
435
|
+
void ensureAppSettings().then(() => setDbReady(true));
|
|
199
436
|
}, []);
|
|
200
437
|
|
|
201
|
-
|
|
438
|
+
const syncEnabled = useSyncEnabled(dbReady);
|
|
439
|
+
useEventSourcedSync(dbReady && syncEnabled);
|
|
440
|
+
|
|
441
|
+
if (!dbReady) return <Loading />;
|
|
202
442
|
return <YourRoutes />;
|
|
203
443
|
}
|
|
204
444
|
```
|
|
205
445
|
|
|
206
|
-
Optional
|
|
446
|
+
Optional sync helpers:
|
|
207
447
|
|
|
208
448
|
```typescript
|
|
209
449
|
// sync-events.ts
|
|
@@ -214,10 +454,7 @@ export async function manualSyncEvents() {
|
|
|
214
454
|
return database.manualSync();
|
|
215
455
|
}
|
|
216
456
|
|
|
217
|
-
// use-event-sourced-sync.ts
|
|
218
|
-
import { useQuery } from "@tanstack/react-query";
|
|
219
|
-
import { manualSyncEvents } from "@/data-access-layer/sync-events";
|
|
220
|
-
|
|
457
|
+
// use-event-sourced-sync.ts — polls manualSync when enabled
|
|
221
458
|
export function useEventSourcedSync(enabled: boolean) {
|
|
222
459
|
return useQuery({
|
|
223
460
|
queryKey: ["sync"],
|
|
@@ -226,9 +463,42 @@ export function useEventSourcedSync(enabled: boolean) {
|
|
|
226
463
|
refetchInterval: 60_000,
|
|
227
464
|
});
|
|
228
465
|
}
|
|
466
|
+
|
|
467
|
+
// use-sync-enabled.ts — live-query settings.syncEnabled for UI
|
|
468
|
+
export function useSyncEnabled(dbReady: boolean) {
|
|
469
|
+
const { data = [] } = useLiveQuery(
|
|
470
|
+
(query) =>
|
|
471
|
+
query
|
|
472
|
+
.from({ setting: db.collections.settings })
|
|
473
|
+
.where(({ setting }) => eq(setting.id, APP_SETTINGS_ID)),
|
|
474
|
+
[dbReady],
|
|
475
|
+
);
|
|
476
|
+
if (!dbReady) return true;
|
|
477
|
+
return data[0]?.syncEnabled ?? true;
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Settings UI toggle (calls `setSyncEnabled` from `app-settings.ts`):
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
import { setSyncEnabled } from "@/data-access-layer/app-settings";
|
|
485
|
+
import { useSyncEnabled } from "@/hooks/common/use-sync-enabled";
|
|
486
|
+
|
|
487
|
+
function SettingsPage() {
|
|
488
|
+
const syncEnabled = useSyncEnabled(true);
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<Switch
|
|
492
|
+
checked={syncEnabled}
|
|
493
|
+
onCheckedChange={(checked) => void setSyncEnabled(checked)}
|
|
494
|
+
/>
|
|
495
|
+
);
|
|
496
|
+
}
|
|
229
497
|
```
|
|
230
498
|
|
|
231
|
-
|
|
499
|
+
Disable manual **Sync now** when sync is off: `const syncEnabled = useSyncEnabled(true)` and pass it to your button's `disabled` prop.
|
|
500
|
+
|
|
501
|
+
### 7. Write data
|
|
232
502
|
|
|
233
503
|
Use collections like normal TanStack DB. Mutations are logged to the event store automatically.
|
|
234
504
|
|
|
@@ -257,6 +527,7 @@ db.collections.settings.insert({
|
|
|
257
527
|
id: "app",
|
|
258
528
|
theme: "dark",
|
|
259
529
|
language: "en",
|
|
530
|
+
syncEnabled: true,
|
|
260
531
|
});
|
|
261
532
|
|
|
262
533
|
db.collections.todos.update("todo-id", (draft) => {
|
|
@@ -267,7 +538,7 @@ db.collections.todos.update("todo-id", (draft) => {
|
|
|
267
538
|
db.collections.todos.delete("todo-id");
|
|
268
539
|
```
|
|
269
540
|
|
|
270
|
-
For `settings`, use a fixed id (e.g. `"app"`) as a singleton row:
|
|
541
|
+
For `settings`, use a fixed id (e.g. `"app"`) as a singleton row. Prefer `ensureAppSettings()` at startup instead of inserting manually:
|
|
271
542
|
|
|
272
543
|
```typescript
|
|
273
544
|
db.collections.settings.update("app", (draft) => {
|
|
@@ -275,7 +546,7 @@ db.collections.settings.update("app", (draft) => {
|
|
|
275
546
|
});
|
|
276
547
|
```
|
|
277
548
|
|
|
278
|
-
###
|
|
549
|
+
### 8. Read data
|
|
279
550
|
|
|
280
551
|
```typescript
|
|
281
552
|
import { useLiveQuery } from "@tanstack/react-db";
|
|
@@ -306,7 +577,7 @@ function TodoList() {
|
|
|
306
577
|
}
|
|
307
578
|
```
|
|
308
579
|
|
|
309
|
-
###
|
|
580
|
+
### 9. Sync
|
|
310
581
|
|
|
311
582
|
```typescript
|
|
312
583
|
import { ensureDb } from "./data-access-layer/collections";
|
|
@@ -317,7 +588,7 @@ await db.sync();
|
|
|
317
588
|
window.addEventListener("online", () => void db.sync());
|
|
318
589
|
```
|
|
319
590
|
|
|
320
|
-
Or use a periodic hook (see step
|
|
591
|
+
Or use a periodic hook (see step 6). Respect `syncEnabled` — background sync should stay off when the user opts out.
|
|
321
592
|
|
|
322
593
|
## Inspecting sync state
|
|
323
594
|
|
|
@@ -493,6 +764,28 @@ const transport: SyncTransport = {
|
|
|
493
764
|
const db = await createEventSourcedDB({ sync: transport, /* ... */ });
|
|
494
765
|
```
|
|
495
766
|
|
|
767
|
+
## Opting out of sync
|
|
768
|
+
|
|
769
|
+
`syncEnabled` defaults to `true` on `createBrowserEventSourcedDB`. When disabled:
|
|
770
|
+
|
|
771
|
+
- `sync()` skips push/pull (returns `{ pushed: 0, pulled: 0, errors: [] }`)
|
|
772
|
+
- `manualSync()` still replays pending inbox events locally but does not hit the network
|
|
773
|
+
- Background sync hooks should pass `dbReady && syncEnabled` as their `enabled` flag
|
|
774
|
+
|
|
775
|
+
Toggle at runtime:
|
|
776
|
+
|
|
777
|
+
```typescript
|
|
778
|
+
const database = await ensureDb();
|
|
779
|
+
database.setSyncEnabled(false);
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
**Recommended pattern:** persist the preference in your `settings` collection and mirror it on startup — see [App settings](#5-app-settings-app-settingsts) (`ensureAppSettings` + `setSyncEnabled`). That keeps the Settings UI, background sync hook, and engine in sync.
|
|
783
|
+
|
|
784
|
+
```typescript
|
|
785
|
+
await setSyncEnabled(false);
|
|
786
|
+
// updates settings row + calls db.setSyncEnabled(false)
|
|
787
|
+
```
|
|
788
|
+
|
|
496
789
|
## React Native
|
|
497
790
|
|
|
498
791
|
Same helper shape via `createReactNativeEventSourcedDB` (from `event-sourced-collection/react-native`). React Native can't auto-open the database, so `load` returns the `database` driver alongside the platform functions.
|
|
@@ -550,11 +843,12 @@ The recommended entry points. Import from `event-sourced-collection/browser` or
|
|
|
550
843
|
|
|
551
844
|
| Option | Required | Description |
|
|
552
845
|
| ------ | -------- | ----------- |
|
|
553
|
-
| `collections` | Yes | Collection definitions (`getKey`, optional `schemaVersion`) — must not use the reserved ids `outbox`/`inbox` |
|
|
846
|
+
| `collections` | Yes | Collection definitions (`getKey`, optional `schemaVersion`, optional `indexes[]`) — must not use the reserved ids `outbox`/`inbox` |
|
|
554
847
|
| `load` | Yes | Async callback returning the platform modules + `createCollection` + `persistedCollectionOptions`. Keeps platform/framework imports in your app |
|
|
555
848
|
| `databaseName` | Browser only | SQLite database file name |
|
|
556
849
|
| `coordinatorDbName` | No (browser) | Multi-tab coordinator name; defaults to `databaseName` without the `.sqlite` suffix |
|
|
557
850
|
| `sync` | No | Offline by default. Same shapes as `createEventSourcedDB` |
|
|
851
|
+
| `syncEnabled` | No | Default `true`. When `false`, push/pull are skipped until re-enabled |
|
|
558
852
|
| `schemaVersion` | No | Default `1` |
|
|
559
853
|
| `debug` | No | `boolean` or a custom logger |
|
|
560
854
|
|
|
@@ -577,11 +871,14 @@ Low-level core for full control or non-standard platforms. The helpers above cal
|
|
|
577
871
|
| `persistence` | Yes | TanStack DB persistence config |
|
|
578
872
|
| `createCollection` | Yes | TanStack DB `createCollection` |
|
|
579
873
|
| `persistedCollectionOptions` | Yes | From your platform package |
|
|
580
|
-
| `collections` | Yes | Collection definitions (`getKey`, optional `schemaVersion`) — must not use the reserved ids `outbox`/`inbox` |
|
|
874
|
+
| `collections` | Yes | Collection definitions (`getKey`, optional `schemaVersion`, optional `indexes[]`) — must not use the reserved ids `outbox`/`inbox` |
|
|
581
875
|
| `sync` | No | Offline by default. Accepts typed `pushEvents`/`pullEvents`, URL `pushUrl`/`pullUrl`, legacy URL `push`/`pull`, or legacy `SyncTransport` |
|
|
876
|
+
| `syncEnabled` | No | Default `true`. When `false`, push/pull are skipped until re-enabled |
|
|
582
877
|
| `schemaVersion` | No | Default `1` |
|
|
583
878
|
|
|
584
|
-
Returns `{ collections, sync, manualSync, dispose }`, where `collections` includes your registered collections plus the built-in `outbox` and `inbox`.
|
|
879
|
+
Returns `{ collections, sync, manualSync, getSyncEnabled, setSyncEnabled, dispose }`, where `collections` includes your registered collections plus the built-in `outbox` and `inbox`.
|
|
880
|
+
|
|
881
|
+
- `getSyncEnabled()` / `setSyncEnabled(enabled)` — runtime toggle for push/pull (default `true` at init via `syncEnabled` config option).
|
|
585
882
|
|
|
586
883
|
### `createBrowserPlatform(deps, config)`
|
|
587
884
|
|
package/dist/browser.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as CreateCollectionFn, D as SyncUrlConfig, E as SyncTransport, O as EventSourcedLogger, S as SQLiteDriver, i as EventSourcedDB, j as PersistedCollectionOptionsFn, w as SyncHandlersConfig } from "./types-BX8dtGOu.mjs";
|
|
2
2
|
import { PersistedCollectionCoordinator, PersistedCollectionPersistence } from "@tanstack/db-sqlite-persistence-core";
|
|
3
3
|
import { BrowserCollectionCoordinatorOptions, BrowserWASQLiteDatabase as BrowserWASQLiteDatabase$1, BrowserWASQLitePersistenceOptions, OpenBrowserWASQLiteOPFSDatabaseOptions } from "@tanstack/browser-db-sqlite-persistence";
|
|
4
4
|
|
|
@@ -34,6 +34,11 @@ declare function createBrowserPlatform(deps: BrowserPlatformDeps, config: Browse
|
|
|
34
34
|
type CollectionDefConstraint = {
|
|
35
35
|
getKey: (state: never) => string | number;
|
|
36
36
|
schemaVersion?: number;
|
|
37
|
+
indexes?: ReadonlyArray<{
|
|
38
|
+
select: (row: never) => unknown;
|
|
39
|
+
name?: string;
|
|
40
|
+
indexType?: import("@tanstack/db").IndexConstructor<string | number>;
|
|
41
|
+
}>;
|
|
37
42
|
};
|
|
38
43
|
type BrowserEventSourcedModules = BrowserPlatformDeps & {
|
|
39
44
|
createCollection: CreateCollectionFn;
|
|
@@ -45,6 +50,7 @@ type BrowserEventSourcedDBConfig<TDefs extends Record<string, CollectionDefConst
|
|
|
45
50
|
collections: TDefs;
|
|
46
51
|
load: () => Promise<BrowserEventSourcedModules>;
|
|
47
52
|
sync?: SyncHandlersConfig | SyncUrlConfig | SyncTransport;
|
|
53
|
+
syncEnabled?: boolean;
|
|
48
54
|
schemaVersion?: number;
|
|
49
55
|
debug?: boolean | EventSourcedLogger;
|
|
50
56
|
};
|
package/dist/browser.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.mts","names":[],"sources":["../src/platforms/browser-types.ts","../src/platforms/browser-wa-sqlite-driver.ts","../src/platforms/browser.ts","../src/platforms/browser-event-sourced-db.ts"],"mappings":";;;;;KAQY,0BAAA,GAA6B,8BAA8B;EACrE,OAAO;AAAA;AAAA,KAGG,mBAAA;EACV,+BAAA,GACE,OAAA,EAAS,sCAAA,KACN,OAAA,CAAQ,yBAAA;EACb,gCAAA,GACE,OAAA,EAAS,iCAAA,KACN,8BAAA;EACL,4BAAA,OACE,OAAA,EAAS,mCAAA,KACN,0BAAA;AAAA;AAAA,KAGK,qBAAA;EACV,YAAA;EACA,iBAAiB;AAAA;AAAA,KAGP,qBAAA;EACV,MAAA,EAD+B,YAAA;EAE/B,WAAA,EAAa,8BAAA;EACb,KAAA,QAAa,OAAA;AAAA;;;KC9BH,uBAAA;EACV,OAAA,mBACE,GAAA,UACA,MAAA,GAAS,aAAA,cACN,OAAA,CAAQ,aAAA,CAAc,IAAA;EAC3B,KAAA,SAAc,OAAA;AAAA;;;iBCUM,qBAAA,CACpB,IAAA,EAAM,mBAAA,EACN,MAAA,EAAQ,qBAAA,GACP,OAAA,CAAQ,qBAAA;;;KCPN,uBAAA;EACH,MAAA,GAAS,KAAA;EACT,aAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"browser.d.mts","names":[],"sources":["../src/platforms/browser-types.ts","../src/platforms/browser-wa-sqlite-driver.ts","../src/platforms/browser.ts","../src/platforms/browser-event-sourced-db.ts"],"mappings":";;;;;KAQY,0BAAA,GAA6B,8BAA8B;EACrE,OAAO;AAAA;AAAA,KAGG,mBAAA;EACV,+BAAA,GACE,OAAA,EAAS,sCAAA,KACN,OAAA,CAAQ,yBAAA;EACb,gCAAA,GACE,OAAA,EAAS,iCAAA,KACN,8BAAA;EACL,4BAAA,OACE,OAAA,EAAS,mCAAA,KACN,0BAAA;AAAA;AAAA,KAGK,qBAAA;EACV,YAAA;EACA,iBAAiB;AAAA;AAAA,KAGP,qBAAA;EACV,MAAA,EAD+B,YAAA;EAE/B,WAAA,EAAa,8BAAA;EACb,KAAA,QAAa,OAAA;AAAA;;;KC9BH,uBAAA;EACV,OAAA,mBACE,GAAA,UACA,MAAA,GAAS,aAAA,cACN,OAAA,CAAQ,aAAA,CAAc,IAAA;EAC3B,KAAA,SAAc,OAAA;AAAA;;;iBCUM,qBAAA,CACpB,IAAA,EAAM,mBAAA,EACN,MAAA,EAAQ,qBAAA,GACP,OAAA,CAAQ,qBAAA;;;KCPN,uBAAA;EACH,MAAA,GAAS,KAAA;EACT,aAAA;EACA,OAAA,GAAU,aAAa;IACrB,MAAA,GAAS,GAAA;IACT,IAAA;IACA,SAAA,0BAAmC,gBAAA;EAAA;AAAA;AAAA,KAI3B,0BAAA,GAA6B,mBAAA;EACvC,gBAAA,EAAkB,kBAAA;EAClB,0BAAA,EAA4B,4BAAA;AAAA;AAAA,KAGlB,2BAAA,eAA0C,MAAA,SAAe,uBAAA;EACnE,YAAA;EACA,iBAAA;EACA,WAAA,EAAa,KAAA;EACb,IAAA,QAAY,OAAA,CAAQ,0BAAA;EACpB,IAAA,GAAO,kBAAA,GAAqB,aAAA,GAAgB,aAAA;EAC5C,WAAA;EACA,aAAA;EACA,KAAA,aAAkB,kBAAA;AAAA;AAAA,KAGR,2BAAA,eAA0C,MAAA,SAAe,uBAAA;EACnE,QAAA,QAAgB,OAAA,CAAQ,cAAA,CAAe,KAAA;EACvC,EAAA,EAAI,cAAA,CAAe,KAAA;EACnB,KAAA,QAAa,OAAA;AAAA;AAAA,iBAGC,2BAAA,qBACM,MAAA,SAAe,uBAAA,GACnC,MAAA,EAAQ,2BAAA,CAA4B,KAAA,IAAS,2BAAA,CAA4B,KAAA"}
|
package/dist/browser.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createEventSourcedDB, t as createLazySingleton } from "./lazy-singleton-
|
|
1
|
+
import { n as createEventSourcedDB, t as createLazySingleton } from "./lazy-singleton-CB50yOVo.mjs";
|
|
2
2
|
//#region src/platforms/browser-wa-sqlite-driver.ts
|
|
3
3
|
function assertTransactionCallbackHasDriverArg(fn) {
|
|
4
4
|
if (fn.length > 0) return;
|
|
@@ -124,6 +124,7 @@ function createBrowserEventSourcedDB(config) {
|
|
|
124
124
|
persistedCollectionOptions: modules.persistedCollectionOptions,
|
|
125
125
|
collections: config.collections,
|
|
126
126
|
sync: config.sync,
|
|
127
|
+
syncEnabled: config.syncEnabled,
|
|
127
128
|
schemaVersion: config.schemaVersion,
|
|
128
129
|
debug: config.debug
|
|
129
130
|
});
|
package/dist/browser.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.mjs","names":[],"sources":["../src/platforms/browser-wa-sqlite-driver.ts","../src/platforms/browser.ts","../src/platforms/browser-event-sourced-db.ts"],"sourcesContent":["import type { SQLiteDriver } from \"../types\";\n\nexport type BrowserWASQLiteDatabase = {\n execute: <TRow = unknown>(\n sql: string,\n params?: ReadonlyArray<unknown>,\n ) => Promise<ReadonlyArray<TRow>>;\n close?: () => Promise<void> | void;\n};\n\nfunction assertTransactionCallbackHasDriverArg(\n fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,\n): void {\n if (fn.length > 0) {\n return;\n }\n\n throw new Error(\n \"SQLiteDriver.transaction callback must accept the transaction driver argument\",\n );\n}\n\nfunction assertDatabaseShape(\n database: BrowserWASQLiteDatabase,\n): asserts database is BrowserWASQLiteDatabase {\n if (typeof database.execute !== \"function\") {\n throw new Error(\"Browser wa-sqlite database handle must provide execute(sql, params?)\");\n }\n}\n\nexport class BrowserWASQLiteDriver implements SQLiteDriver {\n private readonly database: BrowserWASQLiteDatabase;\n private queue: Promise<void> = Promise.resolve();\n private nextSavepointId = 1;\n private closed = false;\n\n constructor(database: BrowserWASQLiteDatabase) {\n assertDatabaseShape(database);\n this.database = database;\n }\n\n async exec(sql: string): Promise<void> {\n await this.enqueue(async () => {\n await this.database.execute(sql);\n });\n }\n\n async query<T>(sql: string, params: ReadonlyArray<unknown> = []): Promise<ReadonlyArray<T>> {\n return this.enqueue(() => this.database.execute<T>(sql, params));\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n await this.enqueue(async () => {\n await this.database.execute(sql, params);\n });\n }\n\n async transaction<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn);\n\n return this.enqueue(async () => {\n await this.database.execute(`BEGIN IMMEDIATE`);\n try {\n const result = await fn(this.createTransactionDriver());\n await this.database.execute(`COMMIT`);\n return result;\n } catch (error) {\n try {\n await this.database.execute(`ROLLBACK`);\n } catch {\n }\n throw error;\n }\n });\n }\n\n async transactionWithDriver<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n return this.transaction(fn);\n }\n\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n this.closed = true;\n\n if (typeof this.database.close === \"function\") {\n await Promise.resolve(this.database.close());\n }\n }\n\n private createTransactionDriver(): SQLiteDriver {\n return {\n exec: (sql) => this.database.execute(sql).then(() => undefined),\n query: <T>(sql: string, params: ReadonlyArray<unknown> = []) =>\n this.database.execute<T>(sql, params),\n run: (sql: string, params: ReadonlyArray<unknown> = []) =>\n this.database.execute(sql, params).then(() => undefined),\n transaction: <T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>) =>\n this.runNestedTransaction(fn),\n transactionWithDriver: <T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>) =>\n this.runNestedTransaction(fn),\n };\n }\n\n private async runNestedTransaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn);\n\n const savepointName = `tsdb_sp_${this.nextSavepointId}`;\n this.nextSavepointId++;\n await this.database.execute(`SAVEPOINT ${savepointName}`);\n try {\n const result = await fn(this.createTransactionDriver());\n await this.database.execute(`RELEASE SAVEPOINT ${savepointName}`);\n return result;\n } catch (error) {\n await this.database.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`);\n await this.database.execute(`RELEASE SAVEPOINT ${savepointName}`);\n throw error;\n }\n }\n\n private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {\n const queuedOperation = this.queue.then(operation, operation);\n this.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n );\n return queuedOperation;\n }\n}\n","import type { PersistedCollectionPersistence } from \"../types\";\nimport { BrowserWASQLiteDriver } from \"./browser-wa-sqlite-driver\";\nimport type {\n BrowserPlatformConfig,\n BrowserPlatformDeps,\n BrowserPlatformResult,\n} from \"./browser-types\";\n\nexport type {\n BrowserCoordinatorInstance,\n BrowserPlatformConfig,\n BrowserPlatformDeps,\n BrowserPlatformResult,\n} from \"./browser-types\";\n\nexport type { BrowserWASQLiteDatabase } from \"./browser-wa-sqlite-driver\";\n\nexport async function createBrowserPlatform(\n deps: BrowserPlatformDeps,\n config: BrowserPlatformConfig,\n): Promise<BrowserPlatformResult> {\n const database = await deps.openBrowserWASQLiteOPFSDatabase({\n databaseName: config.databaseName,\n });\n\n const coordinator = new deps.BrowserCollectionCoordinator({\n dbName: config.coordinatorDbName ?? config.databaseName.replace(/\\.sqlite$/, \"\"),\n });\n\n const persistence = deps.createBrowserWASQLitePersistence({\n database,\n coordinator,\n });\n\n const driver = new BrowserWASQLiteDriver(database);\n\n return {\n driver,\n persistence,\n close: async () => {\n coordinator.dispose();\n await driver.close();\n },\n };\n}\n","import { createEventSourcedDB } from \"../create-event-sourced-db\";\nimport { createLazySingleton } from \"../lazy-singleton\";\nimport type { CreateCollectionFn, PersistedCollectionOptionsFn } from \"../persisted-collection\";\nimport type {\n EventSourcedDB,\n EventSourcedLogger,\n SyncHandlersConfig,\n SyncTransport,\n SyncUrlConfig,\n} from \"../types\";\nimport { createBrowserPlatform } from \"./browser\";\nimport type { BrowserPlatformDeps } from \"./browser-types\";\n\ntype CollectionDefConstraint = {\n getKey: (state: never) => string | number;\n schemaVersion?: number;\n};\n\nexport type BrowserEventSourcedModules = BrowserPlatformDeps & {\n createCollection: CreateCollectionFn;\n persistedCollectionOptions: PersistedCollectionOptionsFn;\n};\n\nexport type BrowserEventSourcedDBConfig<TDefs extends Record<string, CollectionDefConstraint>> = {\n databaseName: string;\n coordinatorDbName?: string;\n collections: TDefs;\n load: () => Promise<BrowserEventSourcedModules>;\n sync?: SyncHandlersConfig | SyncUrlConfig | SyncTransport;\n schemaVersion?: number;\n debug?: boolean | EventSourcedLogger;\n};\n\nexport type BrowserEventSourcedDBHandle<TDefs extends Record<string, CollectionDefConstraint>> = {\n ensureDb: () => Promise<EventSourcedDB<TDefs>>;\n db: EventSourcedDB<TDefs>;\n close: () => Promise<void>;\n};\n\nexport function createBrowserEventSourcedDB<\n const TDefs extends Record<string, CollectionDefConstraint>,\n>(config: BrowserEventSourcedDBConfig<TDefs>): BrowserEventSourcedDBHandle<TDefs> {\n let activeDb: EventSourcedDB<TDefs> | null = null;\n let closePlatform: (() => Promise<void>) | null = null;\n\n const factory = async (): Promise<EventSourcedDB<TDefs>> => {\n const modules = await config.load();\n\n const platform = await createBrowserPlatform(\n {\n openBrowserWASQLiteOPFSDatabase: modules.openBrowserWASQLiteOPFSDatabase,\n createBrowserWASQLitePersistence: modules.createBrowserWASQLitePersistence,\n BrowserCollectionCoordinator: modules.BrowserCollectionCoordinator,\n },\n {\n databaseName: config.databaseName,\n coordinatorDbName: config.coordinatorDbName,\n },\n );\n\n closePlatform = platform.close;\n\n const database = await createEventSourcedDB<TDefs>({\n persistence: platform.persistence,\n createCollection: modules.createCollection,\n persistedCollectionOptions: modules.persistedCollectionOptions,\n collections: config.collections,\n sync: config.sync,\n schemaVersion: config.schemaVersion,\n debug: config.debug,\n });\n\n activeDb = database;\n return database;\n };\n\n const singleton = createLazySingleton<EventSourcedDB<TDefs>>(factory, {\n guard: assertBrowserEnvironment,\n notInitializedMessage:\n \"Event-sourced DB is not initialized. Call ensureDb() before using db.\",\n });\n\n const close = async (): Promise<void> => {\n if (activeDb) {\n activeDb.dispose();\n activeDb = null;\n }\n if (closePlatform) {\n await closePlatform();\n closePlatform = null;\n }\n singleton.reset();\n };\n\n return {\n ensureDb: singleton.ensure,\n db: singleton.proxy,\n close,\n };\n}\n\nfunction assertBrowserEnvironment(): void {\n if (typeof window === \"undefined\") {\n throw new Error(\"Event-sourced DB (browser) is only available in a browser environment.\");\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"browser.mjs","names":[],"sources":["../src/platforms/browser-wa-sqlite-driver.ts","../src/platforms/browser.ts","../src/platforms/browser-event-sourced-db.ts"],"sourcesContent":["import type { SQLiteDriver } from \"../types\";\n\nexport type BrowserWASQLiteDatabase = {\n execute: <TRow = unknown>(\n sql: string,\n params?: ReadonlyArray<unknown>,\n ) => Promise<ReadonlyArray<TRow>>;\n close?: () => Promise<void> | void;\n};\n\nfunction assertTransactionCallbackHasDriverArg(\n fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,\n): void {\n if (fn.length > 0) {\n return;\n }\n\n throw new Error(\n \"SQLiteDriver.transaction callback must accept the transaction driver argument\",\n );\n}\n\nfunction assertDatabaseShape(\n database: BrowserWASQLiteDatabase,\n): asserts database is BrowserWASQLiteDatabase {\n if (typeof database.execute !== \"function\") {\n throw new Error(\"Browser wa-sqlite database handle must provide execute(sql, params?)\");\n }\n}\n\nexport class BrowserWASQLiteDriver implements SQLiteDriver {\n private readonly database: BrowserWASQLiteDatabase;\n private queue: Promise<void> = Promise.resolve();\n private nextSavepointId = 1;\n private closed = false;\n\n constructor(database: BrowserWASQLiteDatabase) {\n assertDatabaseShape(database);\n this.database = database;\n }\n\n async exec(sql: string): Promise<void> {\n await this.enqueue(async () => {\n await this.database.execute(sql);\n });\n }\n\n async query<T>(sql: string, params: ReadonlyArray<unknown> = []): Promise<ReadonlyArray<T>> {\n return this.enqueue(() => this.database.execute<T>(sql, params));\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n await this.enqueue(async () => {\n await this.database.execute(sql, params);\n });\n }\n\n async transaction<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn);\n\n return this.enqueue(async () => {\n await this.database.execute(`BEGIN IMMEDIATE`);\n try {\n const result = await fn(this.createTransactionDriver());\n await this.database.execute(`COMMIT`);\n return result;\n } catch (error) {\n try {\n await this.database.execute(`ROLLBACK`);\n } catch {\n }\n throw error;\n }\n });\n }\n\n async transactionWithDriver<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n return this.transaction(fn);\n }\n\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n this.closed = true;\n\n if (typeof this.database.close === \"function\") {\n await Promise.resolve(this.database.close());\n }\n }\n\n private createTransactionDriver(): SQLiteDriver {\n return {\n exec: (sql) => this.database.execute(sql).then(() => undefined),\n query: <T>(sql: string, params: ReadonlyArray<unknown> = []) =>\n this.database.execute<T>(sql, params),\n run: (sql: string, params: ReadonlyArray<unknown> = []) =>\n this.database.execute(sql, params).then(() => undefined),\n transaction: <T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>) =>\n this.runNestedTransaction(fn),\n transactionWithDriver: <T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>) =>\n this.runNestedTransaction(fn),\n };\n }\n\n private async runNestedTransaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn);\n\n const savepointName = `tsdb_sp_${this.nextSavepointId}`;\n this.nextSavepointId++;\n await this.database.execute(`SAVEPOINT ${savepointName}`);\n try {\n const result = await fn(this.createTransactionDriver());\n await this.database.execute(`RELEASE SAVEPOINT ${savepointName}`);\n return result;\n } catch (error) {\n await this.database.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`);\n await this.database.execute(`RELEASE SAVEPOINT ${savepointName}`);\n throw error;\n }\n }\n\n private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {\n const queuedOperation = this.queue.then(operation, operation);\n this.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n );\n return queuedOperation;\n }\n}\n","import type { PersistedCollectionPersistence } from \"../types\";\nimport { BrowserWASQLiteDriver } from \"./browser-wa-sqlite-driver\";\nimport type {\n BrowserPlatformConfig,\n BrowserPlatformDeps,\n BrowserPlatformResult,\n} from \"./browser-types\";\n\nexport type {\n BrowserCoordinatorInstance,\n BrowserPlatformConfig,\n BrowserPlatformDeps,\n BrowserPlatformResult,\n} from \"./browser-types\";\n\nexport type { BrowserWASQLiteDatabase } from \"./browser-wa-sqlite-driver\";\n\nexport async function createBrowserPlatform(\n deps: BrowserPlatformDeps,\n config: BrowserPlatformConfig,\n): Promise<BrowserPlatformResult> {\n const database = await deps.openBrowserWASQLiteOPFSDatabase({\n databaseName: config.databaseName,\n });\n\n const coordinator = new deps.BrowserCollectionCoordinator({\n dbName: config.coordinatorDbName ?? config.databaseName.replace(/\\.sqlite$/, \"\"),\n });\n\n const persistence = deps.createBrowserWASQLitePersistence({\n database,\n coordinator,\n });\n\n const driver = new BrowserWASQLiteDriver(database);\n\n return {\n driver,\n persistence,\n close: async () => {\n coordinator.dispose();\n await driver.close();\n },\n };\n}\n","import { createEventSourcedDB } from \"../create-event-sourced-db\";\nimport { createLazySingleton } from \"../lazy-singleton\";\nimport type { CreateCollectionFn, PersistedCollectionOptionsFn } from \"../persisted-collection\";\nimport type {\n EventSourcedDB,\n EventSourcedLogger,\n SyncHandlersConfig,\n SyncTransport,\n SyncUrlConfig,\n} from \"../types\";\nimport { createBrowserPlatform } from \"./browser\";\nimport type { BrowserPlatformDeps } from \"./browser-types\";\n\ntype CollectionDefConstraint = {\n getKey: (state: never) => string | number;\n schemaVersion?: number;\n indexes?: ReadonlyArray<{\n select: (row: never) => unknown;\n name?: string;\n indexType?: import(\"@tanstack/db\").IndexConstructor<string | number>;\n }>;\n};\n\nexport type BrowserEventSourcedModules = BrowserPlatformDeps & {\n createCollection: CreateCollectionFn;\n persistedCollectionOptions: PersistedCollectionOptionsFn;\n};\n\nexport type BrowserEventSourcedDBConfig<TDefs extends Record<string, CollectionDefConstraint>> = {\n databaseName: string;\n coordinatorDbName?: string;\n collections: TDefs;\n load: () => Promise<BrowserEventSourcedModules>;\n sync?: SyncHandlersConfig | SyncUrlConfig | SyncTransport;\n syncEnabled?: boolean;\n schemaVersion?: number;\n debug?: boolean | EventSourcedLogger;\n};\n\nexport type BrowserEventSourcedDBHandle<TDefs extends Record<string, CollectionDefConstraint>> = {\n ensureDb: () => Promise<EventSourcedDB<TDefs>>;\n db: EventSourcedDB<TDefs>;\n close: () => Promise<void>;\n};\n\nexport function createBrowserEventSourcedDB<\n const TDefs extends Record<string, CollectionDefConstraint>,\n>(config: BrowserEventSourcedDBConfig<TDefs>): BrowserEventSourcedDBHandle<TDefs> {\n let activeDb: EventSourcedDB<TDefs> | null = null;\n let closePlatform: (() => Promise<void>) | null = null;\n\n const factory = async (): Promise<EventSourcedDB<TDefs>> => {\n const modules = await config.load();\n\n const platform = await createBrowserPlatform(\n {\n openBrowserWASQLiteOPFSDatabase: modules.openBrowserWASQLiteOPFSDatabase,\n createBrowserWASQLitePersistence: modules.createBrowserWASQLitePersistence,\n BrowserCollectionCoordinator: modules.BrowserCollectionCoordinator,\n },\n {\n databaseName: config.databaseName,\n coordinatorDbName: config.coordinatorDbName,\n },\n );\n\n closePlatform = platform.close;\n\n const database = await createEventSourcedDB<TDefs>({\n persistence: platform.persistence,\n createCollection: modules.createCollection,\n persistedCollectionOptions: modules.persistedCollectionOptions,\n collections: config.collections,\n sync: config.sync,\n syncEnabled: config.syncEnabled,\n schemaVersion: config.schemaVersion,\n debug: config.debug,\n });\n\n activeDb = database;\n return database;\n };\n\n const singleton = createLazySingleton<EventSourcedDB<TDefs>>(factory, {\n guard: assertBrowserEnvironment,\n notInitializedMessage:\n \"Event-sourced DB is not initialized. Call ensureDb() before using db.\",\n });\n\n const close = async (): Promise<void> => {\n if (activeDb) {\n activeDb.dispose();\n activeDb = null;\n }\n if (closePlatform) {\n await closePlatform();\n closePlatform = null;\n }\n singleton.reset();\n };\n\n return {\n ensureDb: singleton.ensure,\n db: singleton.proxy,\n close,\n };\n}\n\nfunction assertBrowserEnvironment(): void {\n if (typeof window === \"undefined\") {\n throw new Error(\"Event-sourced DB (browser) is only available in a browser environment.\");\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as CreateCollectionFn, C as ServerEvent, D as SyncUrlConfig, E as SyncTransport, O as EventSourcedLogger, S as SQLiteDriver, T as SyncResult, _ as PushConfirmation, a as EventSourcedDBConfig, b as PushResponse, c as InferState, d as OutboundEvent, f as OutboxEntry, g as PullResponse, h as PullEventsFn, i as EventSourcedDB, j as PersistedCollectionOptionsFn, k as createEventSourcedLogger, l as ManualSyncResult, m as PersistedCollectionPersistence, n as CollectionIndexDef, o as InboxEntry, p as OutboxSyncStatus, r as CollectionMap, s as InferKey, t as CollectionDef, u as MutationType, v as PushEventsFn, w as SyncHandlersConfig, x as ReservedCollections, y as PushFailure } from "./types-BX8dtGOu.mjs";
|
|
2
2
|
import { uuidv7 as generateEventId } from "uuidv7";
|
|
3
|
+
import { IndexConstructor } from "@tanstack/db";
|
|
3
4
|
|
|
4
5
|
//#region src/create-event-sourced-db.d.ts
|
|
5
6
|
type CollectionDefConstraint = {
|
|
6
7
|
getKey: (state: never) => string | number;
|
|
7
8
|
schemaVersion?: number;
|
|
9
|
+
indexes?: ReadonlyArray<{
|
|
10
|
+
select: (row: never) => unknown;
|
|
11
|
+
name?: string;
|
|
12
|
+
indexType?: IndexConstructor<string | number>;
|
|
13
|
+
}>;
|
|
8
14
|
};
|
|
9
15
|
declare function createEventSourcedDB<const TDefs extends Record<string, CollectionDefConstraint>>(config: EventSourcedDBConfig<TDefs>): Promise<EventSourcedDB<TDefs>>;
|
|
10
16
|
//#endregion
|
|
@@ -33,5 +39,5 @@ declare class SyncPullError extends Error {
|
|
|
33
39
|
constructor(status: number, body: string);
|
|
34
40
|
}
|
|
35
41
|
//#endregion
|
|
36
|
-
export { type CollectionDef, type CollectionMap, type CreateCollectionFn, type EventSourcedDB, type EventSourcedDBConfig, type EventSourcedLogger, type InboxEntry, type InferKey, type InferState, type LazySingleton, type LazySingletonOptions, type ManualSyncResult, type MutationType, type OutboundEvent, type OutboxEntry, type OutboxSyncStatus, type PersistedCollectionOptionsFn, type PersistedCollectionPersistence, type PullEventsFn, type PullResponse, type PushConfirmation, type PushEventsFn, type PushFailure, type PushResponse, type ReservedCollections, type SQLiteDriver, type ServerEvent, type SyncHandlersConfig, SyncPullError, SyncPushError, type SyncResult, type SyncTransport, type SyncUrlConfig, createEventSourcedDB, createEventSourcedLogger, createHttpTransport, createLazySingleton, generateEventId };
|
|
42
|
+
export { type CollectionDef, type CollectionIndexDef, type CollectionMap, type CreateCollectionFn, type EventSourcedDB, type EventSourcedDBConfig, type EventSourcedLogger, type InboxEntry, type InferKey, type InferState, type LazySingleton, type LazySingletonOptions, type ManualSyncResult, type MutationType, type OutboundEvent, type OutboxEntry, type OutboxSyncStatus, type PersistedCollectionOptionsFn, type PersistedCollectionPersistence, type PullEventsFn, type PullResponse, type PushConfirmation, type PushEventsFn, type PushFailure, type PushResponse, type ReservedCollections, type SQLiteDriver, type ServerEvent, type SyncHandlersConfig, SyncPullError, SyncPushError, type SyncResult, type SyncTransport, type SyncUrlConfig, createEventSourcedDB, createEventSourcedLogger, createHttpTransport, createLazySingleton, generateEventId };
|
|
37
43
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/create-event-sourced-db.ts","../src/lazy-singleton.ts","../src/sync.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/create-event-sourced-db.ts","../src/lazy-singleton.ts","../src/sync.ts"],"mappings":";;;;;KAuBK,uBAAA;EACH,MAAA,GAAS,KAAA;EACT,aAAA;EACA,OAAA,GAAU,aAAa;IACrB,MAAA,GAAS,GAAA;IACT,IAAA;IACA,SAAA,GAAY,gBAAA;EAAA;AAAA;AAAA,iBAuCM,oBAAA,qBACA,MAAA,SAAe,uBAAA,GACnC,MAAA,EAAQ,oBAAA,CAAqB,KAAA,IAAS,OAAA,CAAQ,cAAA,CAAe,KAAA;;;KCtEnD,oBAAA;EACV,KAAA;EACA,qBAAqB;AAAA;AAAA,KAGX,aAAA;EACV,MAAA,QAAc,OAAA,CAAQ,CAAA;EACtB,KAAA,EAAO,CAAA;EACP,KAAA;AAAA;AAAA,iBAGc,mBAAA,mBACd,OAAA,QAAe,OAAA,CAAQ,CAAA,GACvB,OAAA,GAAS,oBAAA,GACR,aAAA,CAAc,CAAA;;;iBCKD,mBAAA,CAAoB,MAAA,EAAQ,aAAA,GAAgB,aAAa;AAAA,cA8I5D,aAAA,SAAsB,KAAK;EAAA,SAEpB,MAAA;EAAA,SACA,IAAA;cADA,MAAA,UACA,IAAA;AAAA;AAAA,cAOP,aAAA,SAAsB,KAAK;EAAA,SAEpB,MAAA;EAAA,SACA,IAAA;cADA,MAAA,UACA,IAAA;AAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as SyncPullError, i as createEventSourcedLogger, n as createEventSourcedDB, o as SyncPushError, r as generateEventId, s as createHttpTransport, t as createLazySingleton } from "./lazy-singleton-
|
|
1
|
+
import { a as SyncPullError, i as createEventSourcedLogger, n as createEventSourcedDB, o as SyncPushError, r as generateEventId, s as createHttpTransport, t as createLazySingleton } from "./lazy-singleton-CB50yOVo.mjs";
|
|
2
2
|
export { SyncPullError, SyncPushError, createEventSourcedDB, createEventSourcedLogger, createHttpTransport, createLazySingleton, generateEventId };
|
|
@@ -155,9 +155,11 @@ async function createEventSourcedDB(config) {
|
|
|
155
155
|
assertReservedNamesAvailable(config.collections);
|
|
156
156
|
const log = createEventSourcedLogger(config.debug);
|
|
157
157
|
const transport = createSyncTransport(config.sync);
|
|
158
|
+
let syncEnabled = config.syncEnabled ?? true;
|
|
158
159
|
log.info("creating event-sourced db", {
|
|
159
160
|
collectionIds: Object.keys(config.collections),
|
|
160
|
-
hasTransport: transport !== null
|
|
161
|
+
hasTransport: transport !== null,
|
|
162
|
+
syncEnabled
|
|
161
163
|
});
|
|
162
164
|
const defaultSchemaVersion = config.schemaVersion ?? 1;
|
|
163
165
|
const seq = { value: 0 };
|
|
@@ -172,11 +174,13 @@ async function createEventSourcedDB(config) {
|
|
|
172
174
|
getKey,
|
|
173
175
|
persistence: config.persistence,
|
|
174
176
|
schemaVersion: def.schemaVersion ?? defaultSchemaVersion,
|
|
177
|
+
gcTime: Number.POSITIVE_INFINITY,
|
|
175
178
|
onInsert: createMutationHook(outbox, collectionId, "insert", seq, log),
|
|
176
179
|
onUpdate: createMutationHook(outbox, collectionId, "update", seq, log),
|
|
177
180
|
onDelete: createMutationHook(outbox, collectionId, "delete", seq, log)
|
|
178
181
|
});
|
|
179
182
|
const collection = config.createCollection(options);
|
|
183
|
+
applyCollectionIndexes(collection, collectionId, def.indexes, log);
|
|
180
184
|
const hasAcceptMutations = Boolean(collection.utils?.acceptMutations);
|
|
181
185
|
log.debug("registered collection", {
|
|
182
186
|
collectionId,
|
|
@@ -201,6 +205,14 @@ async function createEventSourcedDB(config) {
|
|
|
201
205
|
});
|
|
202
206
|
await replayInbox(inbox, replayTargets, log);
|
|
203
207
|
async function sync() {
|
|
208
|
+
if (!syncEnabled) {
|
|
209
|
+
log.debug("sync skipped: sync disabled");
|
|
210
|
+
return {
|
|
211
|
+
pushed: 0,
|
|
212
|
+
pulled: 0,
|
|
213
|
+
errors: []
|
|
214
|
+
};
|
|
215
|
+
}
|
|
204
216
|
if (!transport) {
|
|
205
217
|
log.warn("sync skipped: no transport configured");
|
|
206
218
|
return {
|
|
@@ -250,7 +262,7 @@ async function createEventSourcedDB(config) {
|
|
|
250
262
|
let pushed = 0;
|
|
251
263
|
let pulled = 0;
|
|
252
264
|
let replayed = 0;
|
|
253
|
-
if (transport) {
|
|
265
|
+
if (syncEnabled && transport) {
|
|
254
266
|
try {
|
|
255
267
|
if (transport.push) pushed = await pushOutbox(outbox, transport.push, log);
|
|
256
268
|
else log.debug("manual sync push skipped: no push transport configured");
|
|
@@ -267,7 +279,8 @@ async function createEventSourcedDB(config) {
|
|
|
267
279
|
log.error("manual sync pull failed", { message: error.message });
|
|
268
280
|
errors.push(error);
|
|
269
281
|
}
|
|
270
|
-
} else log.
|
|
282
|
+
} else if (!syncEnabled) log.debug("manual sync push/pull skipped: sync disabled");
|
|
283
|
+
else log.warn("manual sync: no transport configured, skipping push/pull");
|
|
271
284
|
try {
|
|
272
285
|
replayed = await replayInbox(inbox, replayTargets, log);
|
|
273
286
|
} catch (err) {
|
|
@@ -288,6 +301,13 @@ async function createEventSourcedDB(config) {
|
|
|
288
301
|
errors
|
|
289
302
|
};
|
|
290
303
|
}
|
|
304
|
+
function getSyncEnabled() {
|
|
305
|
+
return syncEnabled;
|
|
306
|
+
}
|
|
307
|
+
function setSyncEnabled(enabled) {
|
|
308
|
+
syncEnabled = enabled;
|
|
309
|
+
log.debug("sync enabled updated", { syncEnabled: enabled });
|
|
310
|
+
}
|
|
291
311
|
function dispose() {
|
|
292
312
|
log.debug("disposing event-sourced db");
|
|
293
313
|
for (const subscription of subscriptions) subscription.unsubscribe();
|
|
@@ -296,6 +316,8 @@ async function createEventSourcedDB(config) {
|
|
|
296
316
|
collections,
|
|
297
317
|
sync,
|
|
298
318
|
manualSync,
|
|
319
|
+
getSyncEnabled,
|
|
320
|
+
setSyncEnabled,
|
|
299
321
|
dispose
|
|
300
322
|
};
|
|
301
323
|
}
|
|
@@ -308,6 +330,25 @@ function createMetaCollection(config, id, getKey, schemaVersion) {
|
|
|
308
330
|
});
|
|
309
331
|
return config.createCollection(options);
|
|
310
332
|
}
|
|
333
|
+
function applyCollectionIndexes(collection, collectionId, indexes, log) {
|
|
334
|
+
if (!indexes?.length) return;
|
|
335
|
+
const indexable = collection;
|
|
336
|
+
const register = () => {
|
|
337
|
+
if ((indexable.getIndexMetadata?.().length ?? 0) > 0) return;
|
|
338
|
+
for (const indexDef of indexes) {
|
|
339
|
+
indexable.createIndex(indexDef.select, {
|
|
340
|
+
name: indexDef.name,
|
|
341
|
+
indexType: indexDef.indexType
|
|
342
|
+
});
|
|
343
|
+
log.debug("registered collection index", {
|
|
344
|
+
collectionId,
|
|
345
|
+
name: indexDef.name
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
register();
|
|
350
|
+
indexable.on?.("status:ready", register);
|
|
351
|
+
}
|
|
311
352
|
function createMutationHook(outbox, collectionId, type, seq, log) {
|
|
312
353
|
return async (params) => {
|
|
313
354
|
for (const mutation of params.transaction.mutations) {
|
|
@@ -626,4 +667,4 @@ function createLazySingleton(factory, options = {}) {
|
|
|
626
667
|
//#endregion
|
|
627
668
|
export { SyncPullError as a, createEventSourcedLogger as i, createEventSourcedDB as n, SyncPushError as o, generateEventId as r, createHttpTransport as s, createLazySingleton as t };
|
|
628
669
|
|
|
629
|
-
//# sourceMappingURL=lazy-singleton-
|
|
670
|
+
//# sourceMappingURL=lazy-singleton-CB50yOVo.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy-singleton-CB50yOVo.mjs","names":[],"sources":["../src/sync.ts","../src/utils/logger.ts","../src/create-event-sourced-db.ts","../src/lazy-singleton.ts"],"sourcesContent":["import type {\n OutboundEvent,\n PullEventsFn,\n PullResponse,\n PushConfirmation,\n PushEventsFn,\n PushResponse,\n SyncHandlersConfig,\n SyncTransport,\n SyncUrlConfig,\n} from \"./types\";\n\nexport type NormalizedSyncTransport = {\n push?: PushEventsFn;\n pull?: (since: number) => Promise<PullResponse>;\n};\n\ntype HeaderConfig = SyncHandlersConfig[\"headers\"];\n\nexport function createHttpTransport(config: SyncUrlConfig): SyncTransport {\n return {\n push: createHttpPushEvents(config.push, config.headers),\n pull: createHttpPullEvents(config.pull, config.headers),\n };\n}\n\nexport function createSyncTransport(\n config?: SyncHandlersConfig | SyncUrlConfig | SyncTransport,\n): NormalizedSyncTransport | null {\n if (!config) {\n return null;\n }\n\n if (isTransport(config)) {\n return {\n push: config.push,\n pull: config.pull,\n };\n }\n\n const pushUrl = getPushUrl(config);\n const pullUrl = getPullUrl(config);\n\n const push = \"pushEvents\" in config && config.pushEvents\n ? config.pushEvents\n : pushUrl\n ? createHttpPushEvents(pushUrl, config.headers)\n : undefined;\n\n const pullEvents = \"pullEvents\" in config ? config.pullEvents : undefined;\n const pull = pullEvents\n ? createPullFromHandler(pullEvents)\n : pullUrl\n ? createHttpPullEvents(pullUrl, config.headers)\n : undefined;\n\n if (!push && !pull) {\n return null;\n }\n\n return { push, pull };\n}\n\nexport function normalizePushResponse(\n response: PushResponse | ReadonlyArray<PushConfirmation>,\n): PushResponse {\n if (isConfirmationArray(response)) {\n return { confirmed: response };\n }\n\n return {\n confirmed: response.confirmed,\n failed: response.failed,\n };\n}\n\nfunction isConfirmationArray(\n response: PushResponse | ReadonlyArray<PushConfirmation>,\n): response is ReadonlyArray<PushConfirmation> {\n return Array.isArray(response);\n}\n\nfunction createHttpPushEvents(url: string, headers: HeaderConfig): PushEventsFn {\n return async (events: ReadonlyArray<OutboundEvent>): Promise<PushResponse> => {\n if (events.length === 0) return { confirmed: [] };\n\n const resolvedHeaders = await resolveHeaders(headers);\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...resolvedHeaders },\n body: JSON.stringify(events),\n });\n\n if (!response.ok) {\n throw new SyncPushError(response.status, await response.text());\n }\n\n return response.json() as Promise<PushResponse>;\n };\n}\n\nfunction createPullFromHandler(pullEvents: PullEventsFn): (since: number) => Promise<PullResponse> {\n return (since: number) => pullEvents({ since });\n}\n\nfunction createHttpPullEvents(\n url: string,\n headers: HeaderConfig,\n): (since: number) => Promise<PullResponse> {\n return async (since: number): Promise<PullResponse> => {\n const resolvedHeaders = await resolveHeaders(headers);\n const pullUrl = appendSince(url, since);\n\n const response = await fetch(pullUrl, {\n headers: { Accept: \"application/json\", ...resolvedHeaders },\n });\n\n if (!response.ok) {\n throw new SyncPullError(response.status, await response.text());\n }\n\n return response.json() as Promise<PullResponse>;\n };\n}\n\nasync function resolveHeaders(headers: HeaderConfig): Promise<Record<string, string>> {\n if (!headers) return {};\n if (typeof headers === \"function\") return headers();\n return headers;\n}\n\nfunction appendSince(url: string, since: number): string {\n const separator = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${separator}since=${encodeURIComponent(String(since))}`;\n}\n\nfunction getPushUrl(config: SyncHandlersConfig | SyncUrlConfig): string | undefined {\n if (\"pushUrl\" in config && config.pushUrl) {\n return config.pushUrl;\n }\n\n if (\"push\" in config && typeof config.push === \"string\") {\n return config.push;\n }\n\n return undefined;\n}\n\nfunction getPullUrl(config: SyncHandlersConfig | SyncUrlConfig): string | undefined {\n if (\"pullUrl\" in config && config.pullUrl) {\n return config.pullUrl;\n }\n\n if (\"pull\" in config && typeof config.pull === \"string\") {\n return config.pull;\n }\n\n return undefined;\n}\n\nexport class SyncPushError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: string,\n ) {\n super(`Event push failed: HTTP ${status}`);\n this.name = \"SyncPushError\";\n }\n}\n\nexport class SyncPullError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: string,\n ) {\n super(`Event pull failed: HTTP ${status}`);\n this.name = \"SyncPullError\";\n }\n}\n\nexport function isTransport(\n value: SyncHandlersConfig | SyncUrlConfig | SyncTransport,\n): value is SyncTransport {\n return \"push\" in value && typeof value.push === \"function\";\n}\n","export type EventSourcedLogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport type EventSourcedLogger = {\n debug: (message: string, data?: Record<string, unknown>) => void;\n info: (message: string, data?: Record<string, unknown>) => void;\n warn: (message: string, data?: Record<string, unknown>) => void;\n error: (message: string, data?: Record<string, unknown>) => void;\n};\n\nconst noopLogger: EventSourcedLogger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\nconst LOG_PREFIX = \"[event-sourced]\";\n\nexport function createEventSourcedLogger(\n debug?: boolean | EventSourcedLogger,\n): EventSourcedLogger {\n if (debug === undefined || debug === false) {\n return noopLogger;\n }\n\n if (typeof debug === \"object\") {\n return debug;\n }\n\n return {\n debug: (message, data) => {\n if (data === undefined) {\n console.debug(LOG_PREFIX, message);\n return;\n }\n console.debug(LOG_PREFIX, message, data);\n },\n info: (message, data) => {\n if (data === undefined) {\n console.info(LOG_PREFIX, message);\n return;\n }\n console.info(LOG_PREFIX, message, data);\n },\n warn: (message, data) => {\n if (data === undefined) {\n console.warn(LOG_PREFIX, message);\n return;\n }\n console.warn(LOG_PREFIX, message, data);\n },\n error: (message, data) => {\n if (data === undefined) {\n console.error(LOG_PREFIX, message);\n return;\n }\n console.error(LOG_PREFIX, message, data);\n },\n };\n}\n","import type { Collection, IndexConstructor } from \"@tanstack/db\";\nimport { createSyncTransport, normalizePushResponse } from \"./sync\";\nimport type { NormalizedSyncTransport } from \"./sync\";\nimport type { EventSourcedLogger } from \"./utils/logger\";\nimport { createEventSourcedLogger } from \"./utils/logger\";\nimport { generateEventId } from \"./utils/uuid\";\nimport type {\n CollectionMap,\n EventSourcedDB,\n EventSourcedDBConfig,\n InboxEntry,\n MutationType,\n OutboundEvent,\n OutboxEntry,\n ServerEvent,\n SyncResult,\n ManualSyncResult,\n} from \"./types\";\n\nconst OUTBOX_ID = \"outbox\";\nconst INBOX_ID = \"inbox\";\nconst RESERVED_IDS = new Set<string>([OUTBOX_ID, INBOX_ID]);\n\ntype CollectionDefConstraint = {\n getKey: (state: never) => string | number;\n schemaVersion?: number;\n indexes?: ReadonlyArray<{\n select: (row: never) => unknown;\n name?: string;\n indexType?: IndexConstructor<string | number>;\n }>;\n};\n\ntype ReplayMutation = {\n mutationId: string;\n type: MutationType;\n key: string | number;\n modified: Record<string, unknown>;\n original: Record<string, unknown>;\n changes: Record<string, unknown>;\n collection: AcceptMutationsCollection;\n};\n\ntype AcceptMutationsCollection = {\n id?: string;\n utils: {\n acceptMutations?: (transaction: { mutations: Array<ReplayMutation> }) => Promise<void> | void;\n };\n};\n\ntype MutationHookParams = {\n transaction: {\n mutations: ReadonlyArray<{\n mutationId: string;\n key: string | number;\n modified: Record<string, unknown>;\n original: Record<string, unknown>;\n }>;\n };\n};\n\ntype SeqCounter = { value: number };\n\ntype MetaCollectionFactory = Pick<\n EventSourcedDBConfig<Record<string, CollectionDefConstraint>>,\n \"createCollection\" | \"persistedCollectionOptions\" | \"persistence\"\n>;\n\nexport async function createEventSourcedDB<\n const TDefs extends Record<string, CollectionDefConstraint>,\n>(config: EventSourcedDBConfig<TDefs>): Promise<EventSourcedDB<TDefs>> {\n assertReservedNamesAvailable(config.collections);\n\n const log = createEventSourcedLogger(config.debug);\n\n const transport = createSyncTransport(config.sync);\n let syncEnabled = config.syncEnabled ?? true;\n\n log.info(\"creating event-sourced db\", {\n collectionIds: Object.keys(config.collections),\n hasTransport: transport !== null,\n syncEnabled,\n });\n\n const defaultSchemaVersion = config.schemaVersion ?? 1;\n const seq: SeqCounter = { value: 0 };\n\n const outbox = createMetaCollection<OutboxEntry>(\n config,\n OUTBOX_ID,\n (entry) => entry.eventId,\n defaultSchemaVersion,\n );\n\n const inbox = createMetaCollection<InboxEntry>(\n config,\n INBOX_ID,\n (entry) => entry.eventId,\n defaultSchemaVersion,\n );\n\n const userCollections = {} as CollectionMap<TDefs>;\n\n for (const collectionId of Object.keys(config.collections)) {\n const def = config.collections[collectionId]!;\n const getKey = def.getKey as (item: Record<string, unknown>) => string | number;\n\n const options = config.persistedCollectionOptions<Record<string, unknown>, string | number>({\n id: collectionId,\n getKey,\n persistence: config.persistence,\n schemaVersion: def.schemaVersion ?? defaultSchemaVersion,\n gcTime: Number.POSITIVE_INFINITY,\n onInsert: createMutationHook(outbox, collectionId, \"insert\", seq, log),\n onUpdate: createMutationHook(outbox, collectionId, \"update\", seq, log),\n onDelete: createMutationHook(outbox, collectionId, \"delete\", seq, log),\n });\n\n const collection = config.createCollection(options);\n applyCollectionIndexes(collection, collectionId, def.indexes, log);\n const hasAcceptMutations = Boolean(\n (collection as AcceptMutationsCollection).utils?.acceptMutations,\n );\n\n log.debug(\"registered collection\", {\n collectionId,\n hasAcceptMutations,\n });\n\n (userCollections as Record<string, unknown>)[collectionId] = collection;\n }\n\n const collections = {\n ...(userCollections as CollectionMap<TDefs>),\n outbox,\n inbox,\n } as EventSourcedDB<TDefs>[\"collections\"];\n\n const replayTargets = collections as unknown as Record<string, AcceptMutationsCollection>;\n\n const subscriptions = [\n outbox.subscribeChanges(() => {}),\n inbox.subscribeChanges(() => {}),\n ];\n\n await outbox.preload();\n await inbox.preload();\n\n seq.value = nextLocalSeq(outbox);\n\n log.info(\"preloaded meta collections\", {\n outboxCount: outbox.state.size,\n inboxCount: inbox.state.size,\n nextLocalSeq: seq.value,\n });\n\n await replayInbox(inbox, replayTargets, log);\n\n async function sync(): Promise<SyncResult> {\n if (!syncEnabled) {\n log.debug(\"sync skipped: sync disabled\");\n return { pushed: 0, pulled: 0, errors: [] };\n }\n\n if (!transport) {\n log.warn(\"sync skipped: no transport configured\");\n return { pushed: 0, pulled: 0, errors: [] };\n }\n\n log.info(\"sync started\");\n\n await outbox.preload();\n await inbox.preload();\n\n const errors: Error[] = [];\n let pushed = 0;\n let pulled = 0;\n\n try {\n if (transport.push) {\n pushed = await pushOutbox(outbox, transport.push, log);\n } else {\n log.debug(\"push skipped: no push transport configured\");\n }\n } catch (err) {\n const error = toError(err);\n log.error(\"push outbox failed\", { message: error.message });\n errors.push(error);\n }\n\n try {\n if (transport.pull) {\n pulled = await pullInbox(outbox, inbox, transport.pull, replayTargets, log);\n } else {\n log.debug(\"pull skipped: no pull transport configured\");\n }\n } catch (err) {\n const error = toError(err);\n log.error(\"pull inbox failed\", { message: error.message });\n errors.push(error);\n }\n\n log.info(\"sync finished\", { pushed, pulled, errorCount: errors.length });\n\n return { pushed, pulled, errors };\n }\n\n async function manualSync(): Promise<ManualSyncResult> {\n log.info(\"manual sync started\");\n\n await outbox.preload();\n await inbox.preload();\n\n const errors: Error[] = [];\n let pushed = 0;\n let pulled = 0;\n let replayed = 0;\n\n if (syncEnabled && transport) {\n try {\n if (transport.push) {\n pushed = await pushOutbox(outbox, transport.push, log);\n } else {\n log.debug(\"manual sync push skipped: no push transport configured\");\n }\n } catch (err) {\n const error = toError(err);\n log.error(\"manual sync push failed\", { message: error.message });\n errors.push(error);\n }\n\n try {\n if (transport.pull) {\n pulled = await pullInbox(outbox, inbox, transport.pull, replayTargets, log);\n } else {\n log.debug(\"manual sync pull skipped: no pull transport configured\");\n }\n } catch (err) {\n const error = toError(err);\n log.error(\"manual sync pull failed\", { message: error.message });\n errors.push(error);\n }\n } else if (!syncEnabled) {\n log.debug(\"manual sync push/pull skipped: sync disabled\");\n } else {\n log.warn(\"manual sync: no transport configured, skipping push/pull\");\n }\n\n try {\n replayed = await replayInbox(inbox, replayTargets, log);\n } catch (err) {\n const error = toError(err);\n log.error(\"manual sync replay failed\", { message: error.message });\n errors.push(error);\n }\n\n log.info(\"manual sync finished\", { pushed, pulled, replayed, errorCount: errors.length });\n\n return { pushed, pulled, replayed, errors };\n }\n\n function getSyncEnabled(): boolean {\n return syncEnabled;\n }\n\n function setSyncEnabled(enabled: boolean): void {\n syncEnabled = enabled;\n log.debug(\"sync enabled updated\", { syncEnabled: enabled });\n }\n\n function dispose(): void {\n log.debug(\"disposing event-sourced db\");\n for (const subscription of subscriptions) {\n subscription.unsubscribe();\n }\n }\n\n return { collections, sync, manualSync, getSyncEnabled, setSyncEnabled, dispose };\n}\n\nfunction createMetaCollection<TEntry extends object>(\n config: MetaCollectionFactory,\n id: string,\n getKey: (entry: TEntry) => string,\n schemaVersion: number,\n): Collection<TEntry, string> {\n const options = config.persistedCollectionOptions<TEntry, string>({\n id,\n getKey,\n persistence: config.persistence,\n schemaVersion,\n });\n\n return config.createCollection(options);\n}\n\ntype IndexableCollection = {\n createIndex: (\n indexCallback: (row: Record<string, unknown>) => unknown,\n config?: {\n name?: string;\n indexType?: IndexConstructor<string | number>;\n },\n ) => unknown;\n getIndexMetadata?: () => ReadonlyArray<unknown>;\n on?: (event: \"status:ready\", callback: () => void) => () => void;\n};\n\nfunction applyCollectionIndexes(\n collection: Collection<Record<string, unknown>, string | number>,\n collectionId: string,\n indexes: CollectionDefConstraint[\"indexes\"],\n log: EventSourcedLogger,\n): void {\n if (!indexes?.length) {\n return;\n }\n\n const indexable = collection as IndexableCollection;\n\n const register = (): void => {\n if ((indexable.getIndexMetadata?.().length ?? 0) > 0) {\n return;\n }\n\n for (const indexDef of indexes) {\n indexable.createIndex(indexDef.select as (row: Record<string, unknown>) => unknown, {\n name: indexDef.name,\n indexType: indexDef.indexType,\n });\n\n log.debug(\"registered collection index\", {\n collectionId,\n name: indexDef.name,\n });\n }\n };\n\n register();\n\n indexable.on?.(\"status:ready\", register);\n}\n\nfunction createMutationHook(\n outbox: Collection<OutboxEntry, string>,\n collectionId: string,\n type: MutationType,\n seq: SeqCounter,\n log: EventSourcedLogger,\n) {\n return async (params: MutationHookParams): Promise<Record<string, unknown>> => {\n for (const mutation of params.transaction.mutations) {\n const payload = type === \"delete\" ? mutation.original : mutation.modified;\n\n const entry: OutboxEntry = {\n eventId: generateEventId(),\n collectionId,\n type,\n key: mutation.key,\n payload,\n timestamp: Date.now(),\n localSeq: seq.value++,\n globalSeq: null,\n sync: false,\n syncStatus: \"pending\",\n attemptCount: 0,\n lastAttemptAt: null,\n lastError: null,\n lastErrorCode: null,\n retryable: null,\n };\n\n await outbox.insert(entry).isPersisted.promise;\n\n log.debug(\"outbox entry created\", {\n eventId: entry.eventId,\n collectionId,\n type,\n key: entry.key,\n localSeq: entry.localSeq,\n });\n }\n\n return {};\n };\n}\n\nasync function pushOutbox(\n outbox: Collection<OutboxEntry, string>,\n push: NonNullable<NormalizedSyncTransport[\"push\"]>,\n log: EventSourcedLogger,\n): Promise<number> {\n const pending = [...outbox.state.values()]\n .filter((entry) => !entry.sync && entry.syncStatus !== \"failed\")\n .sort((a, b) => a.localSeq - b.localSeq);\n\n log.debug(\"push outbox\", { pendingCount: pending.length });\n\n if (pending.length === 0) return 0;\n\n const attemptAt = Date.now();\n\n for (const entry of pending) {\n await outbox\n .update(entry.eventId, (draft) => {\n draft.syncStatus = \"pending\";\n draft.attemptCount = (draft.attemptCount ?? 0) + 1;\n draft.lastAttemptAt = attemptAt;\n draft.lastError = null;\n draft.lastErrorCode = null;\n draft.retryable = null;\n })\n .isPersisted.promise;\n }\n\n const outbound: OutboundEvent[] = pending.map((entry) => ({\n eventId: entry.eventId,\n collectionId: entry.collectionId,\n type: entry.type,\n key: entry.key,\n payload: entry.payload,\n timestamp: entry.timestamp,\n }));\n\n const response = normalizePushResponse(await push(outbound));\n\n log.info(\"push outbox confirmed\", {\n sent: outbound.length,\n confirmed: response.confirmed.length,\n failed: response.failed?.length ?? 0,\n });\n\n for (const confirmation of response.confirmed) {\n await outbox\n .update(confirmation.eventId, (draft) => {\n draft.sync = true;\n draft.syncStatus = \"synced\";\n draft.globalSeq = confirmation.globalSeq;\n draft.lastError = null;\n draft.lastErrorCode = null;\n draft.retryable = null;\n })\n .isPersisted.promise;\n\n log.debug(\"outbox entry marked pushed\", {\n eventId: confirmation.eventId,\n globalSeq: confirmation.globalSeq,\n });\n }\n\n for (const failure of response.failed ?? []) {\n await outbox\n .update(failure.eventId, (draft) => {\n draft.sync = false;\n draft.syncStatus = \"failed\";\n draft.lastError = failure.message;\n draft.lastErrorCode = failure.code ?? null;\n draft.retryable = failure.retryable ?? null;\n })\n .isPersisted.promise;\n\n log.warn(\"outbox entry marked failed\", {\n eventId: failure.eventId,\n message: failure.message,\n code: failure.code,\n retryable: failure.retryable,\n });\n }\n\n return response.confirmed.length;\n}\n\nasync function pullInbox(\n outbox: Collection<OutboxEntry, string>,\n inbox: Collection<InboxEntry, string>,\n pull: NonNullable<NormalizedSyncTransport[\"pull\"]>,\n targets: Record<string, AcceptMutationsCollection>,\n log: EventSourcedLogger,\n): Promise<number> {\n let pulled = 0;\n let hasMore = true;\n\n while (hasMore) {\n const since = currentSince(inbox);\n log.debug(\"pull inbox page\", { since });\n\n const response = await pull(since);\n\n log.debug(\"pull inbox response\", {\n since,\n eventCount: response.events.length,\n hasMore: response.hasMore,\n cursor: response.cursor,\n });\n\n if (response.events.length === 0) break;\n\n const sorted = [...response.events].sort((a, b) => a.globalSeq - b.globalSeq);\n\n for (const event of sorted) {\n if (outbox.has(event.eventId)) {\n await markInboxEventSynced(inbox, event);\n\n log.debug(\"pull skipped: event originated locally\", {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n });\n continue;\n }\n\n const existing = inbox.get(event.eventId);\n if (existing?.sync) {\n log.debug(\"pull skipped: inbox already applied\", {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n });\n continue;\n }\n\n if (!existing) {\n await inbox.insert(toInboxEntry(event, false)).isPersisted.promise;\n log.debug(\"inbox entry inserted\", {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n collectionId: event.collectionId,\n });\n }\n\n const applied = await replayEvent(\n targets,\n event.collectionId,\n event.eventId,\n event.type,\n event.key,\n event.payload,\n log,\n );\n\n if (!applied) {\n return pulled;\n }\n\n await inbox\n .update(event.eventId, (draft) => {\n draft.sync = true;\n })\n .isPersisted.promise;\n\n log.info(\"pull replay applied\", {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n collectionId: event.collectionId,\n type: event.type,\n key: event.key,\n });\n\n pulled++;\n }\n\n hasMore = response.hasMore;\n }\n\n log.info(\"pull inbox finished\", { pulled });\n\n return pulled;\n}\n\nasync function replayInbox(\n inbox: Collection<InboxEntry, string>,\n targets: Record<string, AcceptMutationsCollection>,\n log: EventSourcedLogger,\n): Promise<number> {\n const pending = [...inbox.state.values()]\n .filter((entry) => !entry.sync)\n .sort((a, b) => a.globalSeq - b.globalSeq);\n\n log.info(\"pending inbox replay started\", { pendingCount: pending.length });\n\n let replayed = 0;\n\n for (const entry of pending) {\n const applied = await replayEvent(\n targets,\n entry.collectionId,\n entry.eventId,\n entry.type,\n entry.key,\n entry.payload,\n log,\n );\n\n if (!applied) {\n break;\n }\n\n await inbox\n .update(entry.eventId, (draft) => {\n draft.sync = true;\n })\n .isPersisted.promise;\n\n replayed++;\n\n log.info(\"pending inbox replay applied\", {\n eventId: entry.eventId,\n globalSeq: entry.globalSeq,\n collectionId: entry.collectionId,\n });\n }\n\n log.info(\"pending inbox replay finished\", { replayed });\n\n return replayed;\n}\n\nasync function replayEvent(\n targets: Record<string, AcceptMutationsCollection>,\n collectionId: string,\n eventId: string,\n type: MutationType,\n key: string | number,\n payload: Record<string, unknown>,\n log: EventSourcedLogger,\n): Promise<boolean> {\n if (RESERVED_IDS.has(collectionId)) {\n log.warn(\"replay skipped: reserved collection\", { eventId, collectionId, type, key });\n return false;\n }\n\n const target = targets[collectionId];\n if (!target) {\n log.warn(\"replay skipped: unknown collection\", {\n eventId,\n collectionId,\n type,\n key,\n knownCollections: Object.keys(targets).filter((id) => !RESERVED_IDS.has(id)),\n });\n return false;\n }\n\n if (!target.utils.acceptMutations) {\n log.warn(\"replay skipped: collection missing acceptMutations\", {\n eventId,\n collectionId,\n type,\n key,\n targetId: target.id,\n });\n return false;\n }\n\n log.debug(\"replay applying mutation\", { eventId, collectionId, type, key });\n\n try {\n await target.utils.acceptMutations({\n mutations: [\n {\n mutationId: eventId,\n type,\n key,\n modified: payload,\n original: payload,\n changes: payload,\n collection: target,\n },\n ],\n });\n\n log.info(\"replay mutation accepted\", { eventId, collectionId, type, key });\n } catch (err) {\n const error = toError(err);\n log.error(\"replay mutation failed\", {\n eventId,\n collectionId,\n type,\n key,\n message: error.message,\n });\n throw error;\n }\n\n return true;\n}\n\nfunction currentSince(inbox: Collection<InboxEntry, string>): number {\n let max = 0;\n\n for (const entry of inbox.state.values()) {\n if (entry.sync && entry.globalSeq > max) max = entry.globalSeq;\n }\n\n return max;\n}\n\nfunction nextLocalSeq(outbox: Collection<OutboxEntry, string>): number {\n let max = -1;\n\n for (const entry of outbox.state.values()) {\n if (entry.localSeq > max) max = entry.localSeq;\n }\n\n return max + 1;\n}\n\nasync function markInboxEventSynced(\n inbox: Collection<InboxEntry, string>,\n event: ServerEvent,\n): Promise<void> {\n const existing = inbox.get(event.eventId);\n\n if (!existing) {\n await inbox.insert(toInboxEntry(event, true)).isPersisted.promise;\n return;\n }\n\n if (!existing.sync || existing.globalSeq !== event.globalSeq) {\n await inbox\n .update(event.eventId, (draft) => {\n draft.globalSeq = event.globalSeq;\n draft.sync = true;\n })\n .isPersisted.promise;\n }\n}\n\nfunction toInboxEntry(event: ServerEvent, sync: boolean): InboxEntry {\n return {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n collectionId: event.collectionId,\n type: event.type,\n key: event.key,\n payload: event.payload,\n timestamp: event.timestamp,\n sync,\n };\n}\n\nfunction assertReservedNamesAvailable(collections: Record<string, unknown>): void {\n for (const id of Object.keys(collections)) {\n if (RESERVED_IDS.has(id)) {\n throw new Error(\n `Collection id \"${id}\" is reserved. \"outbox\" and \"inbox\" are built-in collections.`,\n );\n }\n }\n}\n\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n","export type LazySingletonOptions = {\n guard?: () => void;\n notInitializedMessage?: string;\n};\n\nexport type LazySingleton<T extends object> = {\n ensure: () => Promise<T>;\n proxy: T;\n reset: () => void;\n};\n\nexport function createLazySingleton<T extends object>(\n factory: () => Promise<T>,\n options: LazySingletonOptions = {},\n): LazySingleton<T> {\n let instance: T | null = null;\n let initPromise: Promise<T> | null = null;\n\n const ensure = async (): Promise<T> => {\n options.guard?.();\n\n if (instance) {\n return instance;\n }\n\n if (!initPromise) {\n initPromise = factory()\n .then((resolved) => {\n instance = resolved;\n return resolved;\n })\n .catch((error: unknown) => {\n initPromise = null;\n throw error;\n });\n }\n\n return initPromise;\n };\n\n const message =\n options.notInitializedMessage ?? \"Instance is not initialized. Call ensure() first.\";\n\n const proxy = new Proxy({} as T, {\n get(_target, prop, receiver) {\n if (!instance) {\n throw new Error(message);\n }\n return Reflect.get(instance, prop, receiver);\n },\n has(_target, prop) {\n if (!instance) {\n throw new Error(message);\n }\n return Reflect.has(instance, prop);\n },\n getPrototypeOf() {\n if (!instance) {\n throw new Error(message);\n }\n return Reflect.getPrototypeOf(instance);\n },\n });\n\n const reset = (): void => {\n instance = null;\n initPromise = null;\n };\n\n return { ensure, proxy, reset };\n}\n"],"mappings":"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"}
|
package/dist/react-native.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as CreateCollectionFn, D as SyncUrlConfig, E as SyncTransport, O as EventSourcedLogger, S as SQLiteDriver, i as EventSourcedDB, j as PersistedCollectionOptionsFn, m as PersistedCollectionPersistence, w as SyncHandlersConfig } from "./types-BX8dtGOu.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/platforms/react-native.d.ts
|
|
4
4
|
type ReactNativePlatformDeps = {
|
|
@@ -19,6 +19,11 @@ declare function createReactNativePlatform(deps: ReactNativePlatformDeps, config
|
|
|
19
19
|
type CollectionDefConstraint = {
|
|
20
20
|
getKey: (state: never) => string | number;
|
|
21
21
|
schemaVersion?: number;
|
|
22
|
+
indexes?: ReadonlyArray<{
|
|
23
|
+
select: (row: never) => unknown;
|
|
24
|
+
name?: string;
|
|
25
|
+
indexType?: import("@tanstack/db").IndexConstructor<string | number>;
|
|
26
|
+
}>;
|
|
22
27
|
};
|
|
23
28
|
type ReactNativeEventSourcedModules = ReactNativePlatformDeps & {
|
|
24
29
|
database: SQLiteDriver;
|
|
@@ -29,6 +34,7 @@ type ReactNativeEventSourcedDBConfig<TDefs extends Record<string, CollectionDefC
|
|
|
29
34
|
collections: TDefs;
|
|
30
35
|
load: () => Promise<ReactNativeEventSourcedModules>;
|
|
31
36
|
sync?: SyncHandlersConfig | SyncUrlConfig | SyncTransport;
|
|
37
|
+
syncEnabled?: boolean;
|
|
32
38
|
schemaVersion?: number;
|
|
33
39
|
debug?: boolean | EventSourcedLogger;
|
|
34
40
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-native.d.mts","names":[],"sources":["../src/platforms/react-native.ts","../src/platforms/react-native-event-sourced-db.ts"],"mappings":";;;KAEY,uBAAA;EACV,kCAAA,GAAqC,OAAA;IACnC,QAAA,EAAU,YAAA;EAAA,MACN,8BAA8B;AAAA;AAAA,KAG1B,yBAAA;EACV,QAAA,EAAU,YAAY;AAAA;AAAA,KAGZ,yBAAA;EACV,MAAA,EAAQ,YAAA;EACR,WAAA,EAAa,8BAA8B;AAAA;AAAA,iBAG7B,yBAAA,CACd,IAAA,EAAM,uBAAA,EACN,MAAA,EAAQ,yBAAA,GACP,yBAAA;;;KCNE,uBAAA;EACH,MAAA,GAAS,KAAA;EACT,aAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"react-native.d.mts","names":[],"sources":["../src/platforms/react-native.ts","../src/platforms/react-native-event-sourced-db.ts"],"mappings":";;;KAEY,uBAAA;EACV,kCAAA,GAAqC,OAAA;IACnC,QAAA,EAAU,YAAA;EAAA,MACN,8BAA8B;AAAA;AAAA,KAG1B,yBAAA;EACV,QAAA,EAAU,YAAY;AAAA;AAAA,KAGZ,yBAAA;EACV,MAAA,EAAQ,YAAA;EACR,WAAA,EAAa,8BAA8B;AAAA;AAAA,iBAG7B,yBAAA,CACd,IAAA,EAAM,uBAAA,EACN,MAAA,EAAQ,yBAAA,GACP,yBAAA;;;KCNE,uBAAA;EACH,MAAA,GAAS,KAAA;EACT,aAAA;EACA,OAAA,GAAU,aAAa;IACrB,MAAA,GAAS,GAAA;IACT,IAAA;IACA,SAAA,0BAAmC,gBAAA;EAAA;AAAA;AAAA,KAI3B,8BAAA,GAAiC,uBAAA;EAC3C,QAAA,EAAU,YAAA;EACV,gBAAA,EAAkB,kBAAA;EAClB,0BAAA,EAA4B,4BAAA;AAAA;AAAA,KAGlB,+BAAA,eAA8C,MAAA,SAAe,uBAAA;EAErE,WAAA,EAAa,KAAA;EACb,IAAA,QAAY,OAAA,CAAQ,8BAAA;EACpB,IAAA,GAAO,kBAAA,GAAqB,aAAA,GAAgB,aAAA;EAC5C,WAAA;EACA,aAAA;EACA,KAAA,aAAkB,kBAAA;AAAA;AAAA,KAGV,+BAAA,eACI,MAAA,SAAe,uBAAA;EAE7B,QAAA,QAAgB,OAAA,CAAQ,cAAA,CAAe,KAAA;EACvC,EAAA,EAAI,cAAA,CAAe,KAAA;EACnB,KAAA,QAAa,OAAA;AAAA;AAAA,iBAGC,+BAAA,qBACM,MAAA,SAAe,uBAAA,GACnC,MAAA,EAAQ,+BAAA,CAAgC,KAAA,IAAS,+BAAA,CAAgC,KAAA"}
|
package/dist/react-native.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createEventSourcedDB, t as createLazySingleton } from "./lazy-singleton-
|
|
1
|
+
import { n as createEventSourcedDB, t as createLazySingleton } from "./lazy-singleton-CB50yOVo.mjs";
|
|
2
2
|
//#region src/platforms/react-native.ts
|
|
3
3
|
function createReactNativePlatform(deps, config) {
|
|
4
4
|
const persistence = deps.createReactNativeSQLitePersistence({ database: config.database });
|
|
@@ -19,6 +19,7 @@ function createReactNativeEventSourcedDB(config) {
|
|
|
19
19
|
persistedCollectionOptions: modules.persistedCollectionOptions,
|
|
20
20
|
collections: config.collections,
|
|
21
21
|
sync: config.sync,
|
|
22
|
+
syncEnabled: config.syncEnabled,
|
|
22
23
|
schemaVersion: config.schemaVersion,
|
|
23
24
|
debug: config.debug
|
|
24
25
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-native.mjs","names":[],"sources":["../src/platforms/react-native.ts","../src/platforms/react-native-event-sourced-db.ts"],"sourcesContent":["import type { PersistedCollectionPersistence, SQLiteDriver } from \"../types\";\n\nexport type ReactNativePlatformDeps = {\n createReactNativeSQLitePersistence: (options: {\n database: SQLiteDriver;\n }) => PersistedCollectionPersistence;\n};\n\nexport type ReactNativePlatformConfig = {\n database: SQLiteDriver;\n};\n\nexport type ReactNativePlatformResult = {\n driver: SQLiteDriver;\n persistence: PersistedCollectionPersistence;\n};\n\nexport function createReactNativePlatform(\n deps: ReactNativePlatformDeps,\n config: ReactNativePlatformConfig,\n): ReactNativePlatformResult {\n const persistence = deps.createReactNativeSQLitePersistence({\n database: config.database,\n });\n\n return {\n driver: config.database,\n persistence,\n };\n}\n","import { createEventSourcedDB } from \"../create-event-sourced-db\";\nimport { createLazySingleton } from \"../lazy-singleton\";\nimport type { CreateCollectionFn, PersistedCollectionOptionsFn } from \"../persisted-collection\";\nimport type {\n EventSourcedDB,\n EventSourcedLogger,\n SQLiteDriver,\n SyncHandlersConfig,\n SyncTransport,\n SyncUrlConfig,\n} from \"../types\";\nimport { createReactNativePlatform } from \"./react-native\";\nimport type { ReactNativePlatformDeps } from \"./react-native\";\n\ntype CollectionDefConstraint = {\n getKey: (state: never) => string | number;\n schemaVersion?: number;\n};\n\nexport type ReactNativeEventSourcedModules = ReactNativePlatformDeps & {\n database: SQLiteDriver;\n createCollection: CreateCollectionFn;\n persistedCollectionOptions: PersistedCollectionOptionsFn;\n};\n\nexport type ReactNativeEventSourcedDBConfig<TDefs extends Record<string, CollectionDefConstraint>> =\n {\n collections: TDefs;\n load: () => Promise<ReactNativeEventSourcedModules>;\n sync?: SyncHandlersConfig | SyncUrlConfig | SyncTransport;\n schemaVersion?: number;\n debug?: boolean | EventSourcedLogger;\n };\n\nexport type ReactNativeEventSourcedDBHandle<\n TDefs extends Record<string, CollectionDefConstraint>,\n> = {\n ensureDb: () => Promise<EventSourcedDB<TDefs>>;\n db: EventSourcedDB<TDefs>;\n close: () => Promise<void>;\n};\n\nexport function createReactNativeEventSourcedDB<\n const TDefs extends Record<string, CollectionDefConstraint>,\n>(config: ReactNativeEventSourcedDBConfig<TDefs>): ReactNativeEventSourcedDBHandle<TDefs> {\n let activeDb: EventSourcedDB<TDefs> | null = null;\n\n const factory = async (): Promise<EventSourcedDB<TDefs>> => {\n const modules = await config.load();\n\n const platform = createReactNativePlatform(\n { createReactNativeSQLitePersistence: modules.createReactNativeSQLitePersistence },\n { database: modules.database },\n );\n\n const database = await createEventSourcedDB<TDefs>({\n persistence: platform.persistence,\n createCollection: modules.createCollection,\n persistedCollectionOptions: modules.persistedCollectionOptions,\n collections: config.collections,\n sync: config.sync,\n schemaVersion: config.schemaVersion,\n debug: config.debug,\n });\n\n activeDb = database;\n return database;\n };\n\n const singleton = createLazySingleton<EventSourcedDB<TDefs>>(factory, {\n notInitializedMessage:\n \"Event-sourced DB is not initialized. Call ensureDb() before using db.\",\n });\n\n const close = async (): Promise<void> => {\n if (activeDb) {\n activeDb.dispose();\n activeDb = null;\n }\n singleton.reset();\n };\n\n return {\n ensureDb: singleton.ensure,\n db: singleton.proxy,\n close,\n };\n}\n"],"mappings":";;AAiBA,SAAgB,0BACd,MACA,QAC2B;CAC3B,MAAM,cAAc,KAAK,mCAAmC,EAC1D,UAAU,OAAO,SACnB,CAAC;CAED,OAAO;EACL,QAAQ,OAAO;EACf;CACF;AACF;;;
|
|
1
|
+
{"version":3,"file":"react-native.mjs","names":[],"sources":["../src/platforms/react-native.ts","../src/platforms/react-native-event-sourced-db.ts"],"sourcesContent":["import type { PersistedCollectionPersistence, SQLiteDriver } from \"../types\";\n\nexport type ReactNativePlatformDeps = {\n createReactNativeSQLitePersistence: (options: {\n database: SQLiteDriver;\n }) => PersistedCollectionPersistence;\n};\n\nexport type ReactNativePlatformConfig = {\n database: SQLiteDriver;\n};\n\nexport type ReactNativePlatformResult = {\n driver: SQLiteDriver;\n persistence: PersistedCollectionPersistence;\n};\n\nexport function createReactNativePlatform(\n deps: ReactNativePlatformDeps,\n config: ReactNativePlatformConfig,\n): ReactNativePlatformResult {\n const persistence = deps.createReactNativeSQLitePersistence({\n database: config.database,\n });\n\n return {\n driver: config.database,\n persistence,\n };\n}\n","import { createEventSourcedDB } from \"../create-event-sourced-db\";\nimport { createLazySingleton } from \"../lazy-singleton\";\nimport type { CreateCollectionFn, PersistedCollectionOptionsFn } from \"../persisted-collection\";\nimport type {\n EventSourcedDB,\n EventSourcedLogger,\n SQLiteDriver,\n SyncHandlersConfig,\n SyncTransport,\n SyncUrlConfig,\n} from \"../types\";\nimport { createReactNativePlatform } from \"./react-native\";\nimport type { ReactNativePlatformDeps } from \"./react-native\";\n\ntype CollectionDefConstraint = {\n getKey: (state: never) => string | number;\n schemaVersion?: number;\n indexes?: ReadonlyArray<{\n select: (row: never) => unknown;\n name?: string;\n indexType?: import(\"@tanstack/db\").IndexConstructor<string | number>;\n }>;\n};\n\nexport type ReactNativeEventSourcedModules = ReactNativePlatformDeps & {\n database: SQLiteDriver;\n createCollection: CreateCollectionFn;\n persistedCollectionOptions: PersistedCollectionOptionsFn;\n};\n\nexport type ReactNativeEventSourcedDBConfig<TDefs extends Record<string, CollectionDefConstraint>> =\n {\n collections: TDefs;\n load: () => Promise<ReactNativeEventSourcedModules>;\n sync?: SyncHandlersConfig | SyncUrlConfig | SyncTransport;\n syncEnabled?: boolean;\n schemaVersion?: number;\n debug?: boolean | EventSourcedLogger;\n };\n\nexport type ReactNativeEventSourcedDBHandle<\n TDefs extends Record<string, CollectionDefConstraint>,\n> = {\n ensureDb: () => Promise<EventSourcedDB<TDefs>>;\n db: EventSourcedDB<TDefs>;\n close: () => Promise<void>;\n};\n\nexport function createReactNativeEventSourcedDB<\n const TDefs extends Record<string, CollectionDefConstraint>,\n>(config: ReactNativeEventSourcedDBConfig<TDefs>): ReactNativeEventSourcedDBHandle<TDefs> {\n let activeDb: EventSourcedDB<TDefs> | null = null;\n\n const factory = async (): Promise<EventSourcedDB<TDefs>> => {\n const modules = await config.load();\n\n const platform = createReactNativePlatform(\n { createReactNativeSQLitePersistence: modules.createReactNativeSQLitePersistence },\n { database: modules.database },\n );\n\n const database = await createEventSourcedDB<TDefs>({\n persistence: platform.persistence,\n createCollection: modules.createCollection,\n persistedCollectionOptions: modules.persistedCollectionOptions,\n collections: config.collections,\n sync: config.sync,\n syncEnabled: config.syncEnabled,\n schemaVersion: config.schemaVersion,\n debug: config.debug,\n });\n\n activeDb = database;\n return database;\n };\n\n const singleton = createLazySingleton<EventSourcedDB<TDefs>>(factory, {\n notInitializedMessage:\n \"Event-sourced DB is not initialized. Call ensureDb() before using db.\",\n });\n\n const close = async (): Promise<void> => {\n if (activeDb) {\n activeDb.dispose();\n activeDb = null;\n }\n singleton.reset();\n };\n\n return {\n ensureDb: singleton.ensure,\n db: singleton.proxy,\n close,\n };\n}\n"],"mappings":";;AAiBA,SAAgB,0BACd,MACA,QAC2B;CAC3B,MAAM,cAAc,KAAK,mCAAmC,EAC1D,UAAU,OAAO,SACnB,CAAC;CAED,OAAO;EACL,QAAQ,OAAO;EACf;CACF;AACF;;;ACmBA,SAAgB,gCAEd,QAAwF;CACxF,IAAI,WAAyC;CAE7C,MAAM,UAAU,YAA4C;EAC1D,MAAM,UAAU,MAAM,OAAO,KAAK;EAOlC,MAAM,WAAW,MAAM,qBAA4B;GACjD,aANe,0BACf,EAAE,oCAAoC,QAAQ,mCAAmC,GACjF,EAAE,UAAU,QAAQ,SAAS,CAIT,CAAC,CAAC;GACtB,kBAAkB,QAAQ;GAC1B,4BAA4B,QAAQ;GACpC,aAAa,OAAO;GACpB,MAAM,OAAO;GACb,aAAa,OAAO;GACpB,eAAe,OAAO;GACtB,OAAO,OAAO;EAChB,CAAC;EAED,WAAW;EACX,OAAO;CACT;CAEA,MAAM,YAAY,oBAA2C,SAAS,EACpE,uBACE,wEACJ,CAAC;CAED,MAAM,QAAQ,YAA2B;EACvC,IAAI,UAAU;GACZ,SAAS,QAAQ;GACjB,WAAW;EACb;EACA,UAAU,MAAM;CAClB;CAEA,OAAO;EACL,UAAU,UAAU;EACpB,IAAI,UAAU;EACd;CACF;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Collection, CollectionConfig, UtilsRecord } from "@tanstack/db";
|
|
1
|
+
import { Collection, CollectionConfig, IndexConstructor, UtilsRecord } from "@tanstack/db";
|
|
2
2
|
import { PersistedCollectionPersistence as PersistedCollectionPersistence$1, PersistedCollectionUtils, SQLiteDriver, persistedCollectionOptions } from "@tanstack/db-sqlite-persistence-core";
|
|
3
3
|
|
|
4
4
|
//#region src/persisted-collection.d.ts
|
|
@@ -101,9 +101,15 @@ type SyncHandlersConfig = {
|
|
|
101
101
|
pullUrl?: string;
|
|
102
102
|
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
103
103
|
};
|
|
104
|
+
type CollectionIndexDef<TState = object, TKey extends string | number = string> = {
|
|
105
|
+
select: (row: TState) => unknown;
|
|
106
|
+
name?: string;
|
|
107
|
+
indexType?: IndexConstructor<TKey>;
|
|
108
|
+
};
|
|
104
109
|
type CollectionDef<TState = object, TKey extends string | number = string> = {
|
|
105
110
|
getKey: (state: TState) => TKey;
|
|
106
111
|
schemaVersion?: number;
|
|
112
|
+
indexes?: ReadonlyArray<CollectionIndexDef<TState, TKey>>;
|
|
107
113
|
};
|
|
108
114
|
type InferState<T> = T extends {
|
|
109
115
|
getKey: (state: infer S) => string | number;
|
|
@@ -114,6 +120,11 @@ type InferKey<T> = T extends {
|
|
|
114
120
|
type CollectionDefConstraint = {
|
|
115
121
|
getKey: (state: never) => string | number;
|
|
116
122
|
schemaVersion?: number;
|
|
123
|
+
indexes?: ReadonlyArray<{
|
|
124
|
+
select: (row: never) => unknown;
|
|
125
|
+
name?: string;
|
|
126
|
+
indexType?: IndexConstructor<string | number>;
|
|
127
|
+
}>;
|
|
117
128
|
};
|
|
118
129
|
type CollectionMap<TDefs extends Record<string, CollectionDefConstraint>> = { [K in keyof TDefs]: Collection<InferState<TDefs[K]>, InferKey<TDefs[K]>> };
|
|
119
130
|
type ReservedCollections = {
|
|
@@ -124,6 +135,7 @@ type EventSourcedDBConfig<TDefs extends Record<string, CollectionDefConstraint>>
|
|
|
124
135
|
persistence: PersistedCollectionPersistence$1;
|
|
125
136
|
collections: TDefs;
|
|
126
137
|
sync?: SyncHandlersConfig | SyncUrlConfig | SyncTransport;
|
|
138
|
+
syncEnabled?: boolean;
|
|
127
139
|
schemaVersion?: number;
|
|
128
140
|
debug?: boolean | EventSourcedLogger;
|
|
129
141
|
createCollection: CreateCollectionFn;
|
|
@@ -133,6 +145,8 @@ type EventSourcedDB<TDefs extends Record<string, CollectionDefConstraint>> = {
|
|
|
133
145
|
collections: CollectionMap<TDefs> & ReservedCollections;
|
|
134
146
|
sync: () => Promise<SyncResult>;
|
|
135
147
|
manualSync: () => Promise<ManualSyncResult>;
|
|
148
|
+
getSyncEnabled: () => boolean;
|
|
149
|
+
setSyncEnabled: (enabled: boolean) => void;
|
|
136
150
|
dispose: () => void;
|
|
137
151
|
};
|
|
138
152
|
type SyncResult = {
|
|
@@ -144,5 +158,5 @@ type ManualSyncResult = SyncResult & {
|
|
|
144
158
|
replayed: number;
|
|
145
159
|
};
|
|
146
160
|
//#endregion
|
|
147
|
-
export {
|
|
148
|
-
//# sourceMappingURL=types-
|
|
161
|
+
export { CreateCollectionFn as A, ServerEvent as C, SyncUrlConfig as D, SyncTransport as E, EventSourcedLogger as O, SQLiteDriver as S, SyncResult as T, PushConfirmation as _, EventSourcedDBConfig as a, PushResponse as b, InferState as c, OutboundEvent as d, OutboxEntry as f, PullResponse as g, PullEventsFn as h, EventSourcedDB as i, PersistedCollectionOptionsFn as j, createEventSourcedLogger as k, ManualSyncResult as l, PersistedCollectionPersistence$1 as m, CollectionIndexDef as n, InboxEntry as o, OutboxSyncStatus as p, CollectionMap as r, InferKey as s, CollectionDef as t, MutationType as u, PushEventsFn as v, SyncHandlersConfig as w, ReservedCollections as x, PushFailure as y };
|
|
162
|
+
//# sourceMappingURL=types-BX8dtGOu.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-
|
|
1
|
+
{"version":3,"file":"types-BX8dtGOu.d.mts","names":[],"sources":["../src/persisted-collection.ts","../src/utils/logger.ts","../src/types.ts"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-singleton-DFrAsYTV.mjs","names":[],"sources":["../src/sync.ts","../src/utils/logger.ts","../src/create-event-sourced-db.ts","../src/lazy-singleton.ts"],"sourcesContent":["import type {\n OutboundEvent,\n PullEventsFn,\n PullResponse,\n PushConfirmation,\n PushEventsFn,\n PushResponse,\n SyncHandlersConfig,\n SyncTransport,\n SyncUrlConfig,\n} from \"./types\";\n\nexport type NormalizedSyncTransport = {\n push?: PushEventsFn;\n pull?: (since: number) => Promise<PullResponse>;\n};\n\ntype HeaderConfig = SyncHandlersConfig[\"headers\"];\n\nexport function createHttpTransport(config: SyncUrlConfig): SyncTransport {\n return {\n push: createHttpPushEvents(config.push, config.headers),\n pull: createHttpPullEvents(config.pull, config.headers),\n };\n}\n\nexport function createSyncTransport(\n config?: SyncHandlersConfig | SyncUrlConfig | SyncTransport,\n): NormalizedSyncTransport | null {\n if (!config) {\n return null;\n }\n\n if (isTransport(config)) {\n return {\n push: config.push,\n pull: config.pull,\n };\n }\n\n const pushUrl = getPushUrl(config);\n const pullUrl = getPullUrl(config);\n\n const push = \"pushEvents\" in config && config.pushEvents\n ? config.pushEvents\n : pushUrl\n ? createHttpPushEvents(pushUrl, config.headers)\n : undefined;\n\n const pullEvents = \"pullEvents\" in config ? config.pullEvents : undefined;\n const pull = pullEvents\n ? createPullFromHandler(pullEvents)\n : pullUrl\n ? createHttpPullEvents(pullUrl, config.headers)\n : undefined;\n\n if (!push && !pull) {\n return null;\n }\n\n return { push, pull };\n}\n\nexport function normalizePushResponse(\n response: PushResponse | ReadonlyArray<PushConfirmation>,\n): PushResponse {\n if (isConfirmationArray(response)) {\n return { confirmed: response };\n }\n\n return {\n confirmed: response.confirmed,\n failed: response.failed,\n };\n}\n\nfunction isConfirmationArray(\n response: PushResponse | ReadonlyArray<PushConfirmation>,\n): response is ReadonlyArray<PushConfirmation> {\n return Array.isArray(response);\n}\n\nfunction createHttpPushEvents(url: string, headers: HeaderConfig): PushEventsFn {\n return async (events: ReadonlyArray<OutboundEvent>): Promise<PushResponse> => {\n if (events.length === 0) return { confirmed: [] };\n\n const resolvedHeaders = await resolveHeaders(headers);\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...resolvedHeaders },\n body: JSON.stringify(events),\n });\n\n if (!response.ok) {\n throw new SyncPushError(response.status, await response.text());\n }\n\n return response.json() as Promise<PushResponse>;\n };\n}\n\nfunction createPullFromHandler(pullEvents: PullEventsFn): (since: number) => Promise<PullResponse> {\n return (since: number) => pullEvents({ since });\n}\n\nfunction createHttpPullEvents(\n url: string,\n headers: HeaderConfig,\n): (since: number) => Promise<PullResponse> {\n return async (since: number): Promise<PullResponse> => {\n const resolvedHeaders = await resolveHeaders(headers);\n const pullUrl = appendSince(url, since);\n\n const response = await fetch(pullUrl, {\n headers: { Accept: \"application/json\", ...resolvedHeaders },\n });\n\n if (!response.ok) {\n throw new SyncPullError(response.status, await response.text());\n }\n\n return response.json() as Promise<PullResponse>;\n };\n}\n\nasync function resolveHeaders(headers: HeaderConfig): Promise<Record<string, string>> {\n if (!headers) return {};\n if (typeof headers === \"function\") return headers();\n return headers;\n}\n\nfunction appendSince(url: string, since: number): string {\n const separator = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${separator}since=${encodeURIComponent(String(since))}`;\n}\n\nfunction getPushUrl(config: SyncHandlersConfig | SyncUrlConfig): string | undefined {\n if (\"pushUrl\" in config && config.pushUrl) {\n return config.pushUrl;\n }\n\n if (\"push\" in config && typeof config.push === \"string\") {\n return config.push;\n }\n\n return undefined;\n}\n\nfunction getPullUrl(config: SyncHandlersConfig | SyncUrlConfig): string | undefined {\n if (\"pullUrl\" in config && config.pullUrl) {\n return config.pullUrl;\n }\n\n if (\"pull\" in config && typeof config.pull === \"string\") {\n return config.pull;\n }\n\n return undefined;\n}\n\nexport class SyncPushError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: string,\n ) {\n super(`Event push failed: HTTP ${status}`);\n this.name = \"SyncPushError\";\n }\n}\n\nexport class SyncPullError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: string,\n ) {\n super(`Event pull failed: HTTP ${status}`);\n this.name = \"SyncPullError\";\n }\n}\n\nexport function isTransport(\n value: SyncHandlersConfig | SyncUrlConfig | SyncTransport,\n): value is SyncTransport {\n return \"push\" in value && typeof value.push === \"function\";\n}\n","export type EventSourcedLogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport type EventSourcedLogger = {\n debug: (message: string, data?: Record<string, unknown>) => void;\n info: (message: string, data?: Record<string, unknown>) => void;\n warn: (message: string, data?: Record<string, unknown>) => void;\n error: (message: string, data?: Record<string, unknown>) => void;\n};\n\nconst noopLogger: EventSourcedLogger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\nconst LOG_PREFIX = \"[event-sourced]\";\n\nexport function createEventSourcedLogger(\n debug?: boolean | EventSourcedLogger,\n): EventSourcedLogger {\n if (debug === undefined || debug === false) {\n return noopLogger;\n }\n\n if (typeof debug === \"object\") {\n return debug;\n }\n\n return {\n debug: (message, data) => {\n if (data === undefined) {\n console.debug(LOG_PREFIX, message);\n return;\n }\n console.debug(LOG_PREFIX, message, data);\n },\n info: (message, data) => {\n if (data === undefined) {\n console.info(LOG_PREFIX, message);\n return;\n }\n console.info(LOG_PREFIX, message, data);\n },\n warn: (message, data) => {\n if (data === undefined) {\n console.warn(LOG_PREFIX, message);\n return;\n }\n console.warn(LOG_PREFIX, message, data);\n },\n error: (message, data) => {\n if (data === undefined) {\n console.error(LOG_PREFIX, message);\n return;\n }\n console.error(LOG_PREFIX, message, data);\n },\n };\n}\n","import type { Collection } from \"@tanstack/db\";\nimport { createSyncTransport, normalizePushResponse } from \"./sync\";\nimport type { NormalizedSyncTransport } from \"./sync\";\nimport type { EventSourcedLogger } from \"./utils/logger\";\nimport { createEventSourcedLogger } from \"./utils/logger\";\nimport { generateEventId } from \"./utils/uuid\";\nimport type {\n CollectionMap,\n EventSourcedDB,\n EventSourcedDBConfig,\n InboxEntry,\n MutationType,\n OutboundEvent,\n OutboxEntry,\n ServerEvent,\n SyncResult,\n ManualSyncResult,\n} from \"./types\";\n\nconst OUTBOX_ID = \"outbox\";\nconst INBOX_ID = \"inbox\";\nconst RESERVED_IDS = new Set<string>([OUTBOX_ID, INBOX_ID]);\n\ntype CollectionDefConstraint = {\n getKey: (state: never) => string | number;\n schemaVersion?: number;\n};\n\ntype ReplayMutation = {\n mutationId: string;\n type: MutationType;\n key: string | number;\n modified: Record<string, unknown>;\n original: Record<string, unknown>;\n changes: Record<string, unknown>;\n collection: AcceptMutationsCollection;\n};\n\ntype AcceptMutationsCollection = {\n id?: string;\n utils: {\n acceptMutations?: (transaction: { mutations: Array<ReplayMutation> }) => Promise<void> | void;\n };\n};\n\ntype MutationHookParams = {\n transaction: {\n mutations: ReadonlyArray<{\n mutationId: string;\n key: string | number;\n modified: Record<string, unknown>;\n original: Record<string, unknown>;\n }>;\n };\n};\n\ntype SeqCounter = { value: number };\n\ntype MetaCollectionFactory = Pick<\n EventSourcedDBConfig<Record<string, CollectionDefConstraint>>,\n \"createCollection\" | \"persistedCollectionOptions\" | \"persistence\"\n>;\n\nexport async function createEventSourcedDB<\n const TDefs extends Record<string, CollectionDefConstraint>,\n>(config: EventSourcedDBConfig<TDefs>): Promise<EventSourcedDB<TDefs>> {\n assertReservedNamesAvailable(config.collections);\n\n const log = createEventSourcedLogger(config.debug);\n\n const transport = createSyncTransport(config.sync);\n\n log.info(\"creating event-sourced db\", {\n collectionIds: Object.keys(config.collections),\n hasTransport: transport !== null,\n });\n\n const defaultSchemaVersion = config.schemaVersion ?? 1;\n const seq: SeqCounter = { value: 0 };\n\n const outbox = createMetaCollection<OutboxEntry>(\n config,\n OUTBOX_ID,\n (entry) => entry.eventId,\n defaultSchemaVersion,\n );\n\n const inbox = createMetaCollection<InboxEntry>(\n config,\n INBOX_ID,\n (entry) => entry.eventId,\n defaultSchemaVersion,\n );\n\n const userCollections = {} as CollectionMap<TDefs>;\n\n for (const collectionId of Object.keys(config.collections)) {\n const def = config.collections[collectionId]!;\n const getKey = def.getKey as (item: Record<string, unknown>) => string | number;\n\n const options = config.persistedCollectionOptions<Record<string, unknown>, string | number>({\n id: collectionId,\n getKey,\n persistence: config.persistence,\n schemaVersion: def.schemaVersion ?? defaultSchemaVersion,\n onInsert: createMutationHook(outbox, collectionId, \"insert\", seq, log),\n onUpdate: createMutationHook(outbox, collectionId, \"update\", seq, log),\n onDelete: createMutationHook(outbox, collectionId, \"delete\", seq, log),\n });\n\n const collection = config.createCollection(options);\n const hasAcceptMutations = Boolean(\n (collection as AcceptMutationsCollection).utils?.acceptMutations,\n );\n\n log.debug(\"registered collection\", {\n collectionId,\n hasAcceptMutations,\n });\n\n (userCollections as Record<string, unknown>)[collectionId] = collection;\n }\n\n const collections = {\n ...(userCollections as CollectionMap<TDefs>),\n outbox,\n inbox,\n } as EventSourcedDB<TDefs>[\"collections\"];\n\n const replayTargets = collections as unknown as Record<string, AcceptMutationsCollection>;\n\n const subscriptions = [\n outbox.subscribeChanges(() => {}),\n inbox.subscribeChanges(() => {}),\n ];\n\n await outbox.preload();\n await inbox.preload();\n\n seq.value = nextLocalSeq(outbox);\n\n log.info(\"preloaded meta collections\", {\n outboxCount: outbox.state.size,\n inboxCount: inbox.state.size,\n nextLocalSeq: seq.value,\n });\n\n await replayInbox(inbox, replayTargets, log);\n\n async function sync(): Promise<SyncResult> {\n if (!transport) {\n log.warn(\"sync skipped: no transport configured\");\n return { pushed: 0, pulled: 0, errors: [] };\n }\n\n log.info(\"sync started\");\n\n await outbox.preload();\n await inbox.preload();\n\n const errors: Error[] = [];\n let pushed = 0;\n let pulled = 0;\n\n try {\n if (transport.push) {\n pushed = await pushOutbox(outbox, transport.push, log);\n } else {\n log.debug(\"push skipped: no push transport configured\");\n }\n } catch (err) {\n const error = toError(err);\n log.error(\"push outbox failed\", { message: error.message });\n errors.push(error);\n }\n\n try {\n if (transport.pull) {\n pulled = await pullInbox(outbox, inbox, transport.pull, replayTargets, log);\n } else {\n log.debug(\"pull skipped: no pull transport configured\");\n }\n } catch (err) {\n const error = toError(err);\n log.error(\"pull inbox failed\", { message: error.message });\n errors.push(error);\n }\n\n log.info(\"sync finished\", { pushed, pulled, errorCount: errors.length });\n\n return { pushed, pulled, errors };\n }\n\n async function manualSync(): Promise<ManualSyncResult> {\n log.info(\"manual sync started\");\n\n await outbox.preload();\n await inbox.preload();\n\n const errors: Error[] = [];\n let pushed = 0;\n let pulled = 0;\n let replayed = 0;\n\n if (transport) {\n try {\n if (transport.push) {\n pushed = await pushOutbox(outbox, transport.push, log);\n } else {\n log.debug(\"manual sync push skipped: no push transport configured\");\n }\n } catch (err) {\n const error = toError(err);\n log.error(\"manual sync push failed\", { message: error.message });\n errors.push(error);\n }\n\n try {\n if (transport.pull) {\n pulled = await pullInbox(outbox, inbox, transport.pull, replayTargets, log);\n } else {\n log.debug(\"manual sync pull skipped: no pull transport configured\");\n }\n } catch (err) {\n const error = toError(err);\n log.error(\"manual sync pull failed\", { message: error.message });\n errors.push(error);\n }\n } else {\n log.warn(\"manual sync: no transport configured, skipping push/pull\");\n }\n\n try {\n replayed = await replayInbox(inbox, replayTargets, log);\n } catch (err) {\n const error = toError(err);\n log.error(\"manual sync replay failed\", { message: error.message });\n errors.push(error);\n }\n\n log.info(\"manual sync finished\", { pushed, pulled, replayed, errorCount: errors.length });\n\n return { pushed, pulled, replayed, errors };\n }\n\n function dispose(): void {\n log.debug(\"disposing event-sourced db\");\n for (const subscription of subscriptions) {\n subscription.unsubscribe();\n }\n }\n\n return { collections, sync, manualSync, dispose };\n}\n\nfunction createMetaCollection<TEntry extends object>(\n config: MetaCollectionFactory,\n id: string,\n getKey: (entry: TEntry) => string,\n schemaVersion: number,\n): Collection<TEntry, string> {\n const options = config.persistedCollectionOptions<TEntry, string>({\n id,\n getKey,\n persistence: config.persistence,\n schemaVersion,\n });\n\n return config.createCollection(options);\n}\n\nfunction createMutationHook(\n outbox: Collection<OutboxEntry, string>,\n collectionId: string,\n type: MutationType,\n seq: SeqCounter,\n log: EventSourcedLogger,\n) {\n return async (params: MutationHookParams): Promise<Record<string, unknown>> => {\n for (const mutation of params.transaction.mutations) {\n const payload = type === \"delete\" ? mutation.original : mutation.modified;\n\n const entry: OutboxEntry = {\n eventId: generateEventId(),\n collectionId,\n type,\n key: mutation.key,\n payload,\n timestamp: Date.now(),\n localSeq: seq.value++,\n globalSeq: null,\n sync: false,\n syncStatus: \"pending\",\n attemptCount: 0,\n lastAttemptAt: null,\n lastError: null,\n lastErrorCode: null,\n retryable: null,\n };\n\n await outbox.insert(entry).isPersisted.promise;\n\n log.debug(\"outbox entry created\", {\n eventId: entry.eventId,\n collectionId,\n type,\n key: entry.key,\n localSeq: entry.localSeq,\n });\n }\n\n return {};\n };\n}\n\nasync function pushOutbox(\n outbox: Collection<OutboxEntry, string>,\n push: NonNullable<NormalizedSyncTransport[\"push\"]>,\n log: EventSourcedLogger,\n): Promise<number> {\n const pending = [...outbox.state.values()]\n .filter((entry) => !entry.sync && entry.syncStatus !== \"failed\")\n .sort((a, b) => a.localSeq - b.localSeq);\n\n log.debug(\"push outbox\", { pendingCount: pending.length });\n\n if (pending.length === 0) return 0;\n\n const attemptAt = Date.now();\n\n for (const entry of pending) {\n await outbox\n .update(entry.eventId, (draft) => {\n draft.syncStatus = \"pending\";\n draft.attemptCount = (draft.attemptCount ?? 0) + 1;\n draft.lastAttemptAt = attemptAt;\n draft.lastError = null;\n draft.lastErrorCode = null;\n draft.retryable = null;\n })\n .isPersisted.promise;\n }\n\n const outbound: OutboundEvent[] = pending.map((entry) => ({\n eventId: entry.eventId,\n collectionId: entry.collectionId,\n type: entry.type,\n key: entry.key,\n payload: entry.payload,\n timestamp: entry.timestamp,\n }));\n\n const response = normalizePushResponse(await push(outbound));\n\n log.info(\"push outbox confirmed\", {\n sent: outbound.length,\n confirmed: response.confirmed.length,\n failed: response.failed?.length ?? 0,\n });\n\n for (const confirmation of response.confirmed) {\n await outbox\n .update(confirmation.eventId, (draft) => {\n draft.sync = true;\n draft.syncStatus = \"synced\";\n draft.globalSeq = confirmation.globalSeq;\n draft.lastError = null;\n draft.lastErrorCode = null;\n draft.retryable = null;\n })\n .isPersisted.promise;\n\n log.debug(\"outbox entry marked pushed\", {\n eventId: confirmation.eventId,\n globalSeq: confirmation.globalSeq,\n });\n }\n\n for (const failure of response.failed ?? []) {\n await outbox\n .update(failure.eventId, (draft) => {\n draft.sync = false;\n draft.syncStatus = \"failed\";\n draft.lastError = failure.message;\n draft.lastErrorCode = failure.code ?? null;\n draft.retryable = failure.retryable ?? null;\n })\n .isPersisted.promise;\n\n log.warn(\"outbox entry marked failed\", {\n eventId: failure.eventId,\n message: failure.message,\n code: failure.code,\n retryable: failure.retryable,\n });\n }\n\n return response.confirmed.length;\n}\n\nasync function pullInbox(\n outbox: Collection<OutboxEntry, string>,\n inbox: Collection<InboxEntry, string>,\n pull: NonNullable<NormalizedSyncTransport[\"pull\"]>,\n targets: Record<string, AcceptMutationsCollection>,\n log: EventSourcedLogger,\n): Promise<number> {\n let pulled = 0;\n let hasMore = true;\n\n while (hasMore) {\n const since = currentSince(inbox);\n log.debug(\"pull inbox page\", { since });\n\n const response = await pull(since);\n\n log.debug(\"pull inbox response\", {\n since,\n eventCount: response.events.length,\n hasMore: response.hasMore,\n cursor: response.cursor,\n });\n\n if (response.events.length === 0) break;\n\n const sorted = [...response.events].sort((a, b) => a.globalSeq - b.globalSeq);\n\n for (const event of sorted) {\n if (outbox.has(event.eventId)) {\n await markInboxEventSynced(inbox, event);\n\n log.debug(\"pull skipped: event originated locally\", {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n });\n continue;\n }\n\n const existing = inbox.get(event.eventId);\n if (existing?.sync) {\n log.debug(\"pull skipped: inbox already applied\", {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n });\n continue;\n }\n\n if (!existing) {\n await inbox.insert(toInboxEntry(event, false)).isPersisted.promise;\n log.debug(\"inbox entry inserted\", {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n collectionId: event.collectionId,\n });\n }\n\n const applied = await replayEvent(\n targets,\n event.collectionId,\n event.eventId,\n event.type,\n event.key,\n event.payload,\n log,\n );\n\n if (!applied) {\n return pulled;\n }\n\n await inbox\n .update(event.eventId, (draft) => {\n draft.sync = true;\n })\n .isPersisted.promise;\n\n log.info(\"pull replay applied\", {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n collectionId: event.collectionId,\n type: event.type,\n key: event.key,\n });\n\n pulled++;\n }\n\n hasMore = response.hasMore;\n }\n\n log.info(\"pull inbox finished\", { pulled });\n\n return pulled;\n}\n\nasync function replayInbox(\n inbox: Collection<InboxEntry, string>,\n targets: Record<string, AcceptMutationsCollection>,\n log: EventSourcedLogger,\n): Promise<number> {\n const pending = [...inbox.state.values()]\n .filter((entry) => !entry.sync)\n .sort((a, b) => a.globalSeq - b.globalSeq);\n\n log.info(\"pending inbox replay started\", { pendingCount: pending.length });\n\n let replayed = 0;\n\n for (const entry of pending) {\n const applied = await replayEvent(\n targets,\n entry.collectionId,\n entry.eventId,\n entry.type,\n entry.key,\n entry.payload,\n log,\n );\n\n if (!applied) {\n break;\n }\n\n await inbox\n .update(entry.eventId, (draft) => {\n draft.sync = true;\n })\n .isPersisted.promise;\n\n replayed++;\n\n log.info(\"pending inbox replay applied\", {\n eventId: entry.eventId,\n globalSeq: entry.globalSeq,\n collectionId: entry.collectionId,\n });\n }\n\n log.info(\"pending inbox replay finished\", { replayed });\n\n return replayed;\n}\n\nasync function replayEvent(\n targets: Record<string, AcceptMutationsCollection>,\n collectionId: string,\n eventId: string,\n type: MutationType,\n key: string | number,\n payload: Record<string, unknown>,\n log: EventSourcedLogger,\n): Promise<boolean> {\n if (RESERVED_IDS.has(collectionId)) {\n log.warn(\"replay skipped: reserved collection\", { eventId, collectionId, type, key });\n return false;\n }\n\n const target = targets[collectionId];\n if (!target) {\n log.warn(\"replay skipped: unknown collection\", {\n eventId,\n collectionId,\n type,\n key,\n knownCollections: Object.keys(targets).filter((id) => !RESERVED_IDS.has(id)),\n });\n return false;\n }\n\n if (!target.utils.acceptMutations) {\n log.warn(\"replay skipped: collection missing acceptMutations\", {\n eventId,\n collectionId,\n type,\n key,\n targetId: target.id,\n });\n return false;\n }\n\n log.debug(\"replay applying mutation\", { eventId, collectionId, type, key });\n\n try {\n await target.utils.acceptMutations({\n mutations: [\n {\n mutationId: eventId,\n type,\n key,\n modified: payload,\n original: payload,\n changes: payload,\n collection: target,\n },\n ],\n });\n\n log.info(\"replay mutation accepted\", { eventId, collectionId, type, key });\n } catch (err) {\n const error = toError(err);\n log.error(\"replay mutation failed\", {\n eventId,\n collectionId,\n type,\n key,\n message: error.message,\n });\n throw error;\n }\n\n return true;\n}\n\nfunction currentSince(inbox: Collection<InboxEntry, string>): number {\n let max = 0;\n\n for (const entry of inbox.state.values()) {\n if (entry.sync && entry.globalSeq > max) max = entry.globalSeq;\n }\n\n return max;\n}\n\nfunction nextLocalSeq(outbox: Collection<OutboxEntry, string>): number {\n let max = -1;\n\n for (const entry of outbox.state.values()) {\n if (entry.localSeq > max) max = entry.localSeq;\n }\n\n return max + 1;\n}\n\nasync function markInboxEventSynced(\n inbox: Collection<InboxEntry, string>,\n event: ServerEvent,\n): Promise<void> {\n const existing = inbox.get(event.eventId);\n\n if (!existing) {\n await inbox.insert(toInboxEntry(event, true)).isPersisted.promise;\n return;\n }\n\n if (!existing.sync || existing.globalSeq !== event.globalSeq) {\n await inbox\n .update(event.eventId, (draft) => {\n draft.globalSeq = event.globalSeq;\n draft.sync = true;\n })\n .isPersisted.promise;\n }\n}\n\nfunction toInboxEntry(event: ServerEvent, sync: boolean): InboxEntry {\n return {\n eventId: event.eventId,\n globalSeq: event.globalSeq,\n collectionId: event.collectionId,\n type: event.type,\n key: event.key,\n payload: event.payload,\n timestamp: event.timestamp,\n sync,\n };\n}\n\nfunction assertReservedNamesAvailable(collections: Record<string, unknown>): void {\n for (const id of Object.keys(collections)) {\n if (RESERVED_IDS.has(id)) {\n throw new Error(\n `Collection id \"${id}\" is reserved. \"outbox\" and \"inbox\" are built-in collections.`,\n );\n }\n }\n}\n\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n","export type LazySingletonOptions = {\n guard?: () => void;\n notInitializedMessage?: string;\n};\n\nexport type LazySingleton<T extends object> = {\n ensure: () => Promise<T>;\n proxy: T;\n reset: () => void;\n};\n\nexport function createLazySingleton<T extends object>(\n factory: () => Promise<T>,\n options: LazySingletonOptions = {},\n): LazySingleton<T> {\n let instance: T | null = null;\n let initPromise: Promise<T> | null = null;\n\n const ensure = async (): Promise<T> => {\n options.guard?.();\n\n if (instance) {\n return instance;\n }\n\n if (!initPromise) {\n initPromise = factory()\n .then((resolved) => {\n instance = resolved;\n return resolved;\n })\n .catch((error: unknown) => {\n initPromise = null;\n throw error;\n });\n }\n\n return initPromise;\n };\n\n const message =\n options.notInitializedMessage ?? \"Instance is not initialized. Call ensure() first.\";\n\n const proxy = new Proxy({} as T, {\n get(_target, prop, receiver) {\n if (!instance) {\n throw new Error(message);\n }\n return Reflect.get(instance, prop, receiver);\n },\n has(_target, prop) {\n if (!instance) {\n throw new Error(message);\n }\n return Reflect.has(instance, prop);\n },\n getPrototypeOf() {\n if (!instance) {\n throw new Error(message);\n }\n return Reflect.getPrototypeOf(instance);\n },\n });\n\n const reset = (): void => {\n instance = null;\n initPromise = null;\n };\n\n return { ensure, proxy, reset };\n}\n"],"mappings":"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"}
|