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 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 needs four pieces. Split them by concern so setup stays obvious:
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
- ├── data-access-layer/
30
- ├── collections.ts # types, DB init, sync transport, `ensureDb()` + `db`
31
- └── sync-events.ts # thin wrappers around `db.sync()` / `db.manualSync()`
32
- ├── hooks/common/
33
- │ └── use-event-sourced-sync.ts # periodic background sync (optional)
34
- └── routes/ or app shell
35
- └── layout.tsx # call `ensureDb()` once before rendering data UI
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` | Single source of truth: row types, collection registry, push/pull handlers, one `createBrowserEventSourcedDB` call exporting `ensureDb` + `db` |
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 the DB is ready |
43
- | App shell | `await ensureDb()` before routes that read/write collections |
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 → ensureDb() → load() + platform + engine (run once, deduped)
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
- db.sync() or useEventSourcedSync()
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: { getKey: (user: User) => string };
98
- todos: { getKey: (todo: Todo) => string };
99
- settings: { getKey: (settings: AppSettings) => string };
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 (one call)
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 gives you a lazy singleton (`ensureDb`) plus a `db` proxy you can import anywhere. **Collections are created here** — there is no separate `createUsersCollection()` call.
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 the platform packages. Keeping the imports in *your* app (instead of inside the library) means the package never bundles a platform or a framework: you choose `@tanstack/react-db` vs `@tanstack/db`, and SSR bundles stay clean because `load` is dynamically imported on first `ensureDb()`.
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 { ensureDb, db, close } = createBrowserEventSourcedDB<AppCollectionDefs>({
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: { getKey: (user: User) => user.id },
153
- todos: { getKey: (todo: Todo) => todo.id },
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 platform = await import("@tanstack/browser-db-sqlite-persistence");
160
- return { ...platform, createCollection };
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, close };
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 across concurrent calls), returns the ready DB. Throws outside a browser environment.
170
- - `db` — a proxy that forwards to the instance after `ensureDb()` resolves, and throws if used before then.
171
- - `close()` — disposes the engine and closes the SQLite platform; the next `ensureDb()` re-initializes.
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
- - `db.collections.users`
176
- - `db.collections.todos`
177
- - `db.collections.settings`
279
+ #### Collection indexes
178
280
 
179
- Plus two built-in collections (always present, even without sync):
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
- - `db.collections.outbox` — local mutations waiting to be pushed
182
- - `db.collections.inbox` — server events pulled to this device
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
- Both are normal queryable collections for sync UI (see [Inspecting sync state](#inspecting-sync-state)). `outbox` and `inbox` are reserved ids.
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
- ### 5. Wire the app shell
312
+ favorites: {
313
+ getKey: (item: SavedMovieRef) => item.movieId,
314
+ indexes: [{ select: (item) => item.movieId, indexType: BasicIndex, name: "by-movie-id" }],
315
+ },
187
316
 
188
- Call `ensureDb()` once when the app mounts, before any collection reads:
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 { ensureDb } from "./data-access-layer/collections";
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 [ready, setReady] = useState(false);
432
+ const [dbReady, setDbReady] = useState(false);
196
433
 
197
434
  useEffect(() => {
198
- void ensureDb().then(() => setReady(true));
435
+ void ensureAppSettings().then(() => setDbReady(true));
199
436
  }, []);
200
437
 
201
- if (!ready) return <Loading />;
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 background sync (`sync-events.ts` + hook):
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
- ### 6. Write data
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
- ### 7. Read data
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
- ### 8. Sync
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 5).
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
 
@@ -1,4 +1,4 @@
1
- import { A as PersistedCollectionOptionsFn, C as SyncHandlersConfig, D as EventSourcedLogger, E as SyncUrlConfig, T as SyncTransport, k as CreateCollectionFn, r as EventSourcedDB, x as SQLiteDriver } from "./types-CAT14Tbj.mjs";
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
  };
@@ -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,KAGU,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,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"}
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-DFrAsYTV.mjs";
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
  });
@@ -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 PersistedCollectionOptionsFn, C as SyncHandlersConfig, D as EventSourcedLogger, E as SyncUrlConfig, O as createEventSourcedLogger, S as ServerEvent, T as SyncTransport, _ as PushEventsFn, a as InboxEntry, b as ReservedCollections, c as ManualSyncResult, d as OutboxEntry, f as OutboxSyncStatus, g as PushConfirmation, h as PullResponse, i as EventSourcedDBConfig, k as CreateCollectionFn, l as MutationType, m as PullEventsFn, n as CollectionMap, o as InferKey, p as PersistedCollectionPersistence, r as EventSourcedDB, s as InferState, t as CollectionDef, u as OutboundEvent, v as PushFailure, w as SyncResult, x as SQLiteDriver, y as PushResponse } from "./types-CAT14Tbj.mjs";
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
@@ -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":";;;;KAuBK,uBAAA;EACH,MAAA,GAAS,KAAA;EACT,aAAA;AAAA;AAAA,iBAsCoB,oBAAA,qBACA,MAAA,SAAe,uBAAA,GACnC,MAAA,EAAQ,oBAAA,CAAqB,KAAA,IAAS,OAAA,CAAQ,cAAA,CAAe,KAAA;;;KCjEnD,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"}
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-DFrAsYTV.mjs";
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.warn("manual sync: no transport configured, skipping push/pull");
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-DFrAsYTV.mjs.map
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":";;AAmBA,SAAgB,oBAAoB,QAAsC;CACxE,OAAO;EACL,MAAM,qBAAqB,OAAO,MAAM,OAAO,OAAO;EACtD,MAAM,qBAAqB,OAAO,MAAM,OAAO,OAAO;CACxD;AACF;AAEA,SAAgB,oBACd,QACgC;CAChC,IAAI,CAAC,QACH,OAAO;CAGT,IAAI,YAAY,MAAM,GACpB,OAAO;EACL,MAAM,OAAO;EACb,MAAM,OAAO;CACf;CAGF,MAAM,UAAU,WAAW,MAAM;CACjC,MAAM,UAAU,WAAW,MAAM;CAEjC,MAAM,OAAO,gBAAgB,UAAU,OAAO,aAC1C,OAAO,aACP,UACE,qBAAqB,SAAS,OAAO,OAAO,IAC5C,KAAA;CAEN,MAAM,aAAa,gBAAgB,SAAS,OAAO,aAAa,KAAA;CAChE,MAAM,OAAO,aACT,sBAAsB,UAAU,IAChC,UACE,qBAAqB,SAAS,OAAO,OAAO,IAC5C,KAAA;CAEN,IAAI,CAAC,QAAQ,CAAC,MACZ,OAAO;CAGT,OAAO;EAAE;EAAM;CAAK;AACtB;AAEA,SAAgB,sBACd,UACc;CACd,IAAI,oBAAoB,QAAQ,GAC9B,OAAO,EAAE,WAAW,SAAS;CAG/B,OAAO;EACL,WAAW,SAAS;EACpB,QAAQ,SAAS;CACnB;AACF;AAEA,SAAS,oBACP,UAC6C;CAC7C,OAAO,MAAM,QAAQ,QAAQ;AAC/B;AAEA,SAAS,qBAAqB,KAAa,SAAqC;CAC9E,OAAO,OAAO,WAAgE;EAC5E,IAAI,OAAO,WAAW,GAAG,OAAO,EAAE,WAAW,CAAC,EAAE;EAEhD,MAAM,kBAAkB,MAAM,eAAe,OAAO;EAEpD,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS;IAAE,gBAAgB;IAAoB,GAAG;GAAgB;GAClE,MAAM,KAAK,UAAU,MAAM;EAC7B,CAAC;EAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,cAAc,SAAS,QAAQ,MAAM,SAAS,KAAK,CAAC;EAGhE,OAAO,SAAS,KAAK;CACvB;AACF;AAEA,SAAS,sBAAsB,YAAoE;CACjG,QAAQ,UAAkB,WAAW,EAAE,MAAM,CAAC;AAChD;AAEA,SAAS,qBACP,KACA,SAC0C;CAC1C,OAAO,OAAO,UAAyC;EACrD,MAAM,kBAAkB,MAAM,eAAe,OAAO;EACpD,MAAM,UAAU,YAAY,KAAK,KAAK;EAEtC,MAAM,WAAW,MAAM,MAAM,SAAS,EACpC,SAAS;GAAE,QAAQ;GAAoB,GAAG;EAAgB,EAC5D,CAAC;EAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,cAAc,SAAS,QAAQ,MAAM,SAAS,KAAK,CAAC;EAGhE,OAAO,SAAS,KAAK;CACvB;AACF;AAEA,eAAe,eAAe,SAAwD;CACpF,IAAI,CAAC,SAAS,OAAO,CAAC;CACtB,IAAI,OAAO,YAAY,YAAY,OAAO,QAAQ;CAClD,OAAO;AACT;AAEA,SAAS,YAAY,KAAa,OAAuB;CAEvD,OAAO,GAAG,MADQ,IAAI,SAAS,GAAG,IAAI,MAAM,IAClB,QAAQ,mBAAmB,OAAO,KAAK,CAAC;AACpE;AAEA,SAAS,WAAW,QAAgE;CAClF,IAAI,aAAa,UAAU,OAAO,SAChC,OAAO,OAAO;CAGhB,IAAI,UAAU,UAAU,OAAO,OAAO,SAAS,UAC7C,OAAO,OAAO;AAIlB;AAEA,SAAS,WAAW,QAAgE;CAClF,IAAI,aAAa,UAAU,OAAO,SAChC,OAAO,OAAO;CAGhB,IAAI,UAAU,UAAU,OAAO,OAAO,SAAS,UAC7C,OAAO,OAAO;AAIlB;AAEA,IAAa,gBAAb,cAAmC,MAAM;CAErB;CACA;CAFlB,YACE,QACA,MACA;EACA,MAAM,2BAA2B,QAAQ;EAHzB,KAAA,SAAA;EACA,KAAA,OAAA;EAGhB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,gBAAb,cAAmC,MAAM;CAErB;CACA;CAFlB,YACE,QACA,MACA;EACA,MAAM,2BAA2B,QAAQ;EAHzB,KAAA,SAAA;EACA,KAAA,OAAA;EAGhB,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,YACd,OACwB;CACxB,OAAO,UAAU,SAAS,OAAO,MAAM,SAAS;AAClD;;;AChLA,MAAM,aAAiC;CACrC,aAAa,CAAC;CACd,YAAY,CAAC;CACb,YAAY,CAAC;CACb,aAAa,CAAC;AAChB;AAEA,MAAM,aAAa;AAEnB,SAAgB,yBACd,OACoB;CACpB,IAAI,UAAU,KAAA,KAAa,UAAU,OACnC,OAAO;CAGT,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,OAAO;EACL,QAAQ,SAAS,SAAS;GACxB,IAAI,SAAS,KAAA,GAAW;IACtB,QAAQ,MAAM,YAAY,OAAO;IACjC;GACF;GACA,QAAQ,MAAM,YAAY,SAAS,IAAI;EACzC;EACA,OAAO,SAAS,SAAS;GACvB,IAAI,SAAS,KAAA,GAAW;IACtB,QAAQ,KAAK,YAAY,OAAO;IAChC;GACF;GACA,QAAQ,KAAK,YAAY,SAAS,IAAI;EACxC;EACA,OAAO,SAAS,SAAS;GACvB,IAAI,SAAS,KAAA,GAAW;IACtB,QAAQ,KAAK,YAAY,OAAO;IAChC;GACF;GACA,QAAQ,KAAK,YAAY,SAAS,IAAI;EACxC;EACA,QAAQ,SAAS,SAAS;GACxB,IAAI,SAAS,KAAA,GAAW;IACtB,QAAQ,MAAM,YAAY,OAAO;IACjC;GACF;GACA,QAAQ,MAAM,YAAY,SAAS,IAAI;EACzC;CACF;AACF;;;ACxCA,MAAM,YAAY;AAClB,MAAM,WAAW;AACjB,MAAM,+BAAe,IAAI,IAAY,CAAC,WAAW,QAAQ,CAAC;AA+C1D,eAAsB,qBAEpB,QAAqE;CACrE,6BAA6B,OAAO,WAAW;CAE/C,MAAM,MAAM,yBAAyB,OAAO,KAAK;CAEjD,MAAM,YAAY,oBAAoB,OAAO,IAAI;CACjD,IAAI,cAAc,OAAO,eAAe;CAExC,IAAI,KAAK,6BAA6B;EACpC,eAAe,OAAO,KAAK,OAAO,WAAW;EAC7C,cAAc,cAAc;EAC5B;CACF,CAAC;CAED,MAAM,uBAAuB,OAAO,iBAAiB;CACrD,MAAM,MAAkB,EAAE,OAAO,EAAE;CAEnC,MAAM,SAAS,qBACb,QACA,YACC,UAAU,MAAM,SACjB,oBACF;CAEA,MAAM,QAAQ,qBACZ,QACA,WACC,UAAU,MAAM,SACjB,oBACF;CAEA,MAAM,kBAAkB,CAAC;CAEzB,KAAK,MAAM,gBAAgB,OAAO,KAAK,OAAO,WAAW,GAAG;EAC1D,MAAM,MAAM,OAAO,YAAY;EAC/B,MAAM,SAAS,IAAI;EAEnB,MAAM,UAAU,OAAO,2BAAqE;GAC1F,IAAI;GACJ;GACA,aAAa,OAAO;GACpB,eAAe,IAAI,iBAAiB;GACpC,QAAQ,OAAO;GACf,UAAU,mBAAmB,QAAQ,cAAc,UAAU,KAAK,GAAG;GACrE,UAAU,mBAAmB,QAAQ,cAAc,UAAU,KAAK,GAAG;GACrE,UAAU,mBAAmB,QAAQ,cAAc,UAAU,KAAK,GAAG;EACvE,CAAC;EAED,MAAM,aAAa,OAAO,iBAAiB,OAAO;EAClD,uBAAuB,YAAY,cAAc,IAAI,SAAS,GAAG;EACjE,MAAM,qBAAqB,QACxB,WAAyC,OAAO,eACnD;EAEA,IAAI,MAAM,yBAAyB;GACjC;GACA;EACF,CAAC;EAED,gBAA6C,gBAAgB;CAC/D;CAEA,MAAM,cAAc;EAClB,GAAI;EACJ;EACA;CACF;CAEA,MAAM,gBAAgB;CAEtB,MAAM,gBAAgB,CACpB,OAAO,uBAAuB,CAAC,CAAC,GAChC,MAAM,uBAAuB,CAAC,CAAC,CACjC;CAEA,MAAM,OAAO,QAAQ;CACrB,MAAM,MAAM,QAAQ;CAEpB,IAAI,QAAQ,aAAa,MAAM;CAE/B,IAAI,KAAK,8BAA8B;EACrC,aAAa,OAAO,MAAM;EAC1B,YAAY,MAAM,MAAM;EACxB,cAAc,IAAI;CACpB,CAAC;CAED,MAAM,YAAY,OAAO,eAAe,GAAG;CAE3C,eAAe,OAA4B;EACzC,IAAI,CAAC,aAAa;GAChB,IAAI,MAAM,6BAA6B;GACvC,OAAO;IAAE,QAAQ;IAAG,QAAQ;IAAG,QAAQ,CAAC;GAAE;EAC5C;EAEA,IAAI,CAAC,WAAW;GACd,IAAI,KAAK,uCAAuC;GAChD,OAAO;IAAE,QAAQ;IAAG,QAAQ;IAAG,QAAQ,CAAC;GAAE;EAC5C;EAEA,IAAI,KAAK,cAAc;EAEvB,MAAM,OAAO,QAAQ;EACrB,MAAM,MAAM,QAAQ;EAEpB,MAAM,SAAkB,CAAC;EACzB,IAAI,SAAS;EACb,IAAI,SAAS;EAEb,IAAI;GACF,IAAI,UAAU,MACZ,SAAS,MAAM,WAAW,QAAQ,UAAU,MAAM,GAAG;QAErD,IAAI,MAAM,4CAA4C;EAE1D,SAAS,KAAK;GACZ,MAAM,QAAQ,QAAQ,GAAG;GACzB,IAAI,MAAM,sBAAsB,EAAE,SAAS,MAAM,QAAQ,CAAC;GAC1D,OAAO,KAAK,KAAK;EACnB;EAEA,IAAI;GACF,IAAI,UAAU,MACZ,SAAS,MAAM,UAAU,QAAQ,OAAO,UAAU,MAAM,eAAe,GAAG;QAE1E,IAAI,MAAM,4CAA4C;EAE1D,SAAS,KAAK;GACZ,MAAM,QAAQ,QAAQ,GAAG;GACzB,IAAI,MAAM,qBAAqB,EAAE,SAAS,MAAM,QAAQ,CAAC;GACzD,OAAO,KAAK,KAAK;EACnB;EAEA,IAAI,KAAK,iBAAiB;GAAE;GAAQ;GAAQ,YAAY,OAAO;EAAO,CAAC;EAEvE,OAAO;GAAE;GAAQ;GAAQ;EAAO;CAClC;CAEA,eAAe,aAAwC;EACrD,IAAI,KAAK,qBAAqB;EAE9B,MAAM,OAAO,QAAQ;EACrB,MAAM,MAAM,QAAQ;EAEpB,MAAM,SAAkB,CAAC;EACzB,IAAI,SAAS;EACb,IAAI,SAAS;EACb,IAAI,WAAW;EAEf,IAAI,eAAe,WAAW;GAC5B,IAAI;IACF,IAAI,UAAU,MACZ,SAAS,MAAM,WAAW,QAAQ,UAAU,MAAM,GAAG;SAErD,IAAI,MAAM,wDAAwD;GAEtE,SAAS,KAAK;IACZ,MAAM,QAAQ,QAAQ,GAAG;IACzB,IAAI,MAAM,2BAA2B,EAAE,SAAS,MAAM,QAAQ,CAAC;IAC/D,OAAO,KAAK,KAAK;GACnB;GAEA,IAAI;IACF,IAAI,UAAU,MACZ,SAAS,MAAM,UAAU,QAAQ,OAAO,UAAU,MAAM,eAAe,GAAG;SAE1E,IAAI,MAAM,wDAAwD;GAEtE,SAAS,KAAK;IACZ,MAAM,QAAQ,QAAQ,GAAG;IACzB,IAAI,MAAM,2BAA2B,EAAE,SAAS,MAAM,QAAQ,CAAC;IAC/D,OAAO,KAAK,KAAK;GACnB;EACF,OAAO,IAAI,CAAC,aACV,IAAI,MAAM,8CAA8C;OAExD,IAAI,KAAK,0DAA0D;EAGrE,IAAI;GACF,WAAW,MAAM,YAAY,OAAO,eAAe,GAAG;EACxD,SAAS,KAAK;GACZ,MAAM,QAAQ,QAAQ,GAAG;GACzB,IAAI,MAAM,6BAA6B,EAAE,SAAS,MAAM,QAAQ,CAAC;GACjE,OAAO,KAAK,KAAK;EACnB;EAEA,IAAI,KAAK,wBAAwB;GAAE;GAAQ;GAAQ;GAAU,YAAY,OAAO;EAAO,CAAC;EAExF,OAAO;GAAE;GAAQ;GAAQ;GAAU;EAAO;CAC5C;CAEA,SAAS,iBAA0B;EACjC,OAAO;CACT;CAEA,SAAS,eAAe,SAAwB;EAC9C,cAAc;EACd,IAAI,MAAM,wBAAwB,EAAE,aAAa,QAAQ,CAAC;CAC5D;CAEA,SAAS,UAAgB;EACvB,IAAI,MAAM,4BAA4B;EACtC,KAAK,MAAM,gBAAgB,eACzB,aAAa,YAAY;CAE7B;CAEA,OAAO;EAAE;EAAa;EAAM;EAAY;EAAgB;EAAgB;CAAQ;AAClF;AAEA,SAAS,qBACP,QACA,IACA,QACA,eAC4B;CAC5B,MAAM,UAAU,OAAO,2BAA2C;EAChE;EACA;EACA,aAAa,OAAO;EACpB;CACF,CAAC;CAED,OAAO,OAAO,iBAAiB,OAAO;AACxC;AAcA,SAAS,uBACP,YACA,cACA,SACA,KACM;CACN,IAAI,CAAC,SAAS,QACZ;CAGF,MAAM,YAAY;CAElB,MAAM,iBAAuB;EAC3B,KAAK,UAAU,mBAAmB,CAAC,CAAC,UAAU,KAAK,GACjD;EAGF,KAAK,MAAM,YAAY,SAAS;GAC9B,UAAU,YAAY,SAAS,QAAqD;IAClF,MAAM,SAAS;IACf,WAAW,SAAS;GACtB,CAAC;GAED,IAAI,MAAM,+BAA+B;IACvC;IACA,MAAM,SAAS;GACjB,CAAC;EACH;CACF;CAEA,SAAS;CAET,UAAU,KAAK,gBAAgB,QAAQ;AACzC;AAEA,SAAS,mBACP,QACA,cACA,MACA,KACA,KACA;CACA,OAAO,OAAO,WAAiE;EAC7E,KAAK,MAAM,YAAY,OAAO,YAAY,WAAW;GACnD,MAAM,UAAU,SAAS,WAAW,SAAS,WAAW,SAAS;GAEjE,MAAM,QAAqB;IACzB,SAAS,gBAAgB;IACzB;IACA;IACA,KAAK,SAAS;IACd;IACA,WAAW,KAAK,IAAI;IACpB,UAAU,IAAI;IACd,WAAW;IACX,MAAM;IACN,YAAY;IACZ,cAAc;IACd,eAAe;IACf,WAAW;IACX,eAAe;IACf,WAAW;GACb;GAEA,MAAM,OAAO,OAAO,KAAK,CAAC,CAAC,YAAY;GAEvC,IAAI,MAAM,wBAAwB;IAChC,SAAS,MAAM;IACf;IACA;IACA,KAAK,MAAM;IACX,UAAU,MAAM;GAClB,CAAC;EACH;EAEA,OAAO,CAAC;CACV;AACF;AAEA,eAAe,WACb,QACA,MACA,KACiB;CACjB,MAAM,UAAU,CAAC,GAAG,OAAO,MAAM,OAAO,CAAC,CAAC,CACvC,QAAQ,UAAU,CAAC,MAAM,QAAQ,MAAM,eAAe,QAAQ,CAAC,CAC/D,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CAEzC,IAAI,MAAM,eAAe,EAAE,cAAc,QAAQ,OAAO,CAAC;CAEzD,IAAI,QAAQ,WAAW,GAAG,OAAO;CAEjC,MAAM,YAAY,KAAK,IAAI;CAE3B,KAAK,MAAM,SAAS,SAClB,MAAM,OACH,OAAO,MAAM,UAAU,UAAU;EAChC,MAAM,aAAa;EACnB,MAAM,gBAAgB,MAAM,gBAAgB,KAAK;EACjD,MAAM,gBAAgB;EACtB,MAAM,YAAY;EAClB,MAAM,gBAAgB;EACtB,MAAM,YAAY;CACpB,CAAC,CAAC,CACD,YAAY;CAGjB,MAAM,WAA4B,QAAQ,KAAK,WAAW;EACxD,SAAS,MAAM;EACf,cAAc,MAAM;EACpB,MAAM,MAAM;EACZ,KAAK,MAAM;EACX,SAAS,MAAM;EACf,WAAW,MAAM;CACnB,EAAE;CAEF,MAAM,WAAW,sBAAsB,MAAM,KAAK,QAAQ,CAAC;CAE3D,IAAI,KAAK,yBAAyB;EAChC,MAAM,SAAS;EACf,WAAW,SAAS,UAAU;EAC9B,QAAQ,SAAS,QAAQ,UAAU;CACrC,CAAC;CAED,KAAK,MAAM,gBAAgB,SAAS,WAAW;EAC7C,MAAM,OACH,OAAO,aAAa,UAAU,UAAU;GACvC,MAAM,OAAO;GACb,MAAM,aAAa;GACnB,MAAM,YAAY,aAAa;GAC/B,MAAM,YAAY;GAClB,MAAM,gBAAgB;GACtB,MAAM,YAAY;EACpB,CAAC,CAAC,CACD,YAAY;EAEf,IAAI,MAAM,8BAA8B;GACtC,SAAS,aAAa;GACtB,WAAW,aAAa;EAC1B,CAAC;CACH;CAEA,KAAK,MAAM,WAAW,SAAS,UAAU,CAAC,GAAG;EAC3C,MAAM,OACH,OAAO,QAAQ,UAAU,UAAU;GAClC,MAAM,OAAO;GACb,MAAM,aAAa;GACnB,MAAM,YAAY,QAAQ;GAC1B,MAAM,gBAAgB,QAAQ,QAAQ;GACtC,MAAM,YAAY,QAAQ,aAAa;EACzC,CAAC,CAAC,CACD,YAAY;EAEf,IAAI,KAAK,8BAA8B;GACrC,SAAS,QAAQ;GACjB,SAAS,QAAQ;GACjB,MAAM,QAAQ;GACd,WAAW,QAAQ;EACrB,CAAC;CACH;CAEA,OAAO,SAAS,UAAU;AAC5B;AAEA,eAAe,UACb,QACA,OACA,MACA,SACA,KACiB;CACjB,IAAI,SAAS;CACb,IAAI,UAAU;CAEd,OAAO,SAAS;EACd,MAAM,QAAQ,aAAa,KAAK;EAChC,IAAI,MAAM,mBAAmB,EAAE,MAAM,CAAC;EAEtC,MAAM,WAAW,MAAM,KAAK,KAAK;EAEjC,IAAI,MAAM,uBAAuB;GAC/B;GACA,YAAY,SAAS,OAAO;GAC5B,SAAS,SAAS;GAClB,QAAQ,SAAS;EACnB,CAAC;EAED,IAAI,SAAS,OAAO,WAAW,GAAG;EAElC,MAAM,SAAS,CAAC,GAAG,SAAS,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;EAE5E,KAAK,MAAM,SAAS,QAAQ;GAC1B,IAAI,OAAO,IAAI,MAAM,OAAO,GAAG;IAC7B,MAAM,qBAAqB,OAAO,KAAK;IAEvC,IAAI,MAAM,0CAA0C;KAClD,SAAS,MAAM;KACf,WAAW,MAAM;IACnB,CAAC;IACD;GACF;GAEA,MAAM,WAAW,MAAM,IAAI,MAAM,OAAO;GACxC,IAAI,UAAU,MAAM;IAClB,IAAI,MAAM,uCAAuC;KAC/C,SAAS,MAAM;KACf,WAAW,MAAM;IACnB,CAAC;IACD;GACF;GAEA,IAAI,CAAC,UAAU;IACb,MAAM,MAAM,OAAO,aAAa,OAAO,KAAK,CAAC,CAAC,CAAC,YAAY;IAC3D,IAAI,MAAM,wBAAwB;KAChC,SAAS,MAAM;KACf,WAAW,MAAM;KACjB,cAAc,MAAM;IACtB,CAAC;GACH;GAYA,IAAI,CAAC,MAViB,YACpB,SACA,MAAM,cACN,MAAM,SACN,MAAM,MACN,MAAM,KACN,MAAM,SACN,GACF,GAGE,OAAO;GAGT,MAAM,MACH,OAAO,MAAM,UAAU,UAAU;IAChC,MAAM,OAAO;GACf,CAAC,CAAC,CACD,YAAY;GAEf,IAAI,KAAK,uBAAuB;IAC9B,SAAS,MAAM;IACf,WAAW,MAAM;IACjB,cAAc,MAAM;IACpB,MAAM,MAAM;IACZ,KAAK,MAAM;GACb,CAAC;GAED;EACF;EAEA,UAAU,SAAS;CACrB;CAEA,IAAI,KAAK,uBAAuB,EAAE,OAAO,CAAC;CAE1C,OAAO;AACT;AAEA,eAAe,YACb,OACA,SACA,KACiB;CACjB,MAAM,UAAU,CAAC,GAAG,MAAM,MAAM,OAAO,CAAC,CAAC,CACtC,QAAQ,UAAU,CAAC,MAAM,IAAI,CAAC,CAC9B,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;CAE3C,IAAI,KAAK,gCAAgC,EAAE,cAAc,QAAQ,OAAO,CAAC;CAEzE,IAAI,WAAW;CAEf,KAAK,MAAM,SAAS,SAAS;EAW3B,IAAI,CAAC,MAViB,YACpB,SACA,MAAM,cACN,MAAM,SACN,MAAM,MACN,MAAM,KACN,MAAM,SACN,GACF,GAGE;EAGF,MAAM,MACH,OAAO,MAAM,UAAU,UAAU;GAChC,MAAM,OAAO;EACf,CAAC,CAAC,CACD,YAAY;EAEf;EAEA,IAAI,KAAK,gCAAgC;GACvC,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,cAAc,MAAM;EACtB,CAAC;CACH;CAEA,IAAI,KAAK,iCAAiC,EAAE,SAAS,CAAC;CAEtD,OAAO;AACT;AAEA,eAAe,YACb,SACA,cACA,SACA,MACA,KACA,SACA,KACkB;CAClB,IAAI,aAAa,IAAI,YAAY,GAAG;EAClC,IAAI,KAAK,uCAAuC;GAAE;GAAS;GAAc;GAAM;EAAI,CAAC;EACpF,OAAO;CACT;CAEA,MAAM,SAAS,QAAQ;CACvB,IAAI,CAAC,QAAQ;EACX,IAAI,KAAK,sCAAsC;GAC7C;GACA;GACA;GACA;GACA,kBAAkB,OAAO,KAAK,OAAO,CAAC,CAAC,QAAQ,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;EAC7E,CAAC;EACD,OAAO;CACT;CAEA,IAAI,CAAC,OAAO,MAAM,iBAAiB;EACjC,IAAI,KAAK,sDAAsD;GAC7D;GACA;GACA;GACA;GACA,UAAU,OAAO;EACnB,CAAC;EACD,OAAO;CACT;CAEA,IAAI,MAAM,4BAA4B;EAAE;EAAS;EAAc;EAAM;CAAI,CAAC;CAE1E,IAAI;EACF,MAAM,OAAO,MAAM,gBAAgB,EACjC,WAAW,CACT;GACE,YAAY;GACZ;GACA;GACA,UAAU;GACV,UAAU;GACV,SAAS;GACT,YAAY;EACd,CACF,EACF,CAAC;EAED,IAAI,KAAK,4BAA4B;GAAE;GAAS;GAAc;GAAM;EAAI,CAAC;CAC3E,SAAS,KAAK;EACZ,MAAM,QAAQ,QAAQ,GAAG;EACzB,IAAI,MAAM,0BAA0B;GAClC;GACA;GACA;GACA;GACA,SAAS,MAAM;EACjB,CAAC;EACD,MAAM;CACR;CAEA,OAAO;AACT;AAEA,SAAS,aAAa,OAA+C;CACnE,IAAI,MAAM;CAEV,KAAK,MAAM,SAAS,MAAM,MAAM,OAAO,GACrC,IAAI,MAAM,QAAQ,MAAM,YAAY,KAAK,MAAM,MAAM;CAGvD,OAAO;AACT;AAEA,SAAS,aAAa,QAAiD;CACrE,IAAI,MAAM;CAEV,KAAK,MAAM,SAAS,OAAO,MAAM,OAAO,GACtC,IAAI,MAAM,WAAW,KAAK,MAAM,MAAM;CAGxC,OAAO,MAAM;AACf;AAEA,eAAe,qBACb,OACA,OACe;CACf,MAAM,WAAW,MAAM,IAAI,MAAM,OAAO;CAExC,IAAI,CAAC,UAAU;EACb,MAAM,MAAM,OAAO,aAAa,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY;EAC1D;CACF;CAEA,IAAI,CAAC,SAAS,QAAQ,SAAS,cAAc,MAAM,WACjD,MAAM,MACH,OAAO,MAAM,UAAU,UAAU;EAChC,MAAM,YAAY,MAAM;EACxB,MAAM,OAAO;CACf,CAAC,CAAC,CACD,YAAY;AAEnB;AAEA,SAAS,aAAa,OAAoB,MAA2B;CACnE,OAAO;EACL,SAAS,MAAM;EACf,WAAW,MAAM;EACjB,cAAc,MAAM;EACpB,MAAM,MAAM;EACZ,KAAK,MAAM;EACX,SAAS,MAAM;EACf,WAAW,MAAM;EACjB;CACF;AACF;AAEA,SAAS,6BAA6B,aAA4C;CAChF,KAAK,MAAM,MAAM,OAAO,KAAK,WAAW,GACtC,IAAI,aAAa,IAAI,EAAE,GACrB,MAAM,IAAI,MACR,kBAAkB,GAAG,8DACvB;AAGN;AAEA,SAAS,QAAQ,KAAqB;CACpC,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC3D;;;ACpuBA,SAAgB,oBACd,SACA,UAAgC,CAAC,GACf;CAClB,IAAI,WAAqB;CACzB,IAAI,cAAiC;CAErC,MAAM,SAAS,YAAwB;EACrC,QAAQ,QAAQ;EAEhB,IAAI,UACF,OAAO;EAGT,IAAI,CAAC,aACH,cAAc,QAAQ,CAAC,CACpB,MAAM,aAAa;GAClB,WAAW;GACX,OAAO;EACT,CAAC,CAAC,CACD,OAAO,UAAmB;GACzB,cAAc;GACd,MAAM;EACR,CAAC;EAGL,OAAO;CACT;CAEA,MAAM,UACJ,QAAQ,yBAAyB;CAEnC,MAAM,QAAQ,IAAI,MAAM,CAAC,GAAQ;EAC/B,IAAI,SAAS,MAAM,UAAU;GAC3B,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,OAAO;GAEzB,OAAO,QAAQ,IAAI,UAAU,MAAM,QAAQ;EAC7C;EACA,IAAI,SAAS,MAAM;GACjB,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,OAAO;GAEzB,OAAO,QAAQ,IAAI,UAAU,IAAI;EACnC;EACA,iBAAiB;GACf,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,OAAO;GAEzB,OAAO,QAAQ,eAAe,QAAQ;EACxC;CACF,CAAC;CAED,MAAM,cAAoB;EACxB,WAAW;EACX,cAAc;CAChB;CAEA,OAAO;EAAE;EAAQ;EAAO;CAAM;AAChC"}
@@ -1,4 +1,4 @@
1
- import { A as PersistedCollectionOptionsFn, C as SyncHandlersConfig, D as EventSourcedLogger, E as SyncUrlConfig, T as SyncTransport, k as CreateCollectionFn, p as PersistedCollectionPersistence, r as EventSourcedDB, x as SQLiteDriver } from "./types-CAT14Tbj.mjs";
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,KAGU,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,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"}
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"}
@@ -1,4 +1,4 @@
1
- import { n as createEventSourcedDB, t as createLazySingleton } from "./lazy-singleton-DFrAsYTV.mjs";
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;;;ACaA,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,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
+ {"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 { PersistedCollectionOptionsFn as A, SyncHandlersConfig as C, EventSourcedLogger as D, SyncUrlConfig as E, createEventSourcedLogger as O, ServerEvent as S, SyncTransport as T, PushEventsFn as _, InboxEntry as a, ReservedCollections as b, ManualSyncResult as c, OutboxEntry as d, OutboxSyncStatus as f, PushConfirmation as g, PullResponse as h, EventSourcedDBConfig as i, CreateCollectionFn as k, MutationType as l, PullEventsFn as m, CollectionMap as n, InferKey as o, PersistedCollectionPersistence$1 as p, EventSourcedDB as r, InferState as s, CollectionDef as t, OutboundEvent as u, PushFailure as v, SyncResult as w, SQLiteDriver as x, PushResponse as y };
148
- //# sourceMappingURL=types-CAT14Tbj.d.mts.map
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-CAT14Tbj.d.mts","names":[],"sources":["../src/persisted-collection.ts","../src/utils/logger.ts","../src/types.ts"],"mappings":";;;;KAMY,4BAAA,UAAsC,0BAA0B;AAAA,KAEhE,kBAAA,oDACV,OAAA,EAAS,gBAAA,CAAiB,CAAA,EAAG,IAAA,SAAa,WAAA,GAAc,wBAAA,MACrD,UAAA,CAAW,CAAA,EAAG,IAAA;;;KCRP,kBAAA;EACV,KAAA,GAAQ,OAAA,UAAiB,IAAA,GAAO,MAAA;EAChC,IAAA,GAAO,OAAA,UAAiB,IAAA,GAAO,MAAA;EAC/B,IAAA,GAAO,OAAA,UAAiB,IAAA,GAAO,MAAA;EAC/B,KAAA,GAAQ,OAAA,UAAiB,IAAA,GAAO,MAAA;AAAA;AAAA,iBAYlB,wBAAA,CACd,KAAA,aAAkB,kBAAA,GACjB,kBAAkB;;;KCPT,YAAA;AAAA,KACA,gBAAA;AAAA,KAEA,WAAA;EACV,OAAA;EACA,YAAA;EACA,IAAA,EAAM,YAAA;EACN,GAAA;EACA,OAAA,EAAS,MAAA;EACT,SAAA;EACA,QAAA;EACA,SAAA;EACA,IAAA;EACA,UAAA,EAAY,gBAAA;EACZ,YAAA;EACA,aAAA;EACA,SAAA;EACA,aAAA;EACA,SAAA;AAAA;AAAA,KAGU,UAAA;EACV,OAAA;EACA,SAAA;EACA,YAAA;EACA,IAAA,EAAM,YAAA;EACN,GAAA;EACA,OAAA,EAAS,MAAM;EACf,SAAA;EACA,IAAA;AAAA;AAAA,KAGU,WAAA;EACV,SAAA;EACA,OAAA;EACA,YAAA;EACA,IAAA,EAAM,YAAA;EACN,GAAA;EACA,OAAA,EAAS,MAAM;EACf,SAAA;EACA,MAAA;AAAA;AAAA,KAGU,gBAAA;EACV,OAAA;EACA,SAAS;AAAA;AAAA,KAGC,WAAA;EACV,OAAA;EACA,OAAA;EACA,IAAA;EACA,SAAA;AAAA;AAAA,KAGU,YAAA;EACV,SAAA,EAAW,aAAA,CAAc,gBAAA;EACzB,MAAA,GAAS,aAAA,CAAc,WAAA;AAAA;AAAA,KAGb,YAAA;EACV,MAAA,EAAQ,aAAa,CAAC,WAAA;EACtB,MAAA;EACA,OAAA;AAAA;AAAA,KAGU,YAAA,IACV,MAAA,EAAQ,aAAA,CAAc,aAAA,MACnB,OAAA,CAAQ,YAAA,GAAe,aAAA,CAAc,gBAAA;AAAA,KAE9B,YAAA,IAAgB,MAAA;EAAU,KAAA;AAAA,MAAoB,OAAO,CAAC,YAAA;AAAA,KAEtD,aAAA;EACV,IAAA,EAAM,YAAA;EACN,IAAA,GAAO,KAAA,aAAkB,OAAA,CAAQ,YAAA;AAAA;AAAA,KAGvB,aAAA;EACV,OAAA;EACA,YAAA;EACA,IAAA,EAAM,YAAA;EACN,GAAA;EACA,OAAA,EAAS,MAAM;EACf,SAAA;AAAA;AAAA,KAGU,aAAA;EACV,IAAA;EACA,IAAA;EACA,OAAA,GACI,MAAA,0BACO,MAAA,mBAAyB,OAAA,CAAQ,MAAA;AAAA;AAAA,KAGlC,kBAAA;EACV,UAAA,GAAa,YAAA;EACb,UAAA,GAAa,YAAA;EACb,OAAA;EACA,OAAA;EACA,OAAA,GACI,MAAA,0BACO,MAAA,mBAAyB,OAAA,CAAQ,MAAA;AAAA;AAAA,KAGlC,aAAA;EAIV,MAAA,GAAS,KAAA,EAAO,MAAA,KAAW,IAAI;EAC/B,aAAA;AAAA;AAAA,KAGU,UAAA,MAAgB,CAAA;EAAY,MAAA,GAAS,KAAA;AAAA,KAC5C,CAAC,6BAEA,CAAA,kBACE,CAAA;AAAA,KAII,QAAA,MAAc,CAAC;EAAW,MAAA,GAAS,KAAA;AAAA,IAC3C,CAAA,2BACE,CAAA;AAAA,KAID,uBAAA;EACH,MAAA,GAAS,KAAA;EACT,aAAA;AAAA;AAAA,KAGU,aAAA,eAA4B,MAAA,SAAe,uBAAA,mBACzC,KAAA,GAAQ,UAAA,CAAW,UAAA,CAAW,KAAA,CAAM,CAAA,IAAK,QAAA,CAAS,KAAA,CAAM,CAAA;AAAA,KAG1D,mBAAA;EACV,MAAA,EAAQ,UAAA,CAAW,WAAA;EACnB,KAAA,EAAO,UAAA,CAAW,UAAA;AAAA;AAAA,KAGR,oBAAA,eAAmC,MAAA,SAAe,uBAAA;EAC5D,WAAA,EAAa,gCAAA;EACb,WAAA,EAAa,KAAA;EACb,IAAA,GAAO,kBAAA,GAAqB,aAAA,GAAgB,aAAA;EAC5C,aAAA;EACA,KAAA,aAAkB,kBAAA;EAClB,gBAAA,EAAkB,kBAAA;EAClB,0BAAA,EAA4B,4BAAA;AAAA;AAAA,KAGlB,cAAA,eAA6B,MAAA,SAAe,uBAAA;EACtD,WAAA,EAAa,aAAA,CAAc,KAAA,IAAS,mBAAA;EACpC,IAAA,QAAY,OAAA,CAAQ,UAAA;EACpB,UAAA,QAAkB,OAAA,CAAQ,gBAAA;EAC1B,OAAA;AAAA;AAAA,KAGU,UAAA;EACV,MAAA;EACA,MAAA;EACA,MAAA,EAAQ,aAAa,CAAC,KAAA;AAAA;AAAA,KAGZ,gBAAA,GAAmB,UAAU;EACvC,QAAQ;AAAA"}
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,6 +1,6 @@
1
1
  {
2
2
  "name": "event-sourced-collection",
3
- "version": "0.0.2",
3
+ "version": "0.0.6",
4
4
  "description": "Event-sourced local-first database built on TanStack DB persistence",
5
5
  "keywords": [
6
6
  "event-sourcing",
@@ -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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"}