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
@@ -0,0 +1,148 @@
1
+ import { getRxStorageBunSQLite } from '../src/storage';
2
+ import type { RxDocumentData } from 'rxdb';
3
+
4
+ interface BenchmarkDocType {
5
+ id: string;
6
+ name: string;
7
+ email: string;
8
+ domain: string;
9
+ }
10
+
11
+ async function benchmarkSmartRegex() {
12
+ console.log('🏴‍☠️ Smart Regex → LIKE Optimization Benchmark\n');
13
+
14
+ const storage = getRxStorageBunSQLite();
15
+ const instance = await storage.createStorageInstance<BenchmarkDocType>({
16
+ databaseInstanceToken: 'smart-regex-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
+ email: { type: 'string' },
27
+ domain: { 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', 'email', 'domain', '_deleted', '_attachments', '_rev', '_meta']
39
+ },
40
+ options: {},
41
+ multiInstance: false,
42
+ devMode: false
43
+ });
44
+
45
+ console.log('📝 Inserting 100,000 documents...');
46
+ const insertStart = performance.now();
47
+
48
+ const domains = ['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'company.com'];
49
+ const batchSize = 1000;
50
+ for (let batch = 0; batch < 100; batch++) {
51
+ const docs: Array<{ document: RxDocumentData<BenchmarkDocType> }> = [];
52
+ for (let i = 0; i < batchSize; i++) {
53
+ const idx = batch * batchSize + i;
54
+ const domain = domains[idx % domains.length];
55
+ const doc: any = {
56
+ id: `user${idx}`,
57
+ name: `User ${idx}`,
58
+ email: `user${idx}@${domain}`,
59
+ domain: domain,
60
+ _deleted: false,
61
+ _attachments: {},
62
+ _rev: '1-abc',
63
+ _meta: { lwt: Date.now() }
64
+ };
65
+ docs.push({ document: doc });
66
+ }
67
+ await instance.bulkWrite(docs, 'benchmark');
68
+ }
69
+
70
+ const insertEnd = performance.now();
71
+ console.log(`✅ Inserted 100,000 documents in ${(insertEnd - insertStart).toFixed(2)}ms\n`);
72
+
73
+ console.log('='.repeat(60));
74
+ console.log('Test 1: Prefix pattern (^User 1) - Uses index');
75
+ console.log('='.repeat(60));
76
+ const start1 = performance.now();
77
+ const result1 = await instance.query({
78
+ query: { selector: { name: { $regex: '^User 1' } }, sort: [], skip: 0 },
79
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
80
+ });
81
+ const end1 = performance.now();
82
+ console.log(`Found ${result1.documents.length.toLocaleString()} docs in ${(end1 - start1).toFixed(2)}ms\n`);
83
+
84
+ console.log('='.repeat(60));
85
+ console.log('Test 2: Suffix pattern (@gmail.com$) - No index but optimized');
86
+ console.log('='.repeat(60));
87
+ const start2 = performance.now();
88
+ const result2 = await instance.query({
89
+ query: { selector: { email: { $regex: '@gmail\\.com$' } }, sort: [], skip: 0 },
90
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
91
+ });
92
+ const end2 = performance.now();
93
+ console.log(`Found ${result2.documents.length.toLocaleString()} docs in ${(end2 - start2).toFixed(2)}ms\n`);
94
+
95
+ console.log('='.repeat(60));
96
+ console.log('Test 3: Exact match (^gmail.com$) - Uses = operator');
97
+ console.log('='.repeat(60));
98
+ const start3 = performance.now();
99
+ const result3 = await instance.query({
100
+ query: { selector: { domain: { $regex: '^gmail\\.com$' } }, sort: [], skip: 0 },
101
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
102
+ });
103
+ const end3 = performance.now();
104
+ console.log(`Found ${result3.documents.length.toLocaleString()} docs in ${(end3 - start3).toFixed(2)}ms\n`);
105
+
106
+ console.log('='.repeat(60));
107
+ console.log('Test 4: Contains pattern (User) - LIKE %pattern%');
108
+ console.log('='.repeat(60));
109
+ const start4 = performance.now();
110
+ const result4 = await instance.query({
111
+ query: { selector: { name: { $regex: 'User' } }, sort: [], skip: 0 },
112
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
113
+ });
114
+ const end4 = performance.now();
115
+ console.log(`Found ${result4.documents.length.toLocaleString()} docs in ${(end4 - start4).toFixed(2)}ms\n`);
116
+
117
+ console.log('='.repeat(60));
118
+ console.log('Test 5: Case-insensitive (user, i flag) - LOWER() optimization');
119
+ console.log('='.repeat(60));
120
+ const start5 = performance.now();
121
+ const result5 = await instance.query({
122
+ query: { selector: { name: { $regex: 'user', $options: 'i' } }, sort: [], skip: 0 },
123
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
124
+ });
125
+ const end5 = performance.now();
126
+ console.log(`Found ${result5.documents.length.toLocaleString()} docs in ${(end5 - start5).toFixed(2)}ms\n`);
127
+
128
+ const avgTime = ((end1 - start1) + (end2 - start2) + (end3 - start3) + (end4 - start4) + (end5 - start5)) / 5;
129
+
130
+ console.log('='.repeat(60));
131
+ console.log('📊 RESULTS SUMMARY (100k documents)');
132
+ console.log('='.repeat(60));
133
+ console.log(`Prefix (^): ${(end1 - start1).toFixed(2)}ms`);
134
+ console.log(`Suffix ($): ${(end2 - start2).toFixed(2)}ms`);
135
+ console.log(`Exact (^$): ${(end3 - start3).toFixed(2)}ms`);
136
+ console.log(`Contains: ${(end4 - start4).toFixed(2)}ms`);
137
+ console.log(`Case-insensitive: ${(end5 - start5).toFixed(2)}ms`);
138
+ console.log(`Average: ${avgTime.toFixed(2)}ms`);
139
+ console.log('='.repeat(60));
140
+ console.log();
141
+ console.log('✅ Smart regex → LIKE converter working!');
142
+ console.log(' All simple patterns converted to indexed LIKE queries');
143
+ console.log('='.repeat(60) + '\n');
144
+
145
+ await instance.close();
146
+ }
147
+
148
+ benchmarkSmartRegex().catch(console.error);
@@ -0,0 +1,210 @@
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
+ email?: string;
9
+ status: string;
10
+ tags?: string[];
11
+ }
12
+
13
+ async function benchmarkAtScale(docCount: number) {
14
+ console.log(`\n${'='.repeat(60)}`);
15
+ console.log(`🏴‍☠️ SQL vs Mingo Benchmark: ${docCount.toLocaleString()} documents`);
16
+ console.log(`${'='.repeat(60)}\n`);
17
+
18
+ const storage = getRxStorageBunSQLite();
19
+ const instance = await storage.createStorageInstance<BenchmarkDocType>({
20
+ databaseInstanceToken: `benchmark-token-${docCount}`,
21
+ databaseName: 'benchmark',
22
+ collectionName: 'users',
23
+ schema: {
24
+ version: 0,
25
+ primaryKey: 'id',
26
+ type: 'object',
27
+ properties: {
28
+ id: { type: 'string', maxLength: 100 },
29
+ name: { type: 'string' },
30
+ age: { type: 'number' },
31
+ email: { type: 'string' },
32
+ status: { type: 'string' },
33
+ tags: { type: 'array', items: { type: 'string' } },
34
+ _deleted: { type: 'boolean' },
35
+ _attachments: { type: 'object' },
36
+ _rev: { type: 'string' },
37
+ _meta: {
38
+ type: 'object',
39
+ properties: {
40
+ lwt: { type: 'number' }
41
+ }
42
+ }
43
+ },
44
+ required: ['id', 'name', 'age', 'status', '_deleted', '_attachments', '_rev', '_meta']
45
+ },
46
+ options: {},
47
+ multiInstance: false,
48
+ devMode: false
49
+ });
50
+
51
+ console.log(`📝 Inserting ${docCount.toLocaleString()} documents...`);
52
+ const insertStart = performance.now();
53
+
54
+ const batchSize = 1000;
55
+ for (let batch = 0; batch < docCount / batchSize; batch++) {
56
+ const docs: Array<{ document: RxDocumentData<BenchmarkDocType> }> = [];
57
+ for (let i = 0; i < batchSize; i++) {
58
+ const idx = batch * batchSize + i;
59
+ const doc: any = {
60
+ id: `user${idx}`,
61
+ name: `User ${idx}`,
62
+ age: 18 + (idx % 50),
63
+ status: idx % 2 === 0 ? 'active' : 'inactive',
64
+ _deleted: false,
65
+ _attachments: {},
66
+ _rev: '1-abc',
67
+ _meta: { lwt: Date.now() }
68
+ };
69
+
70
+ if (idx % 3 === 0) {
71
+ doc.email = `user${idx}@gmail.com`;
72
+ } else if (idx % 3 === 1) {
73
+ doc.email = `user${idx}@yahoo.com`;
74
+ }
75
+
76
+ if (idx % 2 === 0) {
77
+ doc.tags = ['active', 'premium'];
78
+ }
79
+
80
+ docs.push({ document: doc });
81
+ }
82
+ await instance.bulkWrite(docs, 'benchmark');
83
+ if ((batch + 1) % 10 === 0) {
84
+ process.stdout.write(`\r Progress: ${((batch + 1) * batchSize).toLocaleString()} / ${docCount.toLocaleString()}`);
85
+ }
86
+ }
87
+
88
+ const insertEnd = performance.now();
89
+ console.log(`\n✅ Inserted ${docCount.toLocaleString()} documents in ${(insertEnd - insertStart).toFixed(2)}ms\n`);
90
+
91
+ const memBefore = process.memoryUsage();
92
+
93
+ console.log('📊 Test 1: $exists operator (email field exists)');
94
+ console.log(' SQL: WHERE email IS NOT NULL');
95
+ const start1 = performance.now();
96
+ const result1 = await instance.query({
97
+ query: { selector: { email: { $exists: true } }, sort: [], skip: 0 },
98
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
99
+ });
100
+ const end1 = performance.now();
101
+ console.log(` ✅ Found ${result1.documents.length.toLocaleString()} docs in ${(end1 - start1).toFixed(2)}ms\n`);
102
+
103
+ console.log('📊 Test 2: $regex operator (name starts with "User 1")');
104
+ console.log(' SQL: WHERE name LIKE "User 1%"');
105
+ const start2 = performance.now();
106
+ const result2 = await instance.query({
107
+ query: { selector: { name: { $regex: '^User 1' } }, sort: [], skip: 0 },
108
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
109
+ });
110
+ const end2 = performance.now();
111
+ console.log(` ✅ Found ${result2.documents.length.toLocaleString()} docs in ${(end2 - start2).toFixed(2)}ms\n`);
112
+
113
+ console.log('📊 Test 3: $gt operator (age > 50)');
114
+ console.log(' SQL: WHERE age > 50');
115
+ const start3 = performance.now();
116
+ const result3 = await instance.query({
117
+ query: { selector: { age: { $gt: 50 } }, sort: [], skip: 0 },
118
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
119
+ });
120
+ const end3 = performance.now();
121
+ console.log(` ✅ Found ${result3.documents.length.toLocaleString()} docs in ${(end3 - start3).toFixed(2)}ms\n`);
122
+
123
+ console.log('📊 Test 4: $in operator (status in [active, pending])');
124
+ console.log(' SQL: WHERE status IN ("active", "pending")');
125
+ const start4 = performance.now();
126
+ const result4 = await instance.query({
127
+ query: { selector: { status: { $in: ['active', 'pending'] } }, sort: [], skip: 0 },
128
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
129
+ });
130
+ const end4 = performance.now();
131
+ console.log(` ✅ Found ${result4.documents.length.toLocaleString()} docs in ${(end4 - start4).toFixed(2)}ms\n`);
132
+
133
+ console.log('📊 Test 5: $elemMatch operator (tags contains "premium")');
134
+ console.log(' Mingo: In-memory filter (no SQL translation)');
135
+ const start5 = performance.now();
136
+ const result5 = await instance.query({
137
+ query: { selector: { tags: { $elemMatch: { $eq: 'premium' } } }, sort: [], skip: 0 },
138
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
139
+ });
140
+ const end5 = performance.now();
141
+ console.log(` ✅ Found ${result5.documents.length.toLocaleString()} docs in ${(end5 - start5).toFixed(2)}ms\n`);
142
+
143
+ const memAfter = process.memoryUsage();
144
+ const memDelta = {
145
+ rss: ((memAfter.rss - memBefore.rss) / 1024 / 1024).toFixed(2),
146
+ heapUsed: ((memAfter.heapUsed - memBefore.heapUsed) / 1024 / 1024).toFixed(2)
147
+ };
148
+
149
+ console.log(`${'='.repeat(60)}`);
150
+ console.log('📊 RESULTS SUMMARY');
151
+ console.log(`${'='.repeat(60)}`);
152
+ console.log(`SQL Operators ($exists, $regex, $gt, $in):`);
153
+ console.log(` - $exists: ${(end1 - start1).toFixed(2)}ms`);
154
+ console.log(` - $regex: ${(end2 - start2).toFixed(2)}ms`);
155
+ console.log(` - $gt: ${(end3 - start3).toFixed(2)}ms`);
156
+ console.log(` - $in: ${(end4 - start4).toFixed(2)}ms`);
157
+ console.log(` - Average: ${((end1 - start1 + end2 - start2 + end3 - start3 + end4 - start4) / 4).toFixed(2)}ms`);
158
+ console.log();
159
+ console.log(`Mingo Fallback ($elemMatch):`);
160
+ console.log(` - $elemMatch: ${(end5 - start5).toFixed(2)}ms`);
161
+ console.log();
162
+ console.log(`Memory Delta:`);
163
+ console.log(` - RSS: ${memDelta.rss}MB`);
164
+ console.log(` - Heap: ${memDelta.heapUsed}MB`);
165
+ console.log(`${'='.repeat(60)}\n`);
166
+
167
+ await instance.close();
168
+
169
+ return {
170
+ sqlAvg: (end1 - start1 + end2 - start2 + end3 - start3 + end4 - start4) / 4,
171
+ mingoTime: end5 - start5,
172
+ memDelta
173
+ };
174
+ }
175
+
176
+ async function main() {
177
+ console.log('🏴‍☠️ SQL vs Mingo Performance Comparison');
178
+ console.log('Testing with bun:sqlite native driver\n');
179
+
180
+ const results10k = await benchmarkAtScale(10000);
181
+ const results100k = await benchmarkAtScale(100000);
182
+
183
+ console.log('\n' + '='.repeat(60));
184
+ console.log('🎯 FINAL VERDICT');
185
+ console.log('='.repeat(60));
186
+ console.log('\n10k documents:');
187
+ console.log(` SQL avg: ${results10k.sqlAvg.toFixed(2)}ms`);
188
+ console.log(` Mingo: ${results10k.mingoTime.toFixed(2)}ms`);
189
+ console.log(` Ratio: ${(results10k.mingoTime / results10k.sqlAvg).toFixed(2)}x`);
190
+
191
+ console.log('\n100k documents:');
192
+ console.log(` SQL avg: ${results100k.sqlAvg.toFixed(2)}ms`);
193
+ console.log(` Mingo: ${results100k.mingoTime.toFixed(2)}ms`);
194
+ console.log(` Ratio: ${(results100k.mingoTime / results100k.sqlAvg).toFixed(2)}x`);
195
+
196
+ console.log('\n' + '='.repeat(60));
197
+ if (results100k.mingoTime / results100k.sqlAvg > 5) {
198
+ console.log('✅ VERDICT: SQL translation is WORTH IT at scale (5x+ faster)');
199
+ console.log(' Continue hybrid approach: SQL for simple, Mingo for complex');
200
+ } else if (results100k.mingoTime / results100k.sqlAvg > 2) {
201
+ console.log('⚠️ VERDICT: SQL is faster but not dramatically (2-5x)');
202
+ console.log(' Hybrid approach is reasonable, but Mingo-only is viable');
203
+ } else {
204
+ console.log('❌ VERDICT: SQL translation NOT worth the effort (<2x speedup)');
205
+ console.log(' Consider using Mingo everywhere for simplicity');
206
+ }
207
+ console.log('='.repeat(60) + '\n');
208
+ }
209
+
210
+ main().catch(console.error);
@@ -0,0 +1,175 @@
1
+ import { getRxStorageBunSQLite } from '../src/storage';
2
+ import type { RxDocumentData } from 'rxdb';
3
+ import { Query } from 'mingo';
4
+
5
+ interface BenchmarkDocType {
6
+ id: string;
7
+ name: string;
8
+ age: number;
9
+ email?: string;
10
+ status: string;
11
+ }
12
+
13
+ async function sqlVsMingoComparison() {
14
+ console.log('🏴‍☠️ SQL vs Mingo: Head-to-Head Comparison\n');
15
+
16
+ const storage = getRxStorageBunSQLite();
17
+ const instance = await storage.createStorageInstance<BenchmarkDocType>({
18
+ databaseInstanceToken: 'sql-vs-mingo-token',
19
+ databaseName: 'benchmark',
20
+ collectionName: 'users',
21
+ schema: {
22
+ version: 0,
23
+ primaryKey: 'id',
24
+ type: 'object',
25
+ properties: {
26
+ id: { type: 'string', maxLength: 100 },
27
+ name: { type: 'string' },
28
+ age: { type: 'number' },
29
+ email: { type: 'string' },
30
+ status: { type: 'string' },
31
+ _deleted: { type: 'boolean' },
32
+ _attachments: { type: 'object' },
33
+ _rev: { type: 'string' },
34
+ _meta: {
35
+ type: 'object',
36
+ properties: {
37
+ lwt: { type: 'number' }
38
+ }
39
+ }
40
+ },
41
+ required: ['id', 'name', 'age', 'status', '_deleted', '_attachments', '_rev', '_meta']
42
+ },
43
+ options: {},
44
+ multiInstance: false,
45
+ devMode: false
46
+ });
47
+
48
+ console.log('📝 Inserting 100,000 documents...');
49
+ const insertStart = performance.now();
50
+
51
+ const batchSize = 1000;
52
+ for (let batch = 0; batch < 100; batch++) {
53
+ const docs: Array<{ document: RxDocumentData<BenchmarkDocType> }> = [];
54
+ for (let i = 0; i < batchSize; i++) {
55
+ const idx = batch * batchSize + i;
56
+ const doc: any = {
57
+ id: `user${idx}`,
58
+ name: `User ${idx}`,
59
+ age: 18 + (idx % 50),
60
+ status: idx % 2 === 0 ? 'active' : 'inactive',
61
+ _deleted: false,
62
+ _attachments: {},
63
+ _rev: '1-abc',
64
+ _meta: { lwt: Date.now() }
65
+ };
66
+
67
+ if (idx % 3 === 0) {
68
+ doc.email = `user${idx}@gmail.com`;
69
+ }
70
+
71
+ docs.push({ document: doc });
72
+ }
73
+ await instance.bulkWrite(docs, 'benchmark');
74
+ }
75
+
76
+ const insertEnd = performance.now();
77
+ console.log(`✅ Inserted 100,000 documents in ${(insertEnd - insertStart).toFixed(2)}ms\n`);
78
+
79
+ console.log('='.repeat(60));
80
+ console.log('Test 1: $gt operator (age > 50)');
81
+ console.log('='.repeat(60));
82
+
83
+ const sqlStart1 = performance.now();
84
+ const sqlResult1 = await instance.query({
85
+ query: { selector: { age: { $gt: 50 } }, sort: [], skip: 0 },
86
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
87
+ });
88
+ const sqlEnd1 = performance.now();
89
+ console.log(`SQL (with indexes): ${(sqlEnd1 - sqlStart1).toFixed(2)}ms - Found ${sqlResult1.documents.length.toLocaleString()} docs`);
90
+
91
+ const mingoStart1 = performance.now();
92
+ const allDocs1 = await instance.query({
93
+ query: { selector: {}, sort: [], skip: 0 },
94
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
95
+ });
96
+ const mingoQuery1 = new Query({ age: { $gt: 50 } });
97
+ const mingoResult1 = allDocs1.documents.filter(doc => mingoQuery1.test(doc));
98
+ const mingoEnd1 = performance.now();
99
+ console.log(`Mingo (in-memory): ${(mingoEnd1 - mingoStart1).toFixed(2)}ms - Found ${mingoResult1.length.toLocaleString()} docs`);
100
+ console.log(`Speedup: ${((mingoEnd1 - mingoStart1) / (sqlEnd1 - sqlStart1)).toFixed(2)}x faster with SQL\n`);
101
+
102
+ console.log('='.repeat(60));
103
+ console.log('Test 2: $eq operator (status = "active")');
104
+ console.log('='.repeat(60));
105
+
106
+ const sqlStart2 = performance.now();
107
+ const sqlResult2 = await instance.query({
108
+ query: { selector: { status: { $eq: 'active' } }, sort: [], skip: 0 },
109
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
110
+ });
111
+ const sqlEnd2 = performance.now();
112
+ console.log(`SQL (with indexes): ${(sqlEnd2 - sqlStart2).toFixed(2)}ms - Found ${sqlResult2.documents.length.toLocaleString()} docs`);
113
+
114
+ const mingoStart2 = performance.now();
115
+ const allDocs2 = await instance.query({
116
+ query: { selector: {}, sort: [], skip: 0 },
117
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
118
+ });
119
+ const mingoQuery2 = new Query({ status: { $eq: 'active' } });
120
+ const mingoResult2 = allDocs2.documents.filter(doc => mingoQuery2.test(doc));
121
+ const mingoEnd2 = performance.now();
122
+ console.log(`Mingo (in-memory): ${(mingoEnd2 - mingoStart2).toFixed(2)}ms - Found ${mingoResult2.length.toLocaleString()} docs`);
123
+ console.log(`Speedup: ${((mingoEnd2 - mingoStart2) / (sqlEnd2 - sqlStart2)).toFixed(2)}x faster with SQL\n`);
124
+
125
+ console.log('='.repeat(60));
126
+ console.log('Test 3: $in operator (status in ["active", "pending"])');
127
+ console.log('='.repeat(60));
128
+
129
+ const sqlStart3 = performance.now();
130
+ const sqlResult3 = await instance.query({
131
+ query: { selector: { status: { $in: ['active', 'pending'] } }, sort: [], skip: 0 },
132
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
133
+ });
134
+ const sqlEnd3 = performance.now();
135
+ console.log(`SQL (with indexes): ${(sqlEnd3 - sqlStart3).toFixed(2)}ms - Found ${sqlResult3.documents.length.toLocaleString()} docs`);
136
+
137
+ const mingoStart3 = performance.now();
138
+ const allDocs3 = await instance.query({
139
+ query: { selector: {}, sort: [], skip: 0 },
140
+ queryPlan: { index: [], startKeys: [], endKeys: [], inclusiveStart: true, inclusiveEnd: true, sortSatisfiedByIndex: false, selectorSatisfiedByIndex: false }
141
+ });
142
+ const mingoQuery3 = new Query({ status: { $in: ['active', 'pending'] } });
143
+ const mingoResult3 = allDocs3.documents.filter(doc => mingoQuery3.test(doc));
144
+ const mingoEnd3 = performance.now();
145
+ console.log(`Mingo (in-memory): ${(mingoEnd3 - mingoStart3).toFixed(2)}ms - Found ${mingoResult3.length.toLocaleString()} docs`);
146
+ console.log(`Speedup: ${((mingoEnd3 - mingoStart3) / (sqlEnd3 - sqlStart3)).toFixed(2)}x faster with SQL\n`);
147
+
148
+ const sqlAvg = ((sqlEnd1 - sqlStart1) + (sqlEnd2 - sqlStart2) + (sqlEnd3 - sqlStart3)) / 3;
149
+ const mingoAvg = ((mingoEnd1 - mingoStart1) + (mingoEnd2 - mingoStart2) + (mingoEnd3 - mingoStart3)) / 3;
150
+
151
+ console.log('='.repeat(60));
152
+ console.log('📊 FINAL RESULTS (100k documents)');
153
+ console.log('='.repeat(60));
154
+ console.log(`SQL (with indexes): ${sqlAvg.toFixed(2)}ms average`);
155
+ console.log(`Mingo (in-memory): ${mingoAvg.toFixed(2)}ms average`);
156
+ console.log(`Overall Speedup: ${(mingoAvg / sqlAvg).toFixed(2)}x faster with SQL`);
157
+ console.log('='.repeat(60));
158
+ console.log();
159
+
160
+ if (mingoAvg / sqlAvg > 2) {
161
+ console.log('✅ VERDICT: SQL with indexes is SIGNIFICANTLY faster (2x+)');
162
+ console.log(' Hybrid strategy VALIDATED: Use SQL for simple, Mingo for complex');
163
+ } else if (mingoAvg / sqlAvg > 1.5) {
164
+ console.log('⚠️ VERDICT: SQL is moderately faster (1.5-2x)');
165
+ console.log(' Hybrid strategy is reasonable but not dramatic');
166
+ } else {
167
+ console.log('❌ VERDICT: SQL and Mingo are similar (<1.5x difference)');
168
+ console.log(' Consider Mingo-only for simplicity');
169
+ }
170
+ console.log('='.repeat(60) + '\n');
171
+
172
+ await instance.close();
173
+ }
174
+
175
+ sqlVsMingoComparison().catch(console.error);