boundlessdb 0.7.0 → 0.9.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/README.md +97 -135
  3. package/dist/browser.d.ts +3 -3
  4. package/dist/browser.d.ts.map +1 -1
  5. package/dist/browser.js +2 -2
  6. package/dist/browser.js.map +1 -1
  7. package/dist/event-store.d.ts +3 -1
  8. package/dist/event-store.d.ts.map +1 -1
  9. package/dist/event-store.js +52 -14
  10. package/dist/event-store.js.map +1 -1
  11. package/dist/index.d.ts +3 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/query-builder.d.ts +28 -15
  16. package/dist/query-builder.d.ts.map +1 -1
  17. package/dist/query-builder.js +48 -17
  18. package/dist/query-builder.js.map +1 -1
  19. package/dist/storage/memory.d.ts.map +1 -1
  20. package/dist/storage/memory.js +14 -1
  21. package/dist/storage/memory.js.map +1 -1
  22. package/dist/storage/postgres.d.ts +19 -1
  23. package/dist/storage/postgres.d.ts.map +1 -1
  24. package/dist/storage/postgres.js +160 -6
  25. package/dist/storage/postgres.js.map +1 -1
  26. package/dist/storage/sqlite.d.ts.map +1 -1
  27. package/dist/storage/sqlite.js +153 -3
  28. package/dist/storage/sqlite.js.map +1 -1
  29. package/dist/storage/sqljs.d.ts.map +1 -1
  30. package/dist/storage/sqljs.js +87 -3
  31. package/dist/storage/sqljs.js.map +1 -1
  32. package/dist/types.d.ts +48 -3
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/types.js +16 -4
  35. package/dist/types.js.map +1 -1
  36. package/package.json +1 -1
  37. package/dist/event-store.browser.d.ts +0 -82
  38. package/dist/event-store.browser.d.ts.map +0 -1
  39. package/dist/event-store.browser.js +0 -336
  40. package/dist/event-store.browser.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,135 @@
2
2
 
3
3
  All notable changes to BoundlessDB will be documented in this file.
4
4
 
5
+ ## [0.9.0] - 2026-03-01
6
+
7
+ ### Breaking Changes
8
+
9
+ #### Renamed: `withKey()` → `andKey()`
10
+
11
+ The chaining method for AND key constraints has been renamed to make the semantics immediately clear:
12
+
13
+ ```typescript
14
+ // Before (v0.8.0)
15
+ store.query()
16
+ .matchType('StudentSubscribed')
17
+ .withKey('course', 'cs101')
18
+ .withKey('student', 'alice')
19
+ .read();
20
+
21
+ // After (v0.9.0)
22
+ store.query()
23
+ .matchType('StudentSubscribed')
24
+ .andKey('course', 'cs101')
25
+ .andKey('student', 'alice')
26
+ .read();
27
+ ```
28
+
29
+ **Migration:** Find and replace `.withKey(` → `.andKey(` in your codebase.
30
+
31
+ ### Added
32
+
33
+ #### Key-Only Queries: `matchKey(key, value)`
34
+
35
+ Query events by key without specifying event types. This is the DCB-native way to query — by tags, not by event type.
36
+
37
+ ```typescript
38
+ // "Everything about course cs101" — no need to list event types!
39
+ const result = await store.query()
40
+ .matchKey('course', 'cs101')
41
+ .read();
42
+
43
+ // AND: "Alice's enrollment in cs101"
44
+ const result = await store.query()
45
+ .matchKey('course', 'cs101')
46
+ .andKey('student', 'alice')
47
+ .read();
48
+ ```
49
+
50
+ **Why this matters:** Previously, querying by key required listing every event type manually:
51
+
52
+ ```typescript
53
+ // Before: 8 event types just to query a cart 😱
54
+ const cartEventTypes = [
55
+ 'CartCreated', 'ItemAdded', 'ItemRemoved', 'ItemArchived',
56
+ 'CartSubmitted', 'CartCleared', 'CartPublished', 'CartPublicationFailed',
57
+ ];
58
+ const result = await store.read({
59
+ conditions: cartEventTypes.map(type => ({ type, key: 'cart', value: cartId })),
60
+ });
61
+
62
+ // After: one line ✨
63
+ const result = await store.query()
64
+ .matchKey('cart', cartId)
65
+ .read();
66
+ ```
67
+
68
+ Key-only queries work with `andKey()` for AND logic, `fromPosition()`, `limit()`, and `appendCondition`.
69
+
70
+ #### Multi-Type Queries: `matchType(...types)`
71
+
72
+ `matchType()` now accepts multiple types (variadic). Types within one call are OR:
73
+
74
+ ```typescript
75
+ // "All course lifecycle events for cs101"
76
+ const result = await store.query()
77
+ .matchType('CourseCreated', 'CourseCancelled')
78
+ .andKey('course', 'cs101')
79
+ .read();
80
+ ```
81
+
82
+ This maps directly to the DCB spec's QueryItem, where a single item can have multiple types.
83
+
84
+ ### Query API Summary
85
+
86
+ | Method | Role |
87
+ |---|---|
88
+ | `matchKey(key, value)` | Start condition by key (any event type). |
89
+ | `matchType(...types)` | Start condition by type(s). |
90
+ | `andKey(key, value)` | Add AND key constraint to last condition. |
91
+ | `matchTypeAndKey(type, key, value)` | Shorthand for `matchType(type).andKey(key, value)`. |
92
+
93
+ **Rules:**
94
+ - `matchType()` / `matchKey()` → starts a new condition (OR between conditions)
95
+ - `andKey()` → extends the last condition (AND within condition)
96
+
97
+ ### DCB Spec Mapping
98
+
99
+ | DCB Spec | BoundlessDB |
100
+ |---|---|
101
+ | QueryItem.types (OR within) | `matchType('A', 'B')` |
102
+ | QueryItem.tags (AND within) | `.andKey('key', 'value')` chain |
103
+ | Tag-only QueryItem | `matchKey('key', 'value')` |
104
+ | Multiple QueryItems (OR) | Multiple `matchType()` / `matchKey()` calls |
105
+
106
+ ### Tests
107
+
108
+ - **214 tests** (up from 174): +40 new tests covering key-only queries, multi-type queries, AND/OR combinations, appendCondition propagation, and error cases.
109
+
110
+ ### Documentation
111
+
112
+ - README updated with new API examples throughout
113
+ - `docs/sqlite-queries.md`: New sections for key-only, key-only AND, multi-type queries. Decision logic expanded to all 8 condition variants.
114
+ - Landing page: Updated highlight section and code examples
115
+ - Issue #83: Full API documentation with all query patterns
116
+
117
+ ---
118
+
119
+ ## [0.8.0] - 2026-02-28
120
+
121
+ ### Added
122
+
123
+ - **Exponential backoff with jitter** for PostgreSQL serialization retries. Default: 10 retries, 50ms base delay, full jitter. Configurable via `PostgresRetryOptions`.
124
+ ```typescript
125
+ new PostgresStorage(url, { maxRetries: 10, retryBaseMs: 50, retryJitter: true });
126
+ ```
127
+ - **`--conflicts` flag** for benchmark scripts. Tests conflict detection overhead and concurrent writers (10 parallel writers, same/different keys).
128
+ - **Aggressive conflict benchmarks**: 50 iterations × 10 writers × 5 events per writer (~5,500 events per run).
129
+
130
+ ### Fixed
131
+
132
+ - **PostgreSQL concurrent writers no longer crash** under high contention. Previously 3 retries without backoff caused failures with 10+ parallel writers.
133
+
5
134
  ## [0.7.0] - 2026-02-28
6
135
 
7
136
  ### Added
package/README.md CHANGED
@@ -84,26 +84,21 @@ event_keys: [pos:1, course, cs101], [pos:1, student, alice]
84
84
  ```
85
85
 
86
86
  ### 4️⃣ Query by Keys
87
- Find all events matching any combination of key conditions:
87
+ Find all events matching key conditions no need to list event types:
88
88
  ```typescript
89
- const result = await store.read({
90
- conditions: [
91
- { type: 'StudentSubscribed', key: 'course', value: 'cs101' }
92
- ]
93
- });
89
+ const result = await store.query()
90
+ .matchKey('course', 'cs101')
91
+ .read();
94
92
  // result.appendCondition captures: "I read all matching events up to position X"
95
93
  ```
96
94
 
97
95
  ## The DCB Pattern: Read → Decide → Write
98
96
 
99
97
  ```typescript
100
- // 1️⃣ READ — Query events and get an appendCondition
101
- const { events, appendCondition } = await store.read({
102
- conditions: [
103
- { type: 'CourseCreated', key: 'course', value: 'cs101' },
104
- { type: 'StudentSubscribed', key: 'course', value: 'cs101' },
105
- ]
106
- });
98
+ // 1️⃣ READ — Query by key and get an appendCondition
99
+ const { events, appendCondition } = await store.query<CourseEvent>()
100
+ .matchKey('course', 'cs101')
101
+ .read();
107
102
 
108
103
  // 2️⃣ DECIDE — Build state, run business logic
109
104
  const state = events.reduce(evolve, initialState);
@@ -154,12 +149,29 @@ if (result.conflict) {
154
149
  Build queries with a chainable API:
155
150
 
156
151
  ```typescript
152
+ // Key-only: everything about course cs101 (any event type!)
157
153
  const { events, appendCondition } = await store.query<CourseEvent>()
158
- .matchType('CourseCreated') // all events of type
159
- .matchTypeAndKey('StudentSubscribed', 'course', 'cs101') // type + key = value
160
- .withKey('student', 'alice') // AND: also match student key
161
- .fromPosition(100n) // start from position
162
- .limit(50) // limit results
154
+ .matchKey('course', 'cs101')
155
+ .read();
156
+
157
+ // Multi-key AND: Alice's enrollment in cs101
158
+ const enrollment = await store.query<CourseEvent>()
159
+ .matchKey('course', 'cs101')
160
+ .andKey('student', 'alice')
161
+ .read();
162
+
163
+ // Multi-type: course lifecycle events for cs101
164
+ const lifecycle = await store.query<CourseEvent>()
165
+ .matchType('CourseCreated', 'CourseCancelled')
166
+ .andKey('course', 'cs101')
167
+ .read();
168
+
169
+ // Type + key
170
+ const enrollments = await store.query<CourseEvent>()
171
+ .matchType('StudentSubscribed')
172
+ .andKey('course', 'cs101')
173
+ .fromPosition(100n)
174
+ .limit(50)
163
175
  .read();
164
176
  ```
165
177
 
@@ -167,14 +179,17 @@ const { events, appendCondition } = await store.query<CourseEvent>()
167
179
 
168
180
  | Method | Description |
169
181
  |--------|-------------|
170
- | `matchType(type)` | Match all events of type (unconstrained) |
171
- | `matchTypeAndKey(type, key, value)` | Match events of type where key=value |
172
- | `withKey(key, value)` | Add AND key to last condition (multi-key) |
182
+ | `matchKey(key, value)` | Match events by key, regardless of type. Starts new condition (OR). |
183
+ | `matchType(...types)` | Match events of one or more types. Starts new condition (OR). |
184
+ | `matchTypeAndKey(type, key, value)` | Shorthand for `matchType(type).andKey(key, value)` |
185
+ | `andKey(key, value)` | Add AND key constraint to last condition |
173
186
  | `fromPosition(bigint)` | Start reading from position |
174
187
  | `limit(number)` | Limit number of results |
175
188
  | `read()` | Execute query, returns `QueryResult` |
176
189
 
177
- The fluent API is equivalent to calling `store.read()` with conditions — use whichever style you prefer.
190
+ **Rules:**
191
+ - `matchType()` / `matchKey()` start a **new** condition (OR between conditions)
192
+ - `andKey()` **extends** the last condition (AND within condition)
178
193
 
179
194
  ## AppendCondition
180
195
 
@@ -191,7 +206,7 @@ interface AppendCondition {
191
206
 
192
207
  ```typescript
193
208
  const result = await store.query()
194
- .matchTypeAndKey('StudentSubscribed', 'course', 'cs101')
209
+ .matchKey('course', 'cs101')
195
210
  .read();
196
211
 
197
212
  // appendCondition = { failIfEventsMatch: [...], after: <last_position> }
@@ -251,51 +266,47 @@ await store.append(newEvents, null);
251
266
  Traditional streams give you ONE boundary. DCB lets you query ANY combination:
252
267
 
253
268
  ```typescript
254
- // "Has Alice already enrolled in CS101?"
255
- // Multi-key AND: must match BOTH keys on the SAME event
256
- const result = await store.query()
257
- .matchType('StudentSubscribed')
258
- .withKey('course', 'cs101')
259
- .withKey('student', 'alice')
260
- .read();
269
+ // Key-only: "Everything about course cs101"
270
+ store.query().matchKey('course', 'cs101').read()
271
+
272
+ // Multi-key AND: "Alice's enrollment in cs101"
273
+ store.query()
274
+ .matchKey('course', 'cs101')
275
+ .andKey('student', 'alice')
276
+ .read()
277
+
278
+ // Multi-type + key: "Course lifecycle events for cs101"
279
+ store.query()
280
+ .matchType('CourseCreated', 'CourseCancelled')
281
+ .andKey('course', 'cs101')
282
+ .read()
283
+
284
+ // OR: "All cancellations OR everything about Alice"
285
+ store.query()
286
+ .matchType('CourseCancelled') // condition 1
287
+ .matchKey('student', 'alice') // condition 2 (OR)
288
+ .read()
261
289
  ```
262
290
 
263
291
  ### AND vs OR
264
292
 
265
- - **`.withKey().withKey()`** = **AND** — the same event must have ALL specified keys
266
- - **Two separate conditions** = **OR** — events matching EITHER condition
293
+ - **`.andKey()`** = **AND** — extends the last condition (same event must match all keys)
294
+ - **`.matchType()` / `.matchKey()`** = **OR** — starts a new condition (events matching either)
267
295
 
268
296
  ```typescript
269
297
  // AND: Events where course='cs101' AND student='alice' (same event)
270
298
  store.query()
271
- .matchType('StudentSubscribed')
272
- .withKey('course', 'cs101')
273
- .withKey('student', 'alice')
299
+ .matchKey('course', 'cs101')
300
+ .andKey('student', 'alice')
274
301
  .read();
275
302
 
276
303
  // OR: Events where course='cs101' OR student='alice' (different events)
277
304
  store.query()
278
- .matchTypeAndKey('StudentSubscribed', 'course', 'cs101')
279
- .matchTypeAndKey('StudentSubscribed', 'student', 'alice')
305
+ .matchKey('course', 'cs101')
306
+ .matchKey('student', 'alice')
280
307
  .read();
281
308
  ```
282
309
 
283
- Multi-key conditions also work in the object syntax:
284
-
285
- ```typescript
286
- const result = await store.read({
287
- conditions: [
288
- {
289
- type: 'StudentSubscribed',
290
- keys: [
291
- { name: 'course', value: 'cs101' },
292
- { name: 'student', value: 'alice' }
293
- ]
294
- }
295
- ]
296
- });
297
- ```
298
-
299
310
  ## Config-based Key Extraction
300
311
 
301
312
  Keys are extracted from event payloads via configuration — events stay pure:
@@ -354,9 +365,10 @@ const consistency = {
354
365
  // Extracted keys: order="ORD-123", month="2026-02"
355
366
 
356
367
  // Query all orders from February 2026:
357
- const { events } = await store.read({
358
- conditions: [{ type: 'OrderPlaced', key: 'month', value: '2026-02' }]
359
- });
368
+ const { events } = await store.query()
369
+ .matchType('OrderPlaced')
370
+ .andKey('month', '2026-02')
371
+ .read();
360
372
  ```
361
373
 
362
374
  This is great for **Close the Books** patterns — query all events in a time period efficiently!
@@ -542,9 +554,9 @@ type ProductItemRemoved = Event<'ProductItemRemoved', {
542
554
  type CartEvents = ProductItemAdded | ProductItemRemoved;
543
555
 
544
556
  // Read with type safety
545
- const result = await store.read<CartEvents>({
546
- conditions: [{ type: 'ProductItemAdded', key: 'cart', value: 'cart-123' }]
547
- });
557
+ const result = await store.query<CartEvents>()
558
+ .matchKey('cart', 'cart-123')
559
+ .read();
548
560
 
549
561
  // TypeScript knows the event types!
550
562
  for (const event of result.events) {
@@ -556,90 +568,36 @@ for (const event of result.events) {
556
568
 
557
569
  ## Query Conditions
558
570
 
559
- Query conditions support both **constrained** (with key/value) and **unconstrained** (type-only) queries.
560
-
561
- ### Constrained Query
562
- Match events of a type where a specific key has a specific value:
563
-
564
- ```typescript
565
- // Get ProductItemAdded events where cart='cart-123'
566
- const result = await store.read({
567
- conditions: [
568
- { type: 'ProductItemAdded', key: 'cart', value: 'cart-123' }
569
- ]
570
- });
571
- ```
572
-
573
- ### Unconstrained Query
574
- Omit `key` and `value` to match **all events of a type**:
575
-
576
- ```typescript
577
- // Get ALL ProductItemAdded events (regardless of cart)
578
- const result = await store.read({
579
- conditions: [
580
- { type: 'ProductItemAdded' } // no key/value = match all
581
- ]
582
- });
583
- ```
584
-
585
- ### Mixed Conditions
586
- Combine constrained and unconstrained in one query (OR logic):
587
-
588
- ```typescript
589
- // "Give me the course definition + all enrollments for cs101"
590
- const result = await store.read({
591
- conditions: [
592
- { type: 'CourseCreated', key: 'course', value: 'cs101' },
593
- { type: 'StudentSubscribed', key: 'course', value: 'cs101' },
594
- { type: 'StudentUnsubscribed', key: 'course', value: 'cs101' },
595
- ]
596
- });
597
-
598
- // "All courses + only Alice's enrollments"
599
- const result = await store.read({
600
- conditions: [
601
- { type: 'CourseCreated' }, // unconstrained: ALL courses
602
- { type: 'StudentSubscribed', key: 'student', value: 'alice' } // constrained: only Alice
603
- ]
604
- });
605
- ```
606
-
607
- ### Same Type, Multiple Values
608
- Query multiple values of the same key:
571
+ The fluent query builder is the recommended API. For advanced use, conditions can also be passed directly to `store.read()`:
609
572
 
610
573
  ```typescript
611
- // Get ProductItemAdded for cart-1 OR cart-2
612
- const result = await store.read({
613
- conditions: [
614
- { type: 'ProductItemAdded', key: 'cart', value: 'cart-1' },
615
- { type: 'ProductItemAdded', key: 'cart', value: 'cart-2' }
616
- ]
617
- });
618
- ```
574
+ // Key-only: events with key, regardless of type
575
+ { keys: [{ name: 'course', value: 'cs101' }] }
619
576
 
620
- ### Type Safety
621
- With TypeScript, conditions are type-safe three forms:
577
+ // Type-only: all events of type
578
+ { type: 'CourseCreated' }
622
579
 
623
- ```typescript
624
- // ✅ Unconstrained (type only)
625
- { type: 'ProductItemAdded' }
626
-
627
- // ✅ Single key
580
+ // Type + single key
628
581
  { type: 'ProductItemAdded', key: 'cart', value: 'cart-123' }
629
582
 
630
- // Multi-key AND
583
+ // Type + multi-key AND
631
584
  { type: 'StudentSubscribed', keys: [
632
585
  { name: 'course', value: 'cs101' },
633
586
  { name: 'student', value: 'alice' }
634
587
  ]}
588
+
589
+ // Multi-type
590
+ { types: ['CourseCreated', 'CourseCancelled'] }
591
+
592
+ // Multi-type + keys
593
+ { types: ['CourseCreated', 'CourseCancelled'], keys: [{ name: 'course', value: 'cs101' }] }
635
594
  ```
636
595
 
637
- ### Empty Conditions
638
- Empty conditions returns **all events** in the store:
596
+ ### All Events
639
597
 
640
598
  ```typescript
641
599
  // Get ALL events (useful for admin/debug/export)
642
- const result = await store.read({ conditions: [] });
600
+ const result = await store.all().read();
643
601
  ```
644
602
 
645
603
  ## API Reference
@@ -659,9 +617,12 @@ Fluent query builder:
659
617
 
660
618
  ```typescript
661
619
  const result = await store.query<CourseEvent>()
662
- .matchType('CourseCreated') // unconstrained (type only)
663
- .matchTypeAndKey('StudentSubscribed', 'course', 'cs101') // type + single key
664
- .withKey('student', 'alice') // AND: add key to last condition
620
+ .matchKey('course', 'cs101') // key-only (any event type)
621
+ .read();
622
+
623
+ const result = await store.query<CourseEvent>()
624
+ .matchType('CourseCreated', 'CourseCancelled') // multi-type
625
+ .andKey('course', 'cs101') // + key constraint
665
626
  .fromPosition(100n) // start from position
666
627
  .limit(50) // limit results
667
628
  .read(); // execute, returns QueryResult
@@ -669,9 +630,10 @@ const result = await store.query<CourseEvent>()
669
630
 
670
631
  | Method | Description |
671
632
  |--------|-------------|
672
- | `matchType(type)` | Match all events of type (unconstrained) |
673
- | `matchTypeAndKey(type, key, value)` | Match events of type where key=value |
674
- | `withKey(key, value)` | Add AND key to last condition (multi-key) |
633
+ | `matchKey(key, value)` | Match events by key, any type. Starts new condition (OR). |
634
+ | `matchType(...types)` | Match events of type(s). Starts new condition (OR). |
635
+ | `matchTypeAndKey(type, key, value)` | Shorthand for `matchType(type).andKey(key, value)` |
636
+ | `andKey(key, value)` | Add AND key constraint to last condition |
675
637
  | `fromPosition(bigint)` | Start reading from position |
676
638
  | `limit(number)` | Limit number of results |
677
639
  | `read()` | Execute query, returns `QueryResult` |
@@ -796,4 +758,4 @@ For detailed SQL query plans and optimization notes, see [docs/sqlite-queries.md
796
758
 
797
759
  ---
798
760
 
799
- Built with ❤️ for [Event Sourcing](https://www.eventstore.com/event-sourcing)
761
+ Built with ❤️ for Event Sourcing
package/dist/browser.d.ts CHANGED
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * This file exports everything needed for browser usage with sql.js storage.
5
5
  */
6
- export type { Event, EventWithMetadata, StoredEvent, Query, QueryCondition, UnconstrainedCondition, ConstrainedCondition, MultiKeyConstrainedCondition, ConsistencyConfig, ConsistencyKeyDef, EventTypeConfig, ExtractedKey, AppendResult, ConflictResult, AppendCondition, EventStoreOptions, } from './types.js';
7
- export { QueryResult, isConflict, isConstrainedCondition, isMultiKeyCondition, normalizeCondition, hasKeys } from './types.js';
8
- export { EventStore, createEventStore, type EventStoreConfig } from './event-store.browser.js';
6
+ export type { Event, EventWithMetadata, StoredEvent, Query, QueryCondition, UnconstrainedCondition, ConstrainedCondition, MultiKeyConstrainedCondition, MultiTypeCondition, MultiTypeConstrainedCondition, KeyOnlyCondition, ConsistencyConfig, ConsistencyKeyDef, EventTypeConfig, ExtractedKey, AppendResult, ConflictResult, AppendCondition, EventStoreOptions, } from './types.js';
7
+ export { QueryResult, isConflict, isConstrainedCondition, isMultiKeyCondition, isMultiTypeCondition, isMultiTypeConstrainedCondition, isKeyOnlyCondition, normalizeCondition, hasKeys } from './types.js';
8
+ export { EventStore, createEventStore, type EventStoreConfig } from './event-store.js';
9
9
  export type { EventStorage, EventToStore } from './storage/interface.js';
10
10
  export { InMemoryStorage } from './storage/memory.js';
11
11
  export { SqlJsStorage, type SqlJsStorageOptions } from './storage/sqljs.js';
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EAEV,KAAK,EACL,iBAAiB,EACjB,WAAW,EAEX,KAAK,EACL,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,4BAA4B,EAE5B,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,YAAY,EAEZ,YAAY,EACZ,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG/H,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG/F,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG5E,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EAEV,KAAK,EACL,iBAAiB,EACjB,WAAW,EAEX,KAAK,EACL,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,4BAA4B,EAC5B,kBAAkB,EAClB,6BAA6B,EAC7B,gBAAgB,EAEhB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,YAAY,EAEZ,YAAY,EACZ,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1M,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGvF,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG5E,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/browser.js CHANGED
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * This file exports everything needed for browser usage with sql.js storage.
5
5
  */
6
- export { QueryResult, isConflict, isConstrainedCondition, isMultiKeyCondition, normalizeCondition, hasKeys } from './types.js';
6
+ export { QueryResult, isConflict, isConstrainedCondition, isMultiKeyCondition, isMultiTypeCondition, isMultiTypeConstrainedCondition, isKeyOnlyCondition, normalizeCondition, hasKeys } from './types.js';
7
7
  // Event Store (browser version)
8
- export { EventStore, createEventStore } from './event-store.browser.js';
8
+ export { EventStore, createEventStore } from './event-store.js';
9
9
  export { InMemoryStorage } from './storage/memory.js';
10
10
  export { SqlJsStorage } from './storage/sqljs.js';
11
11
  // Config
@@ -1 +1 @@
1
- {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0BH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE/H,gCAAgC;AAChC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAyB,MAAM,0BAA0B,CAAC;AAI/F,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,YAAY,EAA4B,MAAM,oBAAoB,CAAC;AAE5E,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6BH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1M,gCAAgC;AAChC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAyB,MAAM,kBAAkB,CAAC;AAIvF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,YAAY,EAA4B,MAAM,oBAAoB,CAAC;AAE5E,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -19,7 +19,9 @@ export declare class EventStore {
19
19
  private readonly config;
20
20
  constructor(options: EventStoreConfig);
21
21
  /**
22
- * Check if config has changed since last run, reindex if needed
22
+ * Check if config has changed since last run, reindex if needed.
23
+ * Works with any storage that implements getConfigHash/setConfigHash.
24
+ * Handles both sync (SqliteStorage) and async (SqlJsStorage) methods.
23
25
  */
24
26
  private checkAndReindexIfNeeded;
25
27
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"event-store.d.ts","sourceRoot":"","sources":["../src/event-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EACL,WAAW,EAKX,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EAInB,KAAK,KAAK,EACV,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,KAAK,EAGX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAsB,MAAM,oBAAoB,CAAC;AA2BtE,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,OAAO,EAAE,YAAY,CAAC;CACvB;AAED;;;;;GAKG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;gBAE/B,OAAO,EAAE,gBAAgB;IAYrC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAqB/B;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC;IAIjD;;;;;;;;;;;;;;;;;;OAkBG;IACH,GAAG,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC;IAI/C;;;;;;;;;;;;;;;;;;OAkBG;IACG,IAAI,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAqB1E;;;;;;;;;;;;;;;;;;;OAmBG;IACG,MAAM,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAClC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAC9B,SAAS,EAAE,eAAe,GAAG,IAAI,GAChC,OAAO,CAAC,YAAY,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IA2D5C;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAkCjC;;OAEG;IACH,UAAU,IAAI,YAAY;IAI1B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,UAAU,CAEtE"}
1
+ {"version":3,"file":"event-store.d.ts","sourceRoot":"","sources":["../src/event-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,WAAW,EAKX,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EAInB,KAAK,KAAK,EACV,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,KAAK,EAGX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAsB,MAAM,oBAAoB,CAAC;AAmDtE,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,OAAO,EAAE,YAAY,CAAC;CACvB;AAED;;;;;GAKG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;gBAE/B,OAAO,EAAE,gBAAgB;IAYrC;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAoC/B;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC;IAIjD;;;;;;;;;;;;;;;;;;OAkBG;IACH,GAAG,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC;IAI/C;;;;;;;;;;;;;;;;;;OAkBG;IACG,IAAI,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAqB1E;;;;;;;;;;;;;;;;;;;OAmBG;IACG,MAAM,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAClC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAC9B,SAAS,EAAE,eAAe,GAAG,IAAI,GAChC,OAAO,CAAC,YAAY,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IA2D5C;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAmCjC;;OAEG;IACH,UAAU,IAAI,YAAY;IAI1B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,UAAU,CAEtE"}
@@ -1,12 +1,28 @@
1
1
  /**
2
2
  * Main EventStore class
3
3
  */
4
- import { randomUUID, createHash } from 'node:crypto';
5
4
  import { KeyExtractor } from './config/extractor.js';
6
5
  import { validateConfig } from './config/validator.js';
7
- import { SqliteStorage } from './storage/sqlite.js';
8
6
  import { QueryResult, normalizeCondition, hasKeys, } from './types.js';
9
7
  import { QueryBuilder } from './query-builder.js';
8
+ /**
9
+ * Generate UUID with fallback for environments without node:crypto
10
+ */
11
+ function generateUUID() {
12
+ try {
13
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
14
+ return crypto.randomUUID();
15
+ }
16
+ }
17
+ catch {
18
+ // crypto.randomUUID might throw in insecure contexts
19
+ }
20
+ // Fallback: Math.random-based UUID v4
21
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
22
+ const r = (Math.random() * 16) | 0;
23
+ return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
24
+ });
25
+ }
10
26
  /**
11
27
  * Recursively sort object keys for deterministic JSON
12
28
  */
@@ -24,11 +40,17 @@ function sortObjectKeys(obj) {
24
40
  return sorted;
25
41
  }
26
42
  /**
27
- * Compute SHA256 hash of ConsistencyConfig
43
+ * Compute a deterministic hash of ConsistencyConfig (FNV-1a).
44
+ * No crypto dependency — just needs to detect config changes.
28
45
  */
29
46
  function hashConfig(config) {
30
47
  const normalized = JSON.stringify(sortObjectKeys(config));
31
- return createHash('sha256').update(normalized).digest('hex');
48
+ let hash = 0x811c9dc5; // FNV offset basis
49
+ for (let i = 0; i < normalized.length; i++) {
50
+ hash ^= normalized.charCodeAt(i);
51
+ hash = Math.imul(hash, 0x01000193); // FNV prime
52
+ }
53
+ return (hash >>> 0).toString(16).padStart(8, '0');
32
54
  }
33
55
  /**
34
56
  * DCB-native Event Store
@@ -46,25 +68,40 @@ export class EventStore {
46
68
  this.storage = options.storage;
47
69
  this.config = options.consistency;
48
70
  this.keyExtractor = new KeyExtractor(this.config);
49
- // Check config hash and reindex if needed (SqliteStorage only)
71
+ // Check config hash and throw on mismatch
50
72
  this.checkAndReindexIfNeeded();
51
73
  }
52
74
  /**
53
- * Check if config has changed since last run, reindex if needed
75
+ * Check if config has changed since last run, reindex if needed.
76
+ * Works with any storage that implements getConfigHash/setConfigHash.
77
+ * Handles both sync (SqliteStorage) and async (SqlJsStorage) methods.
54
78
  */
55
79
  checkAndReindexIfNeeded() {
56
- // Only works with SqliteStorage (has metadata table)
57
- if (!(this.storage instanceof SqliteStorage)) {
80
+ const storage = this.storage;
81
+ if (typeof storage.getConfigHash !== 'function' || typeof storage.setConfigHash !== 'function') {
58
82
  return;
59
83
  }
60
84
  const currentHash = hashConfig(this.config);
61
- const storedHash = this.storage.getConfigHash();
85
+ const result = storage.getConfigHash();
86
+ // Handle async storage (SqlJsStorage returns Promises)
87
+ if (result && typeof result.then === 'function') {
88
+ result.then((storedHash) => {
89
+ if (storedHash === null) {
90
+ storage.setConfigHash(currentHash);
91
+ }
92
+ else if (storedHash !== currentHash) {
93
+ console.error(`Config hash mismatch (stored: ${storedHash}, current: ${currentHash}). ` +
94
+ `Run the reindex script before starting the application.`);
95
+ }
96
+ });
97
+ return;
98
+ }
99
+ // Sync storage (SqliteStorage)
100
+ const storedHash = result;
62
101
  if (storedHash === null) {
63
- // First run — just store the hash
64
- this.storage.setConfigHash(currentHash);
102
+ storage.setConfigHash(currentHash);
65
103
  }
66
104
  else if (storedHash !== currentHash) {
67
- // Config changed — throw error, require explicit reindex via script
68
105
  throw new Error(`Config hash mismatch (stored: ${storedHash}, current: ${currentHash}). ` +
69
106
  `Run the reindex script before starting the application.`);
70
107
  }
@@ -174,7 +211,7 @@ export class EventStore {
174
211
  // Prepare events for storage
175
212
  const now = new Date();
176
213
  const eventsToStore = events.map(event => ({
177
- id: randomUUID(),
214
+ id: generateUUID(),
178
215
  type: event.type,
179
216
  data: event.data,
180
217
  metadata: event.metadata,
@@ -220,7 +257,8 @@ export class EventStore {
220
257
  if (hasKeys(normalized)) {
221
258
  // Build a dedup key from all keys
222
259
  const keysStr = normalized.keys.map(k => `${k.name}:${k.value}`).sort().join('|');
223
- const dedupKey = `${normalized.type}:${keysStr}`;
260
+ const typeStr = 'type' in normalized ? normalized.type : ('types' in normalized ? normalized.types.join('|') : '*');
261
+ const dedupKey = `${typeStr}:${keysStr}`;
224
262
  conditions.set(dedupKey, normalized);
225
263
  }
226
264
  }