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.
- package/.serena/project.yml +84 -0
- package/CHANGELOG.md +300 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/ROADMAP.md +532 -0
- package/benchmarks/benchmark.ts +145 -0
- package/benchmarks/case-insensitive-10runs.ts +156 -0
- package/benchmarks/fts5-1m-scale.ts +126 -0
- package/benchmarks/fts5-before-after.ts +104 -0
- package/benchmarks/indexed-benchmark.ts +141 -0
- package/benchmarks/new-operators-benchmark.ts +140 -0
- package/benchmarks/query-builder-benchmark.ts +88 -0
- package/benchmarks/query-builder-consistency.ts +109 -0
- package/benchmarks/raw-better-sqlite3-10m.ts +85 -0
- package/benchmarks/raw-better-sqlite3.ts +86 -0
- package/benchmarks/raw-bun-sqlite-10m.ts +85 -0
- package/benchmarks/raw-bun-sqlite.ts +86 -0
- package/benchmarks/regex-10runs-all.ts +216 -0
- package/benchmarks/regex-comparison-benchmark.ts +161 -0
- package/benchmarks/regex-real-comparison.ts +213 -0
- package/benchmarks/run-10x.sh +19 -0
- package/benchmarks/smart-regex-benchmark.ts +148 -0
- package/benchmarks/sql-vs-mingo-benchmark.ts +210 -0
- package/benchmarks/sql-vs-mingo-comparison.ts +175 -0
- package/benchmarks/text-vs-jsonb.ts +167 -0
- package/benchmarks/wal-benchmark.ts +112 -0
- package/docs/architectural-patterns.md +1336 -0
- package/docs/id1-testsuite-journey.md +839 -0
- package/docs/official-test-suite-setup.md +393 -0
- package/nul +0 -0
- package/package.json +44 -0
- package/src/changestream.test.ts +182 -0
- package/src/cleanup.test.ts +110 -0
- package/src/collection-isolation.test.ts +74 -0
- package/src/connection-pool.test.ts +102 -0
- package/src/connection-pool.ts +38 -0
- package/src/findDocumentsById.test.ts +122 -0
- package/src/index.ts +2 -0
- package/src/instance.ts +382 -0
- package/src/multi-instance-events.test.ts +204 -0
- package/src/query/and-operator.test.ts +39 -0
- package/src/query/builder.test.ts +96 -0
- package/src/query/builder.ts +154 -0
- package/src/query/elemMatch-operator.test.ts +24 -0
- package/src/query/exists-operator.test.ts +28 -0
- package/src/query/in-operators.test.ts +54 -0
- package/src/query/mod-operator.test.ts +22 -0
- package/src/query/nested-query.test.ts +198 -0
- package/src/query/not-operators.test.ts +49 -0
- package/src/query/operators.test.ts +70 -0
- package/src/query/operators.ts +185 -0
- package/src/query/or-operator.test.ts +68 -0
- package/src/query/regex-escaping-regression.test.ts +43 -0
- package/src/query/regex-operator.test.ts +44 -0
- package/src/query/schema-mapper.ts +27 -0
- package/src/query/size-operator.test.ts +22 -0
- package/src/query/smart-regex.ts +52 -0
- package/src/query/type-operator.test.ts +37 -0
- package/src/query-cache.test.ts +286 -0
- package/src/rxdb-helpers.test.ts +348 -0
- package/src/rxdb-helpers.ts +262 -0
- package/src/schema-version-isolation.test.ts +126 -0
- package/src/statement-manager.ts +69 -0
- package/src/storage.test.ts +589 -0
- package/src/storage.ts +21 -0
- package/src/types.ts +14 -0
- package/test/rxdb-test-suite.ts +27 -0
- 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);
|