bun-sqlite-for-rxdb 1.0.1

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 (68) hide show
  1. package/.serena/project.yml +84 -0
  2. package/CHANGELOG.md +300 -0
  3. package/LICENSE +21 -0
  4. package/README.md +87 -0
  5. package/ROADMAP.md +532 -0
  6. package/benchmarks/benchmark.ts +145 -0
  7. package/benchmarks/case-insensitive-10runs.ts +156 -0
  8. package/benchmarks/fts5-1m-scale.ts +126 -0
  9. package/benchmarks/fts5-before-after.ts +104 -0
  10. package/benchmarks/indexed-benchmark.ts +141 -0
  11. package/benchmarks/new-operators-benchmark.ts +140 -0
  12. package/benchmarks/query-builder-benchmark.ts +88 -0
  13. package/benchmarks/query-builder-consistency.ts +109 -0
  14. package/benchmarks/raw-better-sqlite3-10m.ts +85 -0
  15. package/benchmarks/raw-better-sqlite3.ts +86 -0
  16. package/benchmarks/raw-bun-sqlite-10m.ts +85 -0
  17. package/benchmarks/raw-bun-sqlite.ts +86 -0
  18. package/benchmarks/regex-10runs-all.ts +216 -0
  19. package/benchmarks/regex-comparison-benchmark.ts +161 -0
  20. package/benchmarks/regex-real-comparison.ts +213 -0
  21. package/benchmarks/run-10x.sh +19 -0
  22. package/benchmarks/smart-regex-benchmark.ts +148 -0
  23. package/benchmarks/sql-vs-mingo-benchmark.ts +210 -0
  24. package/benchmarks/sql-vs-mingo-comparison.ts +175 -0
  25. package/benchmarks/text-vs-jsonb.ts +167 -0
  26. package/benchmarks/wal-benchmark.ts +112 -0
  27. package/docs/architectural-patterns.md +1336 -0
  28. package/docs/id1-testsuite-journey.md +839 -0
  29. package/docs/official-test-suite-setup.md +393 -0
  30. package/nul +0 -0
  31. package/package.json +44 -0
  32. package/src/changestream.test.ts +182 -0
  33. package/src/cleanup.test.ts +110 -0
  34. package/src/collection-isolation.test.ts +74 -0
  35. package/src/connection-pool.test.ts +102 -0
  36. package/src/connection-pool.ts +38 -0
  37. package/src/findDocumentsById.test.ts +122 -0
  38. package/src/index.ts +2 -0
  39. package/src/instance.ts +382 -0
  40. package/src/multi-instance-events.test.ts +204 -0
  41. package/src/query/and-operator.test.ts +39 -0
  42. package/src/query/builder.test.ts +96 -0
  43. package/src/query/builder.ts +154 -0
  44. package/src/query/elemMatch-operator.test.ts +24 -0
  45. package/src/query/exists-operator.test.ts +28 -0
  46. package/src/query/in-operators.test.ts +54 -0
  47. package/src/query/mod-operator.test.ts +22 -0
  48. package/src/query/nested-query.test.ts +198 -0
  49. package/src/query/not-operators.test.ts +49 -0
  50. package/src/query/operators.test.ts +70 -0
  51. package/src/query/operators.ts +185 -0
  52. package/src/query/or-operator.test.ts +68 -0
  53. package/src/query/regex-escaping-regression.test.ts +43 -0
  54. package/src/query/regex-operator.test.ts +44 -0
  55. package/src/query/schema-mapper.ts +27 -0
  56. package/src/query/size-operator.test.ts +22 -0
  57. package/src/query/smart-regex.ts +52 -0
  58. package/src/query/type-operator.test.ts +37 -0
  59. package/src/query-cache.test.ts +286 -0
  60. package/src/rxdb-helpers.test.ts +348 -0
  61. package/src/rxdb-helpers.ts +262 -0
  62. package/src/schema-version-isolation.test.ts +126 -0
  63. package/src/statement-manager.ts +69 -0
  64. package/src/storage.test.ts +589 -0
  65. package/src/storage.ts +21 -0
  66. package/src/types.ts +14 -0
  67. package/test/rxdb-test-suite.ts +27 -0
  68. package/tsconfig.json +31 -0
package/ROADMAP.md ADDED
@@ -0,0 +1,532 @@
1
+ # bun-sqlite-for-rxdb Roadmap
2
+
3
+ > **Status:** Phase 2 Complete āœ… | Phase 3 Complete āœ…
4
+ > **Last Updated:** 2026-02-23
5
+
6
+ ---
7
+
8
+ ## šŸŽÆ Vision
9
+
10
+ Build the **fastest RxDB storage adapter** by leveraging Bun's native SQLite (3-6x faster than better-sqlite3).
11
+
12
+ **Key Principles:**
13
+ - Start simple, iterate incrementally (Linus approach)
14
+ - Measure before optimizing
15
+ - Ship working code, not perfect code
16
+ - Test-driven development
17
+
18
+ ---
19
+
20
+ ## āœ… Phase 1: Minimal Working Core (COMPLETE)
21
+
22
+ **Goal:** Prove the concept works
23
+
24
+ **Delivered:**
25
+ - āœ… RxStorage adapter for bun:sqlite
26
+ - āœ… Atomic transactions (bun:sqlite transaction API)
27
+ - āœ… Core methods: bulkWrite, query, findById, count, cleanup
28
+ - āœ… Reactive changeStream with RxJS
29
+ - āœ… In-memory Mango query filtering (simple but functional)
30
+ - āœ… 8/8 tests passing
31
+ - āœ… Supports RxDB v16 and v17 beta
32
+ - āœ… MIT licensed
33
+
34
+ **Performance:**
35
+ - āœ… Transactions: Atomic (all-or-nothing)
36
+ - āš ļø Queries: O(n) — fetches all, filters in JS (slow for 10k+ docs)
37
+
38
+ **What We Learned:**
39
+ - bun:sqlite API is nearly identical to better-sqlite3 āœ…
40
+ - Transaction wrapper works perfectly āœ…
41
+ - In-memory filtering is simple but not scalable āš ļø
42
+
43
+ ---
44
+
45
+ ## 🚧 Phase 2: Query Builder & Production Hardening (COMPLETE āœ…)
46
+
47
+ **Goal:** 10-100x query speedup + production-ready features
48
+
49
+ ### **Phase 2.1: Basic Operators (COMPLETE āœ…)**
50
+
51
+ **Architecture:** Functional Core, Imperative Shell
52
+ - āœ… Pure functions for operator translation (testable)
53
+ - āœ… Schema mapper for column info (DRY)
54
+ - āœ… Query builder for composition (scalable)
55
+ - āœ… Instance for orchestration (thin layer)
56
+
57
+ **Delivered:**
58
+ - āœ… `src/query/operators.ts` - 6 operators ($eq, $ne, $gt, $gte, $lt, $lte)
59
+ - āœ… `src/query/schema-mapper.ts` - Column mapping for _deleted, _meta.lwt, _rev, primaryKey
60
+ - āœ… `src/query/builder.ts` - WHERE clause generation with fallback
61
+ - āœ… Updated `src/instance.ts` - Uses SQL WHERE clauses, falls back to in-memory
62
+ - āœ… 27/27 tests passing (19 storage + 6 operators + 10 builder + 4 schema-mapper)
63
+ - āœ… TypeScript: 0 errors, 0 `any` types (properly typed infrastructure)
64
+ - āœ… Benchmark script created
65
+
66
+ **Type Safety Achievement:**
67
+ - āœ… Removed ALL 32 instances of `any` and `as any`
68
+ - āœ… Proper RxDB type hierarchy (RxDocumentData<RxDocType>)
69
+ - āœ… Research-driven approach (Lisa + Vivian agents)
70
+ - āœ… No bandaids - proper types throughout
71
+
72
+ **Performance:**
73
+ - āœ… Queries: Use SQL WHERE clauses with indexes
74
+ - āœ… Fallback: In-memory filtering if WHERE fails
75
+ - ā³ Benchmark: Not yet measured (script ready)
76
+
77
+ **Total Effort:** 8 hours (4h planned + 4h type safety)
78
+ **Status:** COMPLETE āœ…
79
+
80
+ ---
81
+
82
+ ### **Phase 2.2: WAL Mode (COMPLETE āœ…)**
83
+
84
+ **Delivered:**
85
+ ```typescript
86
+ // src/instance.ts line 55
87
+ this.db.run("PRAGMA journal_mode = WAL");
88
+ ```
89
+
90
+ **Impact:** 3-6x write speedup, better concurrency
91
+
92
+ **Status:** āœ… COMPLETE
93
+
94
+ ---
95
+
96
+ ### **Phase 2.3: JSONB Storage (COMPLETE āœ…)**
97
+
98
+ **Delivered:**
99
+ ```typescript
100
+ // CREATE TABLE with BLOB column
101
+ CREATE TABLE users (id TEXT PRIMARY KEY, data BLOB NOT NULL);
102
+
103
+ // INSERT with jsonb() function
104
+ INSERT INTO users (id, data) VALUES (?, jsonb(?));
105
+
106
+ // SELECT with json() function
107
+ SELECT json(data) as data FROM users WHERE id = ?;
108
+ ```
109
+
110
+ **Benchmark Results** (`benchmarks/text-vs-jsonb.ts`):
111
+ ```
112
+ 1M documents, 15 runs each:
113
+ - Simple query: 1.04x faster (481ms → 464ms)
114
+ - Complex query: 1.57x faster (657ms → 418ms) šŸ”„
115
+ - Read + parse: 1.20x faster (2.37ms → 1.98ms)
116
+ ```
117
+
118
+ **Impact:** 1.57x faster complex queries, 1.20x faster reads
119
+
120
+ **Status:** āœ… COMPLETE (implemented as default storage format)
121
+
122
+ ---
123
+
124
+ ### **Phase 2.4: Conflict Detection (COMPLETE āœ…)**
125
+
126
+ **Delivered:**
127
+ ```typescript
128
+ // src/instance.ts line 108
129
+ status: 409, // Proper RxDB conflict handling
130
+ ```
131
+
132
+ **Impact:** Required for replication support
133
+
134
+ **Status:** āœ… COMPLETE
135
+
136
+ ---
137
+
138
+ ### **Phase 2.5: Query Builder Caching (COMPLETE āœ…)**
139
+
140
+ **Goal:** Cache query builders by schema hash for faster repeated queries
141
+
142
+ **What We Did:**
143
+ 1. āœ… Implemented LRU cache with canonical key generation (fast-stable-stringify)
144
+ 2. āœ… True LRU eviction (delete+re-insert on access)
145
+ 3. āœ… 500 entry limit with FIFO eviction
146
+ 4. āœ… Zero dependencies except fast-stable-stringify (5KB)
147
+ 5. āœ… Created 13 edge case tests (object key order, cache thrashing, etc.)
148
+
149
+ **Performance:**
150
+ - āœ… 4.8-22.6x speedup for cached queries
151
+ - āœ… High-frequency: 565K-808K queries/sec
152
+ - āœ… Memory stress: 1000 unique queries handled correctly
153
+
154
+ **Status:** āœ… COMPLETE
155
+
156
+ ---
157
+
158
+ ## šŸ“Š Phase 3: Validation & Benchmarking (COMPLETE āœ…)
159
+
160
+ **Goal:** Prove correctness with official tests, then measure performance
161
+
162
+ **Philosophy:** Trust the official test suite. Don't reinvent the wheel.
163
+
164
+ ### **Phase 3.1: RxDB Official Test Suite (COMPLETE āœ…)**
165
+
166
+ **Why:** Official validation proves our adapter works correctly. Period.
167
+
168
+ **What We Did:**
169
+ 1. āœ… Ran official test suite: `DEFAULT_STORAGE=custom bun run mocha test_tmp/unit/rx-storage-implementations.test.js`
170
+ 2. āœ… Fixed statement lifecycle issues:
171
+ - Switched from db.prepare() to db.query() for static SQL (caching)
172
+ - Used db.prepare() + finalize() for dynamic SQL (no cache pollution)
173
+ - Created StatementManager abstraction layer for automatic cleanup
174
+ 3. āœ… Implemented connection pooling with reference counting
175
+ 4. āœ… Switched to RxDB's official `addRxStorageMultiInstanceSupport()`
176
+ 5. āœ… Fixed composite primary key handling
177
+ 6. āœ… Rewrote multi-instance tests to use RxDatabase (proper integration level)
178
+ 7. āœ… Added low-level changeStream tests for OUR code only
179
+ 8. āœ… Added Bun test suite compatibility (node:sqlite import fix + test globals)
180
+
181
+ **Test Results:**
182
+ ```
183
+ Local Tests: 120/120 pass (100%) āœ…
184
+ Official RxDB Tests (Mocha through Bun): 112/112 pass (100%) āœ…
185
+ Total: 232/232 tests pass (100%) šŸŽ‰
186
+ ```
187
+
188
+ **Key Findings:**
189
+ - db.query() caches statements (max 20) - good for static SQL
190
+ - db.prepare() requires manual finalize() - good for dynamic SQL
191
+ - StatementManager abstraction eliminates manual try-finally boilerplate
192
+ - Connection pooling is REQUIRED for multi-instance support (not optional)
193
+ - RxDB's official multi-instance support handles BroadcastChannel correctly
194
+ - Test at the right level: RxDatabase for integration, storage instances for low-level
195
+ - Mocha through Bun: 100% compatibility, native bun test: 98.2%
196
+
197
+ **Effort:** 16 hours (investigation + implementation + debugging + test rewrites)
198
+
199
+ **Status:** āœ… COMPLETE (2026-02-23)
200
+
201
+ ---
202
+
203
+ ### **Phase 3.2: Performance Benchmarks (PRIORITY 2)**
204
+
205
+ **Why:** After correctness is proven, measure and document performance gains.
206
+
207
+ **Tasks:**
208
+ 1. Benchmark vs pe-sqlite-for-rxdb (better-sqlite3)
209
+ 2. Measure write throughput (docs/sec)
210
+ 3. Measure query latency at scale (1k, 10k, 100k docs)
211
+ 4. Document performance gains in README
212
+ 5. Create performance comparison charts
213
+
214
+ **Expected outcome:** Documented proof of performance claims
215
+
216
+ **Effort:** 4 hours
217
+
218
+ **What We Did:**
219
+ 1. āœ… Created raw database benchmarks (bun:sqlite vs better-sqlite3)
220
+ 2. āœ… Tested with WAL + PRAGMA synchronous = 1
221
+ 3. āœ… Ran 1M document benchmarks (2 runs for consistency)
222
+ 4. āœ… Updated README with performance results
223
+
224
+ **Performance:**
225
+ - āœ… Bun:sqlite 1.06-1.68x faster than better-sqlite3
226
+ - āœ… WAL + PRAGMA enabled by default in production code
227
+
228
+ **Status:** āœ… COMPLETE
229
+
230
+ ---
231
+
232
+ ### **Phase 3.3: Custom Tests (OPTIONAL - YAGNI)**
233
+
234
+ **Why:** Only if users report specific edge cases not covered by official suite.
235
+
236
+ **Status:** āŒ SKIPPED (not needed - official suite is comprehensive)
237
+
238
+ ---
239
+
240
+ ---
241
+
242
+ ## šŸ”® Phase 4: v1.0 Release Preparation (COMPLETE āœ…)
243
+
244
+ **Goal:** Ship production-ready v1.0 with attachments support
245
+
246
+ **Prerequisites:** āœ… Phase 3 complete (246/246 tests passing)
247
+
248
+ **Current Status:** Production-ready adapter with full attachments support
249
+
250
+ **Research Complete (2026-02-23):**
251
+ - āœ… 4 Lisa agents examined: Dexie, Memory, SQLite/MongoDB official storages
252
+ - āœ… 1 Vivian agent researched: Industry patterns (PostgreSQL, IndexedDB, PouchDB)
253
+ - āœ… Synthesis complete: Minimal correct implementation identified
254
+
255
+ **v1.0 Requirements (ALL COMPLETE):**
256
+ 1. āœ… **Operators** - DONE in v0.4.0 (18 operators)
257
+ 2. āœ… **Attachments** - DONE (4 tests passing, getAttachmentData implemented)
258
+ 3. āœ… **Refactor bulkWrite** - DONE (uses categorizeBulkWriteRows() helper)
259
+
260
+ **Post-v1.0 (Future Enhancements):**
261
+ - Query normalization helpers (`normalizeMangoQuery`, `prepareQuery`) for better cache hit rate
262
+ - Schema migrations (user_version pragma) - needs design, not storage-level
263
+ - Query plan hints (EXPLAIN QUERY PLAN) - optimization, not critical
264
+ - Custom indexes - advanced feature, defer until requested
265
+ - Attachment deduplication by hash - industry pattern, adds complexity
266
+
267
+ **Status:** āœ… COMPLETE (2026-02-23)
268
+
269
+ ---
270
+
271
+ ### **RxDB Helper Functions (v1.0 Implementation)**
272
+
273
+ **Source:** Research from 6 Lisa agents (2026-02-23)
274
+
275
+ **MUST USE for v1.0:**
276
+
277
+ 1. **`categorizeBulkWriteRows()`** ⭐⭐⭐
278
+ - **Purpose:** Battle-tested conflict detection + attachment extraction
279
+ - **Why:** Used by ALL official adapters (Dexie, MongoDB, SQLite)
280
+ - **Returns:** `{ bulkInsertDocs, bulkUpdateDocs, errors, eventBulk, attachmentsAdd/Remove/Update }`
281
+ - **Status:** āœ… DONE - Implemented in src/rxdb-helpers.ts (lines 69-251)
282
+
283
+ 2. **`stripAttachmentsDataFromDocument()`** ⭐⭐
284
+ - **Purpose:** Remove attachment .data field, keep metadata
285
+ - **When:** Before storing documents with attachments
286
+ - **Status:** āœ… DONE - Implemented in src/rxdb-helpers.ts (lines 50-60)
287
+
288
+ 3. **`stripAttachmentsDataFromRow()`** ⭐⭐
289
+ - **Purpose:** Strip attachments from bulk write rows
290
+ - **When:** Processing bulkWrite with attachments
291
+ - **Status:** āœ… DONE - Implemented in src/rxdb-helpers.ts (lines 62-67)
292
+
293
+ 4. **`attachmentWriteDataToNormalData()`** ⭐⭐
294
+ - **Purpose:** Convert attachment write format to storage format
295
+ - **When:** Processing attachment writes
296
+ - **Status:** āœ… DONE - Implemented in src/rxdb-helpers.ts (lines 38-48)
297
+
298
+ 5. **`getAttachmentSize()`** ⭐⭐
299
+ - **Purpose:** Calculate attachment size from base64
300
+ - **When:** Attachment metadata
301
+ - **Status:** āœ… DONE - Implemented in src/rxdb-helpers.ts (lines 34-36)
302
+
303
+ **ALREADY USING:**
304
+ - āœ… `ensureRxStorageInstanceParamsAreCorrect()` - Constructor validation
305
+
306
+ **OPTIONAL (Post-v1.0):**
307
+ - `normalizeMangoQuery()` - Query normalization for better cache hit rate
308
+ - `prepareQuery()` - Query plan hints for optimization
309
+
310
+ **NOT NEEDED (RxDB Internal):**
311
+ - āŒ `getSingleDocument()`, `writeSingle()`, `observeSingle()` - Convenience wrappers
312
+ - āŒ `getWrappedStorageInstance()` - RxDB wraps OUR storage
313
+ - āŒ `flatCloneDocWithMeta()` - RxDB internal
314
+ - āŒ `getWrittenDocumentsFromBulkWriteResponse()` - RxDB internal
315
+
316
+ **Implementation:**
317
+ All helper functions implemented in `src/rxdb-helpers.ts` (custom implementations, not imported from RxDB)
318
+
319
+ ---
320
+
321
+ ## šŸ“‹ Current Priorities
322
+
323
+ ### **Completed (2026-02-23):**
324
+ 1. āœ… Phase 1: Minimal Working Core
325
+ 2. āœ… Phase 2: Query Builder & Production Hardening
326
+ - āœ… Phase 2.1: Basic Operators
327
+ - āœ… Phase 2.2: WAL Mode
328
+ - āœ… Phase 2.3: JSONB Storage
329
+ - āœ… Phase 2.4: Conflict Detection
330
+ - āœ… Phase 2.5: Query Builder Caching (4.8-22.6x speedup)
331
+ 3. āœ… Phase 3: Validation & Benchmarking
332
+ - āœ… Phase 3.1: RxDB Official Test Suite (112/112 passing)
333
+ - āœ… Phase 3.2: Performance Benchmarks (1.06-1.68x faster than better-sqlite3)
334
+
335
+ **Test Results:** 260/260 tests passing (100%)
336
+ - Our tests: 138/138 āœ…
337
+ - Official RxDB: 122/122 āœ…
338
+
339
+ ### **v1.0 Release Ready:**
340
+ 1. āœ… **Attachments Implemented**
341
+ - Attachments table with composite keys (documentId||attachmentId)
342
+ - `getAttachmentData()` method with digest validation
343
+ - All 5 RxDB helpers implemented
344
+ - 4 comprehensive test cases
345
+ 2. āœ… **bulkWrite Refactored**
346
+ - Uses `categorizeBulkWriteRows()` helper
347
+ - Clean architecture with proper conflict handling
348
+ - Automatic attachment extraction
349
+ 3. āœ… **Full test suite passing** - 260/260 tests (100%)
350
+ 4. šŸ“¦ **Ready for npm publish v1.0.0**
351
+ 5. šŸŽ‰ **Community adoption** - Gather feedback, iterate
352
+
353
+ ---
354
+
355
+ ## šŸŽ“ Key Learnings (From Crew Research)
356
+
357
+ ### **From Vivian (RxDB Requirements):**
358
+ - All RxStorageInstance methods documented āœ…
359
+ - Mango query operators: $eq, $gt, $in, $or, $regex, etc. āœ…
360
+ - Conflict resolution: revision-based with _rev field āœ…
361
+ - Attachments: base64-encoded strings āœ…
362
+ - Performance expectations: <10ms writes, binary search queries āœ…
363
+
364
+ ### **From Lisa (SQLite Patterns):**
365
+ - Prepared statements: Cache by schema hash āœ…
366
+ - Indexes: deleted+id, mtime_ms+id (we already have!) āœ…
367
+ - Transactions: Use wrapper for atomicity āœ…
368
+ - WAL mode: Enable once at init āœ…
369
+ - Schema: JSONB BLOB + metadata columns āœ…
370
+
371
+ ### **From Lisa (Gap Analysis):**
372
+ - Query Builder: 557 lines, handles NULL/boolean edge cases āœ…
373
+ - Reference uses 3-layer architecture (we use 1-layer) āœ…
374
+ - JSONB vs TEXT: 20-30% storage savings āœ…
375
+ - Conflict detection: Catch SQLITE_CONSTRAINT_PRIMARYKEY āœ…
376
+ - Our Phase 1 limitations: O(n) queries, no index utilization āœ…
377
+
378
+ ---
379
+
380
+ ## šŸ“ā€ā˜ ļø Linus Torvalds Wisdom
381
+
382
+ > "Talk is cheap. Show me the code."
383
+
384
+ **Applied:**
385
+ - āœ… Phase 1: Shipped working code in 1 day
386
+ - 🚧 Phase 2: Focus on the bottleneck (query builder)
387
+ - ā³ Phase 3: Measure before claiming victory
388
+
389
+ > "Don't over-engineer. Build what you need, when you need it.
390
+ > Bad programmers worry about the code. Good programmers worry about data structures and their relationships first."
391
+
392
+ **Applied:**
393
+ - āœ… Phase 1: In-memory filtering (simple, works)
394
+ - 🚧 Phase 2: SQL filtering (needed for scale)
395
+ - āøļø Phase 4: Advanced features (defer until needed)
396
+
397
+ > "Optimize the slow path, not the fast path."
398
+
399
+ **Applied:**
400
+ - šŸŽÆ Query Builder: THE bottleneck (10-100x impact)
401
+ - ⚔ WAL mode: 3-6x write speedup (5 min effort)
402
+ - šŸ“¦ JSONB: 20-30% savings (2 hour effort)
403
+
404
+ ---
405
+
406
+ ## šŸ“ˆ Success Metrics
407
+
408
+ **Phase 1 (Complete):**
409
+ - āœ… 8/8 tests passing
410
+ - āœ… TypeScript compiles (0 errors)
411
+ - āœ… Atomic transactions working
412
+
413
+ **Phase 2 (Complete āœ…):**
414
+ - āœ… Query Builder: Basic Mango operators working ($eq, $ne, $gt, $gte, $lt, $lte)
415
+ - āœ… 31/31 tests passing
416
+ - āœ… TypeScript: 0 errors, 0 `any` types
417
+ - āœ… WAL mode enabled (3-6x write speedup)
418
+ - āœ… Proper checkpoint implementation
419
+ - āœ… Conflict detection working (409 errors with documentInDb)
420
+ - āœ… Extensively tested serialization formats (MessagePack, bun:jsc, JSON)
421
+ - āœ… **JSON + TEXT storage: 23.40ms average (10k docs)**
422
+
423
+ **Phase 3 (Complete āœ…):**
424
+ - āœ… Advanced Query Operators: $in, $nin, $or, $and
425
+ - āœ… 51/51 tests passing (44 → 51 tests)
426
+ - āœ… NULL handling for array operators
427
+ - āœ… Recursive query builder with logicalDepth tracking
428
+ - āœ… Complex nested queries (4-level nesting tested)
429
+ - āœ… Benchmarked: 27.39ms average (10k docs)
430
+ - āœ… DRY architecture: Pure functions, no god objects
431
+ - āœ… WAL performance verified: 2.39x speedup (in-memory), 3-6x (file-based)
432
+ - āœ… Nested query tests: 7 comprehensive tests
433
+ - āœ… Architectural patterns documented (10 patterns)
434
+ - āœ… RxDB API alignment verified (partial success pattern)
435
+
436
+ **Phase 4 (v1.0 Preparation - COMPLETE āœ…):**
437
+ - āœ… Operators: DONE in v0.4.0 (18 operators implemented)
438
+ - āœ… RxDB official test suite: DONE (122/122 passing)
439
+ - āœ… Benchmarks: DONE (1.06-1.68x faster than better-sqlite3)
440
+ - āœ… Conflict handling: DONE (409 errors with documentInDb)
441
+ - āœ… Attachments support (getAttachmentData + 4 tests passing)
442
+ - āœ… Refactor bulkWrite (uses categorizeBulkWriteRows helper)
443
+ - āœ… All 5 RxDB helper functions implemented
444
+ - āœ… Test Results: 260/260 tests passing (138 local + 122 official)
445
+
446
+ ---
447
+
448
+ ## šŸš€ Post-v1.0 Enhancements (Future)
449
+
450
+ **These are NOT blockers for v1.0. Implement when users request them.**
451
+
452
+ ### **Custom Indexes from schema.indexes (Optional)**
453
+ - Implement `CREATE INDEX` based on `schema.indexes` definitions
454
+ - **Why defer:** Not required by RxStorageInstance interface, optional optimization
455
+ - **How Dexie does it:** Uses `dexieDb.version(1).stores()` with schema.indexes
456
+ - **Effort:** 4-6 hours
457
+ - **Benefit:** Faster queries on indexed fields (currently only deleted/mtime_ms indexed)
458
+
459
+ ---
460
+
461
+ ## šŸ“‹ REMOVED from Roadmap (Research Findings)
462
+
463
+ **These features DON'T EXIST in RxDB or are already complete:**
464
+
465
+ ### āŒ conflictResolutionTasks() / resolveConflictResultionTask()
466
+ - **Status:** REMOVED in RxDB 16.0.0
467
+ - **Evidence:** Release notes explicitly state removal
468
+ - **What we have:** 409 error handling (correct approach)
469
+ - **Conflict resolution:** Happens at replication level, NOT storage level
470
+
471
+ ### āœ… Operators
472
+ - **Status:** DONE in v0.4.0
473
+ - **Implemented:** 18 operators ($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or, $exists, $regex, $elemMatch, $not, $nor, $type, $size, $mod)
474
+
475
+ ### āŒ normalizeMangoQuery() / prepareQuery()
476
+ - **Status:** OUT OF SCOPE - RxDB core responsibility
477
+ - **Evidence:** Implemented in `rx-query-helper.ts` (lines 35, 269)
478
+ - **What storage receives:** `PreparedQuery` objects (already normalized with query plans)
479
+ - **Interface comment:** "User provided mango queries will be filled up by RxDB via normalizeMangoQuery()"
480
+ - **Conclusion:** Storage adapters do NOT implement these
481
+
482
+ ### āŒ user_version pragma for Schema Migrations
483
+ - **Status:** OUT OF SCOPE - RxDB handles migrations at collection level
484
+ - **Evidence:** No storage plugin uses `user_version`, migrations in `plugins/migration-schema/`
485
+ - **How RxDB handles it:** Schema version in `schema.version`, migrations via plugins
486
+ - **Conclusion:** Storage adapters are version-agnostic, only store data for specific schema version
487
+
488
+ ### āŒ EXPLAIN QUERY PLAN
489
+ - **Status:** OUT OF SCOPE - RxDB provides query plans
490
+ - **Evidence:** No storage plugin uses EXPLAIN QUERY PLAN
491
+ - **What RxDB provides:** `PreparedQuery.queryPlan` with index hints from `query-planner.ts`
492
+ - **Interface comment:** "queryPlan is a hint... not required to use it"
493
+ - **Conclusion:** Storage adapters receive query plans, don't need to generate them
494
+
495
+ ---
496
+
497
+ ## šŸ”¬ Future Research Topics
498
+
499
+ #### **Hybrid SQL+Mingo Pattern (QUESTION MARK)**
500
+
501
+ **Goal:** Implement SQL pre-filter + Mingo post-filter IF senior provides evidence.
502
+
503
+ **Status:** ā“ **ON HOLD** - Waiting for senior to provide:
504
+ - Production examples using this pattern
505
+ - Benchmarks proving it's faster than pure SQL
506
+ - Use cases where it's needed
507
+
508
+ **If validated:**
509
+ 1. Implement hybrid query executor
510
+ 2. Benchmark vs pure SQL
511
+ 3. Document when to use each approach
512
+
513
+ **Effort:** 2 hours (if validated)
514
+ **Status:** ā“ Unproven - need evidence from senior
515
+
516
+ ---
517
+
518
+ ## šŸ¤ Contributing
519
+
520
+ This is a community project! Contributions welcome.
521
+
522
+ **How to help:**
523
+ 1. Test with your RxDB app
524
+ 2. Report bugs/edge cases
525
+ 3. Submit PRs for missing features
526
+ 4. Share performance benchmarks
527
+
528
+ ---
529
+
530
+ **Not affiliated with RxDB or Bun. Community-maintained adapter.**
531
+
532
+ _Last updated: 2026-02-23 by adam2am_
@@ -0,0 +1,145 @@
1
+ import { getRxStorageBunSQLite } from '../src/storage';
2
+ import type { RxDocumentData } from 'rxdb';
3
+
4
+ interface BenchmarkDocType {
5
+ id: string;
6
+ name: string;
7
+ age: number;
8
+ status: string;
9
+ }
10
+
11
+ async function benchmark() {
12
+ console.log('šŸ“ā€ā˜ ļø Benchmarking Phase 3 (Advanced Query Operators)...\n');
13
+
14
+ const storage = getRxStorageBunSQLite();
15
+ const instance = await storage.createStorageInstance<BenchmarkDocType>({
16
+ databaseInstanceToken: 'benchmark-token',
17
+ databaseName: 'benchmark',
18
+ collectionName: 'users',
19
+ schema: {
20
+ version: 0,
21
+ primaryKey: 'id',
22
+ type: 'object',
23
+ properties: {
24
+ id: { type: 'string', maxLength: 100 },
25
+ name: { type: 'string' },
26
+ age: { type: 'number' },
27
+ status: { type: 'string' },
28
+ _deleted: { type: 'boolean' },
29
+ _attachments: { type: 'object' },
30
+ _rev: { type: 'string' },
31
+ _meta: {
32
+ type: 'object',
33
+ properties: {
34
+ lwt: { type: 'number' }
35
+ }
36
+ }
37
+ },
38
+ required: ['id', 'name', 'age', 'status', '_deleted', '_attachments', '_rev', '_meta']
39
+ },
40
+ options: {},
41
+ multiInstance: false,
42
+ devMode: false
43
+ });
44
+
45
+ console.log('šŸ“ Inserting 10,000 documents...');
46
+ const docs: Array<{ document: RxDocumentData<BenchmarkDocType> }> = [];
47
+ for (let i = 0; i < 10000; i++) {
48
+ docs.push({
49
+ document: {
50
+ id: `user${i}`,
51
+ name: `User ${i}`,
52
+ age: 18 + (i % 50),
53
+ status: i % 2 === 0 ? 'active' : 'inactive',
54
+ _deleted: false,
55
+ _attachments: {},
56
+ _rev: '1-abc',
57
+ _meta: { lwt: Date.now() }
58
+ }
59
+ });
60
+ }
61
+
62
+ await instance.bulkWrite(docs, 'benchmark');
63
+ console.log('āœ… Inserted 10,000 documents\n');
64
+
65
+ console.log('ā±ļø Query 1: Simple equality (age = 25)');
66
+ const start1 = performance.now();
67
+ const result1 = await instance.query({
68
+ query: { selector: { age: 25 }, sort: [], skip: 0 },
69
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
70
+ });
71
+ const end1 = performance.now();
72
+ console.log(` Found ${result1.documents.length} documents in ${(end1 - start1).toFixed(2)}ms\n`);
73
+
74
+ console.log('ā±ļø Query 2: Greater than (age > 50)');
75
+ const start2 = performance.now();
76
+ const result2 = await instance.query({
77
+ query: { selector: { age: { $gt: 50 } }, sort: [], skip: 0 },
78
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
79
+ });
80
+ const end2 = performance.now();
81
+ console.log(` Found ${result2.documents.length} documents in ${(end2 - start2).toFixed(2)}ms\n`);
82
+
83
+ console.log('ā±ļø Query 3: Multiple conditions (age > 30 AND status = "active")');
84
+ const start3 = performance.now();
85
+ const result3 = await instance.query({
86
+ query: { selector: { age: { $gt: 30 }, status: 'active' }, sort: [], skip: 0 },
87
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
88
+ });
89
+ const end3 = performance.now();
90
+ console.log(` Found ${result3.documents.length} documents in ${(end3 - start3).toFixed(2)}ms\n`);
91
+
92
+ console.log('ā±ļø Query 4: Range query (age >= 20 AND age <= 40)');
93
+ const start4 = performance.now();
94
+ const result4 = await instance.query({
95
+ query: { selector: { age: { $gte: 20, $lte: 40 } }, sort: [], skip: 0 },
96
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
97
+ });
98
+ const end4 = performance.now();
99
+ console.log(` Found ${result4.documents.length} documents in ${(end4 - start4).toFixed(2)}ms\n`);
100
+
101
+ console.log('ā±ļø Query 5: $in operator (age in [25, 30, 35, 40])');
102
+ const start5 = performance.now();
103
+ const result5 = await instance.query({
104
+ query: { selector: { age: { $in: [25, 30, 35, 40] } }, sort: [], skip: 0 },
105
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
106
+ });
107
+ const end5 = performance.now();
108
+ console.log(` Found ${result5.documents.length} documents in ${(end5 - start5).toFixed(2)}ms\n`);
109
+
110
+ console.log('ā±ļø Query 6: $or operator (age < 20 OR age > 60)');
111
+ const start6 = performance.now();
112
+ const result6 = await instance.query({
113
+ query: { selector: { $or: [{ age: { $lt: 20 } }, { age: { $gt: 60 } }] }, sort: [], skip: 0 },
114
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
115
+ });
116
+ const end6 = performance.now();
117
+ console.log(` Found ${result6.documents.length} documents in ${(end6 - start6).toFixed(2)}ms\n`);
118
+
119
+ console.log('ā±ļø Query 7: $nin operator (status not in ["pending", "archived"])');
120
+ const start7 = performance.now();
121
+ const result7 = await instance.query({
122
+ query: { selector: { status: { $nin: ['pending', 'archived'] } }, sort: [], skip: 0 },
123
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
124
+ });
125
+ const end7 = performance.now();
126
+ console.log(` Found ${result7.documents.length} documents in ${(end7 - start7).toFixed(2)}ms\n`);
127
+
128
+ console.log('ā±ļø Query 8: Complex nested ($or + $and + $in)');
129
+ const start8 = performance.now();
130
+ const result8 = await instance.query({
131
+ query: { selector: { $or: [{ $and: [{ age: { $gte: 30 } }, { status: 'active' }] }, { age: { $in: [18, 19, 20] } }] }, sort: [], skip: 0 },
132
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
133
+ });
134
+ const end8 = performance.now();
135
+ console.log(` Found ${result8.documents.length} documents in ${(end8 - start8).toFixed(2)}ms\n`);
136
+
137
+ const avgTime = ((end1 - start1) + (end2 - start2) + (end3 - start3) + (end4 - start4) + (end5 - start5) + (end6 - start6) + (end7 - start7) + (end8 - start8)) / 8;
138
+ console.log(`šŸ“Š Average query time: ${avgTime.toFixed(2)}ms`);
139
+ console.log(`\nāœ… Phase 3 includes advanced operators: $in, $nin, $or, $and`);
140
+ console.log(` Expected speedup: 10-100x for large datasets (vs Phase 1 in-memory filtering)\n`);
141
+
142
+ await instance.close();
143
+ }
144
+
145
+ benchmark().catch(console.error);