event-sourced-collection 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
- # @tanstack-db-collections/event-sourced
1
+ # event-sourced-collection
2
2
 
3
3
  Event-sourced local-first database on top of TanStack DB persistence. Every `insert`, `update`, and `delete` is logged to a SQLite event table and synced to your backend when online.
4
4
 
5
+ Design decisions: [ARCHITECTURE.md](./ARCHITECTURE.md)
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
8
- npm install @tanstack-db-collections/event-sourced @tanstack/db @tanstack/db-sqlite-persistence-core
10
+ npm install event-sourced-collection @tanstack/db @tanstack/db-sqlite-persistence-core
9
11
  ```
10
12
 
11
13
  Plus a platform package:
@@ -18,9 +20,47 @@ npm install @tanstack/browser-db-sqlite-persistence
18
20
  npm install @tanstack/react-native-db-sqlite-persistence
19
21
  ```
20
22
 
23
+ ## Project structure
24
+
25
+ A minimal browser app needs four pieces. Split them by concern so setup stays obvious:
26
+
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
36
+ ```
37
+
38
+ | File | Role |
39
+ | ---- | ---- |
40
+ | `collections.ts` | Single source of truth: row types, collection registry, push/pull handlers, one `createBrowserEventSourcedDB` call exporting `ensureDb` + `db` |
41
+ | `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 |
44
+
45
+ Flow:
46
+
47
+ ```
48
+ app mount → ensureDb() → load() + platform + engine (run once, deduped)
49
+ ↓
50
+ db.collections.* available
51
+ ↓
52
+ useLiveQuery / insert / update / delete
53
+ ↓
54
+ db.sync() or useEventSourcedSync()
55
+ ```
56
+
21
57
  ## Quick start
22
58
 
23
- ### 1. Define types
59
+ Copy the pattern below into `collections.ts`, then wire `ensureDb()` in your app shell.
60
+
61
+ ### 1. Domain types
62
+
63
+ One type per collection you will register. These are your row shapes in SQLite.
24
64
 
25
65
  ```typescript
26
66
  type User = {
@@ -46,65 +86,154 @@ type AppSettings = {
46
86
  };
47
87
  ```
48
88
 
49
- ### 2. Create the database and collections
89
+ ### 2. Collection registry + typed DB handle
50
90
 
51
- Register collections in the `collections` option — that is how they are created. There is no separate `createUsersCollection()` call.
91
+ Keys here must match the `collections` option you pass in step 4. This gives you `db.collections.users`, etc. with full inference.
52
92
 
53
93
  ```typescript
54
- import { createCollection } from "@tanstack/react-db";
55
- import {
56
- BrowserCollectionCoordinator,
57
- createBrowserWASQLitePersistence,
58
- openBrowserWASQLiteOPFSDatabase,
59
- persistedCollectionOptions,
60
- } from "@tanstack/browser-db-sqlite-persistence";
61
- import { createEventSourcedDB } from "@tanstack-db-collections/event-sourced";
62
- import { createBrowserPlatform } from "@tanstack-db-collections/event-sourced/browser";
63
-
64
- const platform = await createBrowserPlatform(
65
- {
66
- openBrowserWASQLiteOPFSDatabase,
67
- createBrowserWASQLitePersistence,
68
- BrowserCollectionCoordinator,
69
- },
70
- { databaseName: "my-app.sqlite" },
71
- );
94
+ import type { EventSourcedDB } from "event-sourced-collection";
72
95
 
73
- export const db = await createEventSourcedDB({
74
- persistence: platform.persistence,
75
- createCollection,
76
- persistedCollectionOptions,
77
- sync: {
78
- pushEvents: (events) => typedRpc.events.push({ events }),
79
- pullEvents: ({ since }) => typedRpc.events.pull({ since }),
80
- },
96
+ type AppCollectionDefs = {
97
+ users: { getKey: (user: User) => string };
98
+ todos: { getKey: (todo: Todo) => string };
99
+ settings: { getKey: (settings: AppSettings) => string };
100
+ };
101
+
102
+ export type AppDb = EventSourcedDB<AppCollectionDefs>;
103
+ ```
104
+
105
+ ### 3. Sync transport
106
+
107
+ Implement push/pull against your API. These run during `db.sync()` — upload local outbox rows, download server events.
108
+
109
+ ```typescript
110
+ import type { OutboundEvent, PullResponse, PushResponse } from "event-sourced-collection";
111
+
112
+ const getAccessToken = () => localStorage.getItem("accessToken") ?? "";
113
+
114
+ async function pushEvents(events: ReadonlyArray<OutboundEvent>): Promise<PushResponse> {
115
+ const response = await fetch("/api/sync/events", {
116
+ method: "POST",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ Authorization: `Bearer ${getAccessToken()}`,
120
+ },
121
+ body: JSON.stringify(events),
122
+ });
123
+ if (!response.ok) throw new Error(`Push failed: ${response.status}`);
124
+ return response.json();
125
+ }
126
+
127
+ async function pullEvents({ since }: { since: number }): Promise<PullResponse> {
128
+ const response = await fetch(`/api/sync/events?since=${since}`, {
129
+ headers: {
130
+ Accept: "application/json",
131
+ Authorization: `Bearer ${getAccessToken()}`,
132
+ },
133
+ });
134
+ if (!response.ok) throw new Error(`Pull failed: ${response.status}`);
135
+ return response.json();
136
+ }
137
+ ```
138
+
139
+ ### 4. Create the database (one call)
140
+
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.
142
+
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()`.
144
+
145
+ ```typescript
146
+ import { createBrowserEventSourcedDB } from "event-sourced-collection/browser";
147
+
148
+ const { ensureDb, db, close } = createBrowserEventSourcedDB<AppCollectionDefs>({
149
+ databaseName: "my-app.sqlite",
150
+ debug: import.meta.env.DEV,
81
151
  collections: {
82
- users: { getKey: (u: User) => u.id },
83
- todos: { getKey: (t: Todo) => t.id },
84
- settings: { getKey: (s: AppSettings) => s.id },
152
+ users: { getKey: (user: User) => user.id },
153
+ todos: { getKey: (todo: Todo) => todo.id },
154
+ settings: { getKey: (settings: AppSettings) => settings.id },
155
+ },
156
+ sync: { pushEvents, pullEvents },
157
+ load: async () => {
158
+ const { createCollection } = await import("@tanstack/react-db");
159
+ const platform = await import("@tanstack/browser-db-sqlite-persistence");
160
+ return { ...platform, createCollection };
85
161
  },
86
162
  });
163
+
164
+ export { db, ensureDb, close };
87
165
  ```
88
166
 
167
+ The handle is `{ ensureDb, db, close }`:
168
+
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.
172
+
89
173
  After setup you get:
90
174
 
91
175
  - `db.collections.users`
92
176
  - `db.collections.todos`
93
177
  - `db.collections.settings`
94
178
 
95
- Plus two built-in collections that are always available, even with no sync configured:
179
+ Plus two built-in collections (always present, even without sync):
180
+
181
+ - `db.collections.outbox` — local mutations waiting to be pushed
182
+ - `db.collections.inbox` — server events pulled to this device
183
+
184
+ Both are normal queryable collections for sync UI (see [Inspecting sync state](#inspecting-sync-state)). `outbox` and `inbox` are reserved ids.
185
+
186
+ ### 5. Wire the app shell
187
+
188
+ Call `ensureDb()` once when the app mounts, before any collection reads:
189
+
190
+ ```typescript
191
+ import { useEffect, useState } from "react";
192
+ import { ensureDb } from "./data-access-layer/collections";
193
+
194
+ function AppShell() {
195
+ const [ready, setReady] = useState(false);
96
196
 
97
- - `db.collections.outbox` — local mutations waiting to be pushed to the server
98
- - `db.collections.inbox` — server events that have been pulled to this device
197
+ useEffect(() => {
198
+ void ensureDb().then(() => setReady(true));
199
+ }, []);
99
200
 
100
- Both are normal queryable collections, so you can visualize sync state directly in your UI (see [Inspecting sync state](#inspecting-sync-state)). `outbox` and `inbox` are reserved ids — defining a collection with either name throws.
201
+ if (!ready) return <Loading />;
202
+ return <YourRoutes />;
203
+ }
204
+ ```
205
+
206
+ Optional background sync (`sync-events.ts` + hook):
207
+
208
+ ```typescript
209
+ // sync-events.ts
210
+ import { ensureDb } from "./collections";
211
+
212
+ export async function manualSyncEvents() {
213
+ const database = await ensureDb();
214
+ return database.manualSync();
215
+ }
216
+
217
+ // use-event-sourced-sync.ts
218
+ import { useQuery } from "@tanstack/react-query";
219
+ import { manualSyncEvents } from "@/data-access-layer/sync-events";
220
+
221
+ export function useEventSourcedSync(enabled: boolean) {
222
+ return useQuery({
223
+ queryKey: ["sync"],
224
+ queryFn: () => manualSyncEvents(),
225
+ enabled,
226
+ refetchInterval: 60_000,
227
+ });
228
+ }
229
+ ```
101
230
 
102
- ### 3. Write data
231
+ ### 6. Write data
103
232
 
104
233
  Use collections like normal TanStack DB. Mutations are logged to the event store automatically.
105
234
 
106
235
  ```typescript
107
- import { db } from "./db";
236
+ import { db } from "./data-access-layer/collections";
108
237
 
109
238
  const userId = crypto.randomUUID();
110
239
 
@@ -146,11 +275,11 @@ db.collections.settings.update("app", (draft) => {
146
275
  });
147
276
  ```
148
277
 
149
- ### 4. Read data
278
+ ### 7. Read data
150
279
 
151
280
  ```typescript
152
281
  import { useLiveQuery } from "@tanstack/react-db";
153
- import { db } from "./db";
282
+ import { db } from "./data-access-layer/collections";
154
283
 
155
284
  function TodoList() {
156
285
  const { data: todos = [] } = useLiveQuery((q) =>
@@ -177,14 +306,19 @@ function TodoList() {
177
306
  }
178
307
  ```
179
308
 
180
- ### 5. Sync
309
+ ### 8. Sync
181
310
 
182
311
  ```typescript
183
- const result = await db.sync();
312
+ import { ensureDb } from "./data-access-layer/collections";
313
+
314
+ const db = await ensureDb();
315
+ await db.sync();
184
316
 
185
- window.addEventListener("online", () => db.sync());
317
+ window.addEventListener("online", () => void db.sync());
186
318
  ```
187
319
 
320
+ Or use a periodic hook (see step 5).
321
+
188
322
  ## Inspecting sync state
189
323
 
190
324
  `outbox` and `inbox` are queryable collections. Each row carries a `sync` flag:
@@ -195,7 +329,7 @@ window.addEventListener("online", () => db.sync());
195
329
  ```typescript
196
330
  import { useLiveQuery } from "@tanstack/react-db";
197
331
  import { eq } from "@tanstack/db";
198
- import { db } from "./db";
332
+ import { db } from "./data-access-layer/collections";
199
333
 
200
334
  function SyncStatus() {
201
335
  const { data: unpushed = [] } = useLiveQuery((q) =>
@@ -218,7 +352,7 @@ function SyncStatus() {
218
352
 
219
353
  | Step | What happens |
220
354
  | ---- | ------------ |
221
- | Register collections | `collections: { users, todos, settings }` in `createEventSourcedDB` |
355
+ | Register collections | `collections: { users, todos, settings }` in `createBrowserEventSourcedDB` (or low-level `createEventSourcedDB`) |
222
356
  | Access them | `db.collections.users`, etc. |
223
357
  | Write data | `.insert()`, `.update()`, `.delete()` |
224
358
  | Log mutations | Each write appends a row to `db.collections.outbox` (`sync: false`) |
@@ -227,9 +361,17 @@ function SyncStatus() {
227
361
 
228
362
  On push, confirmed outbox rows flip to `sync: true` with their server `globalSeq`. On pull, new server events are written to `inbox` (`sync: false`), replayed into the target collection via `acceptMutations`, then flipped to `sync: true`. The client's own events come back on pull and are written to `inbox` as already applied. The pull cursor is derived from the highest synced `globalSeq` in `inbox` — there is no separate cursor table.
229
363
 
364
+ Step by step:
365
+
366
+ 1. User mutates `db.collections.todos` → row appended to local **outbox** (`sync: false`).
367
+ 2. `manualSync()` or background hook pushes outbox → `POST /api/sync/events`.
368
+ 3. Server assigns `globalSeq` → outbox rows flip to `sync: true`.
369
+ 4. Pull fetches newer events → `GET /api/sync/events?since=<cursor>`.
370
+ 5. Remote events land in **inbox**, replay into collections, flip to `sync: true`.
371
+
230
372
  ## Server contract
231
373
 
232
- ### `POST /api/events`
374
+ ### `POST /api/sync/events`
233
375
 
234
376
  Request: array of outbound events (`eventId`, `collectionId`, `type`, `key`, `payload`, `timestamp`).
235
377
 
@@ -242,7 +384,7 @@ Response:
242
384
  }
243
385
  ```
244
386
 
245
- ### `GET /api/events?since={globalSeq}`
387
+ ### `GET /api/sync/events?since={globalSeq}`
246
388
 
247
389
  Response:
248
390
 
@@ -270,6 +412,38 @@ CREATE TABLE events (
270
412
  CREATE INDEX idx_events_global_seq ON events (global_seq);
271
413
  ```
272
414
 
415
+ ## Simulate another device
416
+
417
+ POST a fake event directly to your sync server to mimic a push from another client. Use a unique `eventId` — it must not already exist in the receiving device's local outbox.
418
+
419
+ ```bash
420
+ curl -X POST http://localhost:3000/api/sync/events \
421
+ -H "Content-Type: application/json" \
422
+ -d '[{
423
+ "eventId": "019c0000-0000-7000-8000-remote-demo",
424
+ "collectionId": "todos",
425
+ "type": "insert",
426
+ "key": "remote-todo-demo",
427
+ "payload": {
428
+ "id": "remote-todo-demo",
429
+ "userId": "remote-user",
430
+ "title": "From another device",
431
+ "status": "pending",
432
+ "createdAt": 1730000000000,
433
+ "updatedAt": 1730000000000
434
+ },
435
+ "timestamp": 1730000000000
436
+ }]'
437
+ ```
438
+
439
+ Then call `manualSync()` or `db.sync()` on the client. The event appears in inbox and the todo is replayed locally.
440
+
441
+ Check server state:
442
+
443
+ ```bash
444
+ curl "http://localhost:3000/api/sync/events?since=0"
445
+ ```
446
+
273
447
  ## Sync Options
274
448
 
275
449
  You can run fully offline by omitting `sync`. In that mode local mutations still append to `outbox`, and `sync()` is a no-op.
@@ -277,7 +451,7 @@ You can run fully offline by omitting `sync`. In that mode local mutations still
277
451
  Use typed functions when your app already has RPC/server-function clients:
278
452
 
279
453
  ```typescript
280
- import type { PullResponse, PushResponse } from "@tanstack-db-collections/event-sourced";
454
+ import type { PullResponse, PushResponse } from "event-sourced-collection";
281
455
 
282
456
  const sync = {
283
457
  pushEvents: async (events): Promise<PushResponse> => {
@@ -309,7 +483,7 @@ You can provide only `pushEvents`/`pushUrl` for upload-only sync or only `pullEv
309
483
  For existing integrations, the legacy shapes still work:
310
484
 
311
485
  ```typescript
312
- import type { SyncTransport } from "@tanstack-db-collections/event-sourced";
486
+ import type { SyncTransport } from "event-sourced-collection";
313
487
 
314
488
  const transport: SyncTransport = {
315
489
  async push(events) { /* return confirmed event ids + globalSeq */ },
@@ -321,33 +495,48 @@ const db = await createEventSourcedDB({ sync: transport, /* ... */ });
321
495
 
322
496
  ## React Native
323
497
 
498
+ 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.
499
+
324
500
  ```typescript
325
- import { createCollection } from "@tanstack/react-native-db";
326
- import { createReactNativeSQLitePersistence, persistedCollectionOptions } from "@tanstack/react-native-db-sqlite-persistence";
327
- import { createEventSourcedDB } from "@tanstack-db-collections/event-sourced";
328
- import { createReactNativePlatform } from "@tanstack-db-collections/event-sourced/react-native";
329
- import { openDatabase } from "react-native-op-sqlite";
330
-
331
- const platform = createReactNativePlatform(
332
- { createReactNativeSQLitePersistence },
333
- { database: openDatabase({ name: "my-app.sqlite" }) },
334
- );
501
+ import { createReactNativeEventSourcedDB } from "event-sourced-collection/react-native";
335
502
 
336
- export const db = await createEventSourcedDB({
337
- persistence: platform.persistence,
338
- createCollection,
339
- persistedCollectionOptions,
503
+ const { ensureDb, db } = createReactNativeEventSourcedDB<AppCollectionDefs>({
340
504
  collections: {
341
- users: { getKey: (u: User) => u.id },
342
- todos: { getKey: (t: Todo) => t.id },
505
+ users: { getKey: (user: User) => user.id },
506
+ todos: { getKey: (todo: Todo) => todo.id },
507
+ },
508
+ sync: { pushEvents, pullEvents },
509
+ load: async () => {
510
+ const { createCollection } = await import("@tanstack/react-native-db");
511
+ const { createReactNativeSQLitePersistence, persistedCollectionOptions } = await import(
512
+ "@tanstack/react-native-db-sqlite-persistence"
513
+ );
514
+ const { openDatabase } = await import("react-native-op-sqlite");
515
+
516
+ return {
517
+ createReactNativeSQLitePersistence,
518
+ persistedCollectionOptions,
519
+ createCollection,
520
+ database: openDatabase({ name: "my-app.sqlite" }),
521
+ };
343
522
  },
344
523
  });
524
+
525
+ export { db, ensureDb };
345
526
  ```
346
527
 
347
528
  Insert, update, delete, `useLiveQuery`, and `sync()` work the same as in the browser.
348
529
 
349
530
  ## Cleanup
350
531
 
532
+ Call the handle's `close()` to dispose the engine and release the platform:
533
+
534
+ ```typescript
535
+ await close();
536
+ ```
537
+
538
+ If you wired things up by hand with the low-level `createEventSourcedDB` + `createBrowserPlatform`, dispose them directly instead:
539
+
351
540
  ```typescript
352
541
  db.dispose();
353
542
  await platform.close();
@@ -355,8 +544,34 @@ await platform.close();
355
544
 
356
545
  ## API
357
546
 
547
+ ### `createBrowserEventSourcedDB(config)` / `createReactNativeEventSourcedDB(config)`
548
+
549
+ The recommended entry points. Import from `event-sourced-collection/browser` or `event-sourced-collection/react-native`.
550
+
551
+ | Option | Required | Description |
552
+ | ------ | -------- | ----------- |
553
+ | `collections` | Yes | Collection definitions (`getKey`, optional `schemaVersion`) — must not use the reserved ids `outbox`/`inbox` |
554
+ | `load` | Yes | Async callback returning the platform modules + `createCollection` + `persistedCollectionOptions`. Keeps platform/framework imports in your app |
555
+ | `databaseName` | Browser only | SQLite database file name |
556
+ | `coordinatorDbName` | No (browser) | Multi-tab coordinator name; defaults to `databaseName` without the `.sqlite` suffix |
557
+ | `sync` | No | Offline by default. Same shapes as `createEventSourcedDB` |
558
+ | `schemaVersion` | No | Default `1` |
559
+ | `debug` | No | `boolean` or a custom logger |
560
+
561
+ Returns `{ ensureDb, db, close }`:
562
+
563
+ - `ensureDb()` — runs `load` + setup once (deduped), returns the ready DB. The browser helper throws outside a browser environment.
564
+ - `db` — proxy that forwards to the instance after init and throws if used before `ensureDb()`.
565
+ - `close()` — disposes the engine (and closes the SQLite platform on browser); the next `ensureDb()` re-initializes.
566
+
567
+ ### `createLazySingleton(factory, options?)`
568
+
569
+ Dependency-free building block behind the helpers. Wraps an async `factory` into `{ ensure, proxy, reset }`: `ensure()` runs the factory once (deduped, retried after failure), `proxy` forwards to the instance and throws until initialized, `reset()` clears it. Accepts an optional `guard` and `notInitializedMessage`.
570
+
358
571
  ### `createEventSourcedDB(config)`
359
572
 
573
+ Low-level core for full control or non-standard platforms. The helpers above call this for you.
574
+
360
575
  | Option | Required | Description |
361
576
  | ------ | -------- | ----------- |
362
577
  | `persistence` | Yes | TanStack DB persistence config |
@@ -370,8 +585,8 @@ Returns `{ collections, sync, manualSync, dispose }`, where `collections` includ
370
585
 
371
586
  ### `createBrowserPlatform(deps, config)`
372
587
 
373
- Import from `@tanstack-db-collections/event-sourced/browser`. Sets up browser SQLite + multi-tab coordinator.
588
+ Import from `event-sourced-collection/browser`. Sets up browser SQLite + multi-tab coordinator.
374
589
 
375
590
  ### `createReactNativePlatform(deps, config)`
376
591
 
377
- Import from `@tanstack-db-collections/event-sourced/react-native`. Sets up React Native SQLite persistence.
592
+ Import from `event-sourced-collection/react-native`. Sets up React Native SQLite persistence.
@@ -1,4 +1,4 @@
1
- import { x as SQLiteDriver } from "./types-CAT14Tbj.mjs";
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";
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
 
@@ -30,5 +30,30 @@ type BrowserWASQLiteDatabase = {
30
30
  //#region src/platforms/browser.d.ts
31
31
  declare function createBrowserPlatform(deps: BrowserPlatformDeps, config: BrowserPlatformConfig): Promise<BrowserPlatformResult>;
32
32
  //#endregion
33
- export { type BrowserCoordinatorInstance, type BrowserPlatformConfig, type BrowserPlatformDeps, type BrowserPlatformResult, type BrowserWASQLiteDatabase, createBrowserPlatform };
33
+ //#region src/platforms/browser-event-sourced-db.d.ts
34
+ type CollectionDefConstraint = {
35
+ getKey: (state: never) => string | number;
36
+ schemaVersion?: number;
37
+ };
38
+ type BrowserEventSourcedModules = BrowserPlatformDeps & {
39
+ createCollection: CreateCollectionFn;
40
+ persistedCollectionOptions: PersistedCollectionOptionsFn;
41
+ };
42
+ type BrowserEventSourcedDBConfig<TDefs extends Record<string, CollectionDefConstraint>> = {
43
+ databaseName: string;
44
+ coordinatorDbName?: string;
45
+ collections: TDefs;
46
+ load: () => Promise<BrowserEventSourcedModules>;
47
+ sync?: SyncHandlersConfig | SyncUrlConfig | SyncTransport;
48
+ schemaVersion?: number;
49
+ debug?: boolean | EventSourcedLogger;
50
+ };
51
+ type BrowserEventSourcedDBHandle<TDefs extends Record<string, CollectionDefConstraint>> = {
52
+ ensureDb: () => Promise<EventSourcedDB<TDefs>>;
53
+ db: EventSourcedDB<TDefs>;
54
+ close: () => Promise<void>;
55
+ };
56
+ declare function createBrowserEventSourcedDB<const TDefs extends Record<string, CollectionDefConstraint>>(config: BrowserEventSourcedDBConfig<TDefs>): BrowserEventSourcedDBHandle<TDefs>;
57
+ //#endregion
58
+ export { type BrowserCoordinatorInstance, type BrowserEventSourcedDBConfig, type BrowserEventSourcedDBHandle, type BrowserEventSourcedModules, type BrowserPlatformConfig, type BrowserPlatformDeps, type BrowserPlatformResult, type BrowserWASQLiteDatabase, createBrowserEventSourcedDB, createBrowserPlatform };
34
59
  //# sourceMappingURL=browser.d.mts.map
@@ -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"],"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"}
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"}
package/dist/browser.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { n as createEventSourcedDB, t as createLazySingleton } from "./lazy-singleton-DFrAsYTV.mjs";
1
2
  //#region src/platforms/browser-wa-sqlite-driver.ts
2
3
  function assertTransactionCallbackHasDriverArg(fn) {
3
4
  if (fn.length > 0) return;
@@ -102,6 +103,58 @@ async function createBrowserPlatform(deps, config) {
102
103
  };
103
104
  }
104
105
  //#endregion
105
- export { createBrowserPlatform };
106
+ //#region src/platforms/browser-event-sourced-db.ts
107
+ function createBrowserEventSourcedDB(config) {
108
+ let activeDb = null;
109
+ let closePlatform = null;
110
+ const factory = async () => {
111
+ const modules = await config.load();
112
+ const platform = await createBrowserPlatform({
113
+ openBrowserWASQLiteOPFSDatabase: modules.openBrowserWASQLiteOPFSDatabase,
114
+ createBrowserWASQLitePersistence: modules.createBrowserWASQLitePersistence,
115
+ BrowserCollectionCoordinator: modules.BrowserCollectionCoordinator
116
+ }, {
117
+ databaseName: config.databaseName,
118
+ coordinatorDbName: config.coordinatorDbName
119
+ });
120
+ closePlatform = platform.close;
121
+ const database = await createEventSourcedDB({
122
+ persistence: platform.persistence,
123
+ createCollection: modules.createCollection,
124
+ persistedCollectionOptions: modules.persistedCollectionOptions,
125
+ collections: config.collections,
126
+ sync: config.sync,
127
+ schemaVersion: config.schemaVersion,
128
+ debug: config.debug
129
+ });
130
+ activeDb = database;
131
+ return database;
132
+ };
133
+ const singleton = createLazySingleton(factory, {
134
+ guard: assertBrowserEnvironment,
135
+ notInitializedMessage: "Event-sourced DB is not initialized. Call ensureDb() before using db."
136
+ });
137
+ const close = async () => {
138
+ if (activeDb) {
139
+ activeDb.dispose();
140
+ activeDb = null;
141
+ }
142
+ if (closePlatform) {
143
+ await closePlatform();
144
+ closePlatform = null;
145
+ }
146
+ singleton.reset();
147
+ };
148
+ return {
149
+ ensureDb: singleton.ensure,
150
+ db: singleton.proxy,
151
+ close
152
+ };
153
+ }
154
+ function assertBrowserEnvironment() {
155
+ if (typeof window === "undefined") throw new Error("Event-sourced DB (browser) is only available in a browser environment.");
156
+ }
157
+ //#endregion
158
+ export { createBrowserEventSourcedDB, createBrowserPlatform };
106
159
 
107
160
  //# sourceMappingURL=browser.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.mjs","names":[],"sources":["../src/platforms/browser-wa-sqlite-driver.ts","../src/platforms/browser.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"],"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};\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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"}