bun-sqlite-for-rxdb 1.0.1 → 1.1.3
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/CHANGELOG.md +48 -0
- package/dist/connection-pool.d.ts +4 -0
- package/dist/connection-pool.d.ts.map +1 -0
- package/{src/index.ts → dist/index.d.ts} +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13220 -0
- package/dist/instance.d.ts +42 -0
- package/dist/instance.d.ts.map +1 -0
- package/dist/query/builder.d.ts +6 -0
- package/dist/query/builder.d.ts.map +1 -0
- package/dist/query/operators.d.ts +21 -0
- package/dist/query/operators.d.ts.map +1 -0
- package/dist/query/schema-mapper.d.ts +8 -0
- package/dist/query/schema-mapper.d.ts.map +1 -0
- package/dist/query/smart-regex.d.ts +6 -0
- package/dist/query/smart-regex.d.ts.map +1 -0
- package/dist/rxdb-helpers.d.ts +28 -0
- package/dist/rxdb-helpers.d.ts.map +1 -0
- package/dist/statement-manager.d.ts +15 -0
- package/dist/statement-manager.d.ts.map +1 -0
- package/dist/storage.d.ts +4 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +11 -3
- package/.serena/project.yml +0 -84
- package/ROADMAP.md +0 -532
- package/benchmarks/benchmark.ts +0 -145
- package/benchmarks/case-insensitive-10runs.ts +0 -156
- package/benchmarks/fts5-1m-scale.ts +0 -126
- package/benchmarks/fts5-before-after.ts +0 -104
- package/benchmarks/indexed-benchmark.ts +0 -141
- package/benchmarks/new-operators-benchmark.ts +0 -140
- package/benchmarks/query-builder-benchmark.ts +0 -88
- package/benchmarks/query-builder-consistency.ts +0 -109
- package/benchmarks/raw-better-sqlite3-10m.ts +0 -85
- package/benchmarks/raw-better-sqlite3.ts +0 -86
- package/benchmarks/raw-bun-sqlite-10m.ts +0 -85
- package/benchmarks/raw-bun-sqlite.ts +0 -86
- package/benchmarks/regex-10runs-all.ts +0 -216
- package/benchmarks/regex-comparison-benchmark.ts +0 -161
- package/benchmarks/regex-real-comparison.ts +0 -213
- package/benchmarks/run-10x.sh +0 -19
- package/benchmarks/smart-regex-benchmark.ts +0 -148
- package/benchmarks/sql-vs-mingo-benchmark.ts +0 -210
- package/benchmarks/sql-vs-mingo-comparison.ts +0 -175
- package/benchmarks/text-vs-jsonb.ts +0 -167
- package/benchmarks/wal-benchmark.ts +0 -112
- package/docs/architectural-patterns.md +0 -1336
- package/docs/id1-testsuite-journey.md +0 -839
- package/docs/official-test-suite-setup.md +0 -393
- package/nul +0 -0
- package/src/changestream.test.ts +0 -182
- package/src/cleanup.test.ts +0 -110
- package/src/collection-isolation.test.ts +0 -74
- package/src/connection-pool.test.ts +0 -102
- package/src/connection-pool.ts +0 -38
- package/src/findDocumentsById.test.ts +0 -122
- package/src/instance.ts +0 -382
- package/src/multi-instance-events.test.ts +0 -204
- package/src/query/and-operator.test.ts +0 -39
- package/src/query/builder.test.ts +0 -96
- package/src/query/builder.ts +0 -154
- package/src/query/elemMatch-operator.test.ts +0 -24
- package/src/query/exists-operator.test.ts +0 -28
- package/src/query/in-operators.test.ts +0 -54
- package/src/query/mod-operator.test.ts +0 -22
- package/src/query/nested-query.test.ts +0 -198
- package/src/query/not-operators.test.ts +0 -49
- package/src/query/operators.test.ts +0 -70
- package/src/query/operators.ts +0 -185
- package/src/query/or-operator.test.ts +0 -68
- package/src/query/regex-escaping-regression.test.ts +0 -43
- package/src/query/regex-operator.test.ts +0 -44
- package/src/query/schema-mapper.ts +0 -27
- package/src/query/size-operator.test.ts +0 -22
- package/src/query/smart-regex.ts +0 -52
- package/src/query/type-operator.test.ts +0 -37
- package/src/query-cache.test.ts +0 -286
- package/src/rxdb-helpers.test.ts +0 -348
- package/src/rxdb-helpers.ts +0 -262
- package/src/schema-version-isolation.test.ts +0 -126
- package/src/statement-manager.ts +0 -69
- package/src/storage.test.ts +0 -589
- package/src/storage.ts +0 -21
- package/src/types.ts +0 -14
- package/test/rxdb-test-suite.ts +0 -27
- package/tsconfig.json +0 -31
package/src/query-cache.test.ts
DELETED
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
2
|
-
import { buildWhereClause, getCacheSize, clearCache } from '../src/query/builder';
|
|
3
|
-
import type { RxJsonSchema, MangoQuerySelector, RxDocumentData } from 'rxdb';
|
|
4
|
-
|
|
5
|
-
type TestDoc = {
|
|
6
|
-
id: string;
|
|
7
|
-
name: string;
|
|
8
|
-
age: number;
|
|
9
|
-
data: string;
|
|
10
|
-
_deleted: boolean;
|
|
11
|
-
_attachments: {};
|
|
12
|
-
_rev: string;
|
|
13
|
-
_meta: { lwt: number };
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const schema: RxJsonSchema<RxDocumentData<TestDoc>> = {
|
|
17
|
-
version: 0,
|
|
18
|
-
primaryKey: 'id',
|
|
19
|
-
type: 'object',
|
|
20
|
-
properties: {
|
|
21
|
-
id: { type: 'string', maxLength: 100 },
|
|
22
|
-
name: { type: 'string' },
|
|
23
|
-
age: { type: 'number' },
|
|
24
|
-
data: { type: 'string' },
|
|
25
|
-
_deleted: { type: 'boolean' },
|
|
26
|
-
_attachments: { type: 'object' },
|
|
27
|
-
_rev: { type: 'string' },
|
|
28
|
-
_meta: {
|
|
29
|
-
type: 'object',
|
|
30
|
-
properties: {
|
|
31
|
-
lwt: { type: 'number' }
|
|
32
|
-
},
|
|
33
|
-
required: ['lwt']
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
required: ['id', '_deleted', '_rev', '_meta']
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
describe('Query Builder Cache - Edge Cases & Production Readiness', () => {
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
clearCache();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('Edge Case 1: HUGE selector (10KB+) should not crash', () => {
|
|
45
|
-
const hugeSelector: MangoQuerySelector<RxDocumentData<TestDoc>> = {
|
|
46
|
-
$or: Array.from({ length: 1000 }, (_, i) => ({ age: { $eq: i } }))
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const start = performance.now();
|
|
50
|
-
const result1 = buildWhereClause(hugeSelector, schema);
|
|
51
|
-
const time1 = performance.now() - start;
|
|
52
|
-
|
|
53
|
-
const start2 = performance.now();
|
|
54
|
-
const result2 = buildWhereClause(hugeSelector, schema);
|
|
55
|
-
const time2 = performance.now() - start2;
|
|
56
|
-
|
|
57
|
-
expect(result1.sql).toBe(result2.sql);
|
|
58
|
-
expect(time2).toBeLessThanOrEqual(time1 * 1.5);
|
|
59
|
-
console.log(` Huge selector: ${time1.toFixed(2)}ms → ${time2.toFixed(2)}ms (${(time1/time2).toFixed(1)}x faster cached)`);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('Edge Case 2: Cache eviction at 100 entries', () => {
|
|
63
|
-
for (let i = 0; i < 150; i++) {
|
|
64
|
-
const selector: MangoQuerySelector<RxDocumentData<TestDoc>> = { age: { $eq: i } };
|
|
65
|
-
buildWhereClause(selector, schema);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const firstSelector: MangoQuerySelector<RxDocumentData<TestDoc>> = { age: { $eq: 0 } };
|
|
69
|
-
const start = performance.now();
|
|
70
|
-
buildWhereClause(firstSelector, schema);
|
|
71
|
-
const time = performance.now() - start;
|
|
72
|
-
|
|
73
|
-
expect(time).toBeGreaterThan(0);
|
|
74
|
-
console.log(` First selector after 150 inserts: ${(time * 1000).toFixed(2)}µs (evicted, rebuilt)`);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('Edge Case 3: Schema version change invalidates cache', () => {
|
|
78
|
-
const selector: MangoQuerySelector<RxDocumentData<TestDoc>> = { age: { $gt: 30 } };
|
|
79
|
-
|
|
80
|
-
const schema1 = { ...schema, version: 0 };
|
|
81
|
-
const result1 = buildWhereClause(selector, schema1);
|
|
82
|
-
|
|
83
|
-
const schema2 = { ...schema, version: 1 };
|
|
84
|
-
const result2 = buildWhereClause(selector, schema2);
|
|
85
|
-
|
|
86
|
-
expect(result1.sql).toBe(result2.sql);
|
|
87
|
-
console.log(` Schema version change: cache invalidated correctly`);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('Edge Case 4: Identical selectors with different object order', () => {
|
|
91
|
-
const selector1: MangoQuerySelector<RxDocumentData<TestDoc>> = { age: { $gt: 30 }, name: { $eq: 'test' } };
|
|
92
|
-
const selector2: MangoQuerySelector<RxDocumentData<TestDoc>> = { name: { $eq: 'test' }, age: { $gt: 30 } };
|
|
93
|
-
|
|
94
|
-
const result1 = buildWhereClause(selector1, schema);
|
|
95
|
-
const result2 = buildWhereClause(selector2, schema);
|
|
96
|
-
|
|
97
|
-
expect(result1.sql).toBe(result2.sql);
|
|
98
|
-
console.log(` Different object order: produces same SQL`);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('Edge Case 5: Deeply nested selectors', () => {
|
|
102
|
-
const deepSelector: MangoQuerySelector<RxDocumentData<TestDoc>> = {
|
|
103
|
-
$and: [
|
|
104
|
-
{ $or: [{ age: { $gt: 20 } }, { age: { $lt: 10 } }] },
|
|
105
|
-
{ $or: [{ name: { $eq: 'a' } }, { name: { $eq: 'b' } }] },
|
|
106
|
-
{ $or: [{ data: { $exists: true } }, { data: { $exists: false } }] }
|
|
107
|
-
]
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const start = performance.now();
|
|
111
|
-
const result1 = buildWhereClause(deepSelector, schema);
|
|
112
|
-
const time1 = performance.now() - start;
|
|
113
|
-
|
|
114
|
-
const start2 = performance.now();
|
|
115
|
-
const result2 = buildWhereClause(deepSelector, schema);
|
|
116
|
-
const time2 = performance.now() - start2;
|
|
117
|
-
|
|
118
|
-
expect(result1.sql).toBe(result2.sql);
|
|
119
|
-
expect(time2).toBeLessThan(time1);
|
|
120
|
-
console.log(` Deep nesting: ${time1.toFixed(2)}ms → ${time2.toFixed(2)}ms (${(time1/time2).toFixed(1)}x faster cached)`);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('Edge Case 6: Special characters in selector values', () => {
|
|
124
|
-
const specialSelector: MangoQuerySelector<RxDocumentData<TestDoc>> = {
|
|
125
|
-
name: { $eq: 'test"with\'quotes\nand\ttabs' }
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const result1 = buildWhereClause(specialSelector, schema);
|
|
129
|
-
const result2 = buildWhereClause(specialSelector, schema);
|
|
130
|
-
|
|
131
|
-
expect(result1.sql).toBe(result2.sql);
|
|
132
|
-
expect(result1.args).toEqual(result2.args);
|
|
133
|
-
console.log(` Special characters: handled correctly`);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test('Edge Case 7: Null and undefined values', () => {
|
|
137
|
-
const nullSelector: MangoQuerySelector<RxDocumentData<TestDoc>> = {
|
|
138
|
-
name: { $eq: null as any }
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const result = buildWhereClause(nullSelector, schema);
|
|
142
|
-
expect(result.sql).toContain('IS NULL');
|
|
143
|
-
console.log(` Null values: handled correctly`);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('Edge Case 8: Empty selector', () => {
|
|
147
|
-
const emptySelector: MangoQuerySelector<RxDocumentData<TestDoc>> = {};
|
|
148
|
-
|
|
149
|
-
const result = buildWhereClause(emptySelector, schema);
|
|
150
|
-
expect(result.sql).toBe('1=1');
|
|
151
|
-
console.log(` Empty selector: returns 1=1 (match all)`);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('Edge Case 9: Cache hit rate with repeated queries', () => {
|
|
155
|
-
const selectors = [
|
|
156
|
-
{ age: { $gt: 30 } },
|
|
157
|
-
{ age: { $lt: 20 } },
|
|
158
|
-
{ name: { $eq: 'test' } }
|
|
159
|
-
];
|
|
160
|
-
|
|
161
|
-
const start1 = process.hrtime.bigint();
|
|
162
|
-
for (let i = 0; i < 3; i++) {
|
|
163
|
-
buildWhereClause(selectors[i] as MangoQuerySelector<RxDocumentData<TestDoc>>, schema);
|
|
164
|
-
}
|
|
165
|
-
const firstTime = process.hrtime.bigint() - start1;
|
|
166
|
-
|
|
167
|
-
const start2 = process.hrtime.bigint();
|
|
168
|
-
for (let i = 0; i < 100000; i++) {
|
|
169
|
-
const selector = selectors[i % selectors.length];
|
|
170
|
-
buildWhereClause(selector as MangoQuerySelector<RxDocumentData<TestDoc>>, schema);
|
|
171
|
-
}
|
|
172
|
-
const cachedTime = process.hrtime.bigint() - start2;
|
|
173
|
-
|
|
174
|
-
const avgFirst = Number(firstTime) / 3;
|
|
175
|
-
const avgCached = Number(cachedTime) / 100000;
|
|
176
|
-
const speedup = avgFirst / avgCached;
|
|
177
|
-
|
|
178
|
-
expect(speedup).toBeGreaterThan(1.2);
|
|
179
|
-
expect(getCacheSize()).toBe(3);
|
|
180
|
-
console.log(` Cache hit rate: ${speedup.toFixed(1)}x faster for repeated queries`);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('Edge Case 10: Memory stress test (1000 unique queries)', () => {
|
|
184
|
-
const start = performance.now();
|
|
185
|
-
|
|
186
|
-
for (let i = 0; i < 1000; i++) {
|
|
187
|
-
const selector: MangoQuerySelector<RxDocumentData<TestDoc>> = {
|
|
188
|
-
age: { $eq: i },
|
|
189
|
-
name: { $eq: `user${i}` }
|
|
190
|
-
};
|
|
191
|
-
buildWhereClause(selector, schema);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const time = performance.now() - start;
|
|
195
|
-
const avgPerQuery = time / 1000;
|
|
196
|
-
|
|
197
|
-
expect(avgPerQuery).toBeLessThan(1);
|
|
198
|
-
console.log(` 1000 unique queries: ${time.toFixed(2)}ms total (${avgPerQuery.toFixed(3)}ms per query)`);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test('Production Scenario 1: Concurrent queries from multiple collections', () => {
|
|
202
|
-
const schema1 = { ...schema, version: 0 };
|
|
203
|
-
const schema2 = { ...schema, version: 1 };
|
|
204
|
-
const schema3 = { ...schema, version: 2 };
|
|
205
|
-
|
|
206
|
-
const selector: MangoQuerySelector<RxDocumentData<TestDoc>> = { age: { $gt: 30 } };
|
|
207
|
-
|
|
208
|
-
const result1 = buildWhereClause(selector, schema1);
|
|
209
|
-
const result2 = buildWhereClause(selector, schema2);
|
|
210
|
-
const result3 = buildWhereClause(selector, schema3);
|
|
211
|
-
|
|
212
|
-
expect(result1.sql).toBe(result2.sql);
|
|
213
|
-
expect(result2.sql).toBe(result3.sql);
|
|
214
|
-
console.log(` Multiple schema versions: isolated correctly`);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test('Production Scenario 2: High-frequency queries (10k/sec simulation)', () => {
|
|
218
|
-
const selectors = Array.from({ length: 10 }, (_, i) => ({ age: { $eq: i * 10 } }));
|
|
219
|
-
|
|
220
|
-
const start = performance.now();
|
|
221
|
-
for (let i = 0; i < 10000; i++) {
|
|
222
|
-
const selector = selectors[i % selectors.length];
|
|
223
|
-
buildWhereClause(selector as MangoQuerySelector<RxDocumentData<TestDoc>>, schema);
|
|
224
|
-
}
|
|
225
|
-
const time = performance.now() - start;
|
|
226
|
-
|
|
227
|
-
const qps = 10000 / (time / 1000);
|
|
228
|
-
expect(qps).toBeGreaterThan(100000);
|
|
229
|
-
console.log(` High-frequency: ${qps.toFixed(0)} queries/sec (${(time / 10000 * 1000).toFixed(2)}µs per query)`);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test('Production Scenario 3: Cache behavior under load', () => {
|
|
233
|
-
const results: number[] = [];
|
|
234
|
-
|
|
235
|
-
for (let batch = 0; batch < 5; batch++) {
|
|
236
|
-
const start = performance.now();
|
|
237
|
-
for (let i = 0; i < 1000; i++) {
|
|
238
|
-
const selector: MangoQuerySelector<RxDocumentData<TestDoc>> = { age: { $eq: i % 50 } };
|
|
239
|
-
buildWhereClause(selector, schema);
|
|
240
|
-
}
|
|
241
|
-
results.push(performance.now() - start);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const firstBatch = results[0];
|
|
245
|
-
const avgLaterBatches = results.slice(1).reduce((a, b) => a + b, 0) / 4;
|
|
246
|
-
|
|
247
|
-
expect(avgLaterBatches).toBeLessThanOrEqual(firstBatch * 1.5);
|
|
248
|
-
expect(getCacheSize()).toBe(50);
|
|
249
|
-
console.log(` Under load: First batch ${firstBatch.toFixed(2)}ms, Later batches ${avgLaterBatches.toFixed(2)}ms`);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test('Edge Case 13: Cache is BOUNDED at 500 entries (no exponential growth)', () => {
|
|
253
|
-
clearCache();
|
|
254
|
-
|
|
255
|
-
for (let i = 0; i < 1000; i++) {
|
|
256
|
-
const selector: MangoQuerySelector<RxDocumentData<TestDoc>> = {
|
|
257
|
-
id: { $eq: `unique-${i}` },
|
|
258
|
-
age: { $eq: i }
|
|
259
|
-
};
|
|
260
|
-
buildWhereClause(selector, schema);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
expect(getCacheSize()).toBe(500);
|
|
264
|
-
console.log(` Cache bounded: Added 1000 unique queries, cache size = ${getCacheSize()} (max 500) ✅`);
|
|
265
|
-
|
|
266
|
-
const firstQuery: MangoQuerySelector<RxDocumentData<TestDoc>> = {
|
|
267
|
-
id: { $eq: 'unique-0' },
|
|
268
|
-
age: { $eq: 0 }
|
|
269
|
-
};
|
|
270
|
-
const lastQuery: MangoQuerySelector<RxDocumentData<TestDoc>> = {
|
|
271
|
-
id: { $eq: 'unique-999' },
|
|
272
|
-
age: { $eq: 999 }
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
const start1 = performance.now();
|
|
276
|
-
buildWhereClause(firstQuery, schema);
|
|
277
|
-
const time1 = performance.now() - start1;
|
|
278
|
-
|
|
279
|
-
const start2 = performance.now();
|
|
280
|
-
buildWhereClause(lastQuery, schema);
|
|
281
|
-
const time2 = performance.now() - start2;
|
|
282
|
-
|
|
283
|
-
expect(time2).toBeLessThanOrEqual(time1 * 2);
|
|
284
|
-
console.log(` FIFO eviction works: First query ${time1.toFixed(3)}ms (evicted), Last query ${time2.toFixed(3)}ms (cached)`);
|
|
285
|
-
});
|
|
286
|
-
});
|
package/src/rxdb-helpers.test.ts
DELETED
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import { categorizeBulkWriteRows, ensureRxStorageInstanceParamsAreCorrect } from './rxdb-helpers';
|
|
3
|
-
import type { BulkWriteRow, RxDocumentData, RxStorageInstance, RxStorageInstanceCreationParams, RxStorageDefaultCheckpoint, RxStorageCountResult, EventBulk, RxStorageChangeEvent, RxJsonSchema } from 'rxdb';
|
|
4
|
-
import { Subject } from 'rxjs';
|
|
5
|
-
|
|
6
|
-
type TestDoc = {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
age: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type TestInternals = Record<string, never>;
|
|
13
|
-
type TestOptions = Record<string, never>;
|
|
14
|
-
|
|
15
|
-
const createTestSchema = (overrides: Partial<RxJsonSchema<RxDocumentData<TestDoc>>> = {}): RxJsonSchema<RxDocumentData<TestDoc>> => ({
|
|
16
|
-
version: 0,
|
|
17
|
-
primaryKey: 'id',
|
|
18
|
-
type: 'object',
|
|
19
|
-
properties: {
|
|
20
|
-
id: { type: 'string', maxLength: 100 },
|
|
21
|
-
name: { type: 'string' },
|
|
22
|
-
age: { type: 'number' },
|
|
23
|
-
_deleted: { type: 'boolean' },
|
|
24
|
-
_attachments: { type: 'object' },
|
|
25
|
-
_rev: { type: 'string' },
|
|
26
|
-
_meta: { type: 'object' }
|
|
27
|
-
},
|
|
28
|
-
required: ['id'],
|
|
29
|
-
...overrides
|
|
30
|
-
} as RxJsonSchema<RxDocumentData<TestDoc>>);
|
|
31
|
-
|
|
32
|
-
function createMockStorageInstance<T>(): RxStorageInstance<T, TestInternals, TestOptions, RxStorageDefaultCheckpoint> {
|
|
33
|
-
const changeStreamSubject = new Subject<EventBulk<RxStorageChangeEvent<T>, RxStorageDefaultCheckpoint>>();
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
schema: createTestSchema() as Readonly<RxJsonSchema<RxDocumentData<T>>>,
|
|
37
|
-
collectionName: 'test',
|
|
38
|
-
databaseName: 'testdb',
|
|
39
|
-
options: {},
|
|
40
|
-
internals: {},
|
|
41
|
-
bulkWrite: async () => ({ error: [] }),
|
|
42
|
-
findDocumentsById: async () => [],
|
|
43
|
-
query: async () => ({ documents: [] }),
|
|
44
|
-
count: async (): Promise<RxStorageCountResult> => ({ count: 0, mode: 'fast' }),
|
|
45
|
-
getAttachmentData: async () => '',
|
|
46
|
-
getChangedDocumentsSince: async () => ({
|
|
47
|
-
documents: [],
|
|
48
|
-
checkpoint: { id: '', lwt: 0 }
|
|
49
|
-
}),
|
|
50
|
-
changeStream: () => changeStreamSubject.asObservable(),
|
|
51
|
-
cleanup: async () => true,
|
|
52
|
-
close: async () => {},
|
|
53
|
-
remove: async () => {}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
describe('categorizeBulkWriteRows', () => {
|
|
58
|
-
test('INSERT: no previous, no doc in DB → bulkInsertDocs', () => {
|
|
59
|
-
const docsInDb = new Map<string, RxDocumentData<TestDoc>>();
|
|
60
|
-
const writeRows: BulkWriteRow<TestDoc>[] = [{
|
|
61
|
-
document: {
|
|
62
|
-
id: 'doc1',
|
|
63
|
-
name: 'Test',
|
|
64
|
-
age: 25,
|
|
65
|
-
_deleted: false,
|
|
66
|
-
_attachments: {},
|
|
67
|
-
_rev: '1-a',
|
|
68
|
-
_meta: { lwt: 1000 }
|
|
69
|
-
}
|
|
70
|
-
}];
|
|
71
|
-
|
|
72
|
-
const result = categorizeBulkWriteRows(
|
|
73
|
-
createMockStorageInstance<TestDoc>(),
|
|
74
|
-
'id',
|
|
75
|
-
docsInDb,
|
|
76
|
-
writeRows,
|
|
77
|
-
'test-context'
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
expect(result.bulkInsertDocs.length).toBe(1);
|
|
81
|
-
expect(result.bulkUpdateDocs.length).toBe(0);
|
|
82
|
-
expect(result.errors.length).toBe(0);
|
|
83
|
-
expect(result.eventBulk.events.length).toBe(1);
|
|
84
|
-
expect(result.eventBulk.events[0].operation).toBe('INSERT');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('INSERT conflict: no previous, doc exists in DB → error 409', () => {
|
|
88
|
-
const existingDoc: RxDocumentData<TestDoc> = {
|
|
89
|
-
id: 'doc1',
|
|
90
|
-
name: 'Existing',
|
|
91
|
-
age: 30,
|
|
92
|
-
_deleted: false,
|
|
93
|
-
_attachments: {},
|
|
94
|
-
_rev: '1-a',
|
|
95
|
-
_meta: { lwt: 500 }
|
|
96
|
-
};
|
|
97
|
-
const docsInDb = new Map([['doc1', existingDoc]]);
|
|
98
|
-
const writeRows: BulkWriteRow<TestDoc>[] = [{
|
|
99
|
-
document: {
|
|
100
|
-
id: 'doc1',
|
|
101
|
-
name: 'New',
|
|
102
|
-
age: 25,
|
|
103
|
-
_deleted: false,
|
|
104
|
-
_attachments: {},
|
|
105
|
-
_rev: '1-b',
|
|
106
|
-
_meta: { lwt: 1000 }
|
|
107
|
-
}
|
|
108
|
-
}];
|
|
109
|
-
|
|
110
|
-
const result = categorizeBulkWriteRows(
|
|
111
|
-
createMockStorageInstance<TestDoc>(),
|
|
112
|
-
'id',
|
|
113
|
-
docsInDb,
|
|
114
|
-
writeRows,
|
|
115
|
-
'test-context'
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
expect(result.bulkInsertDocs.length).toBe(0);
|
|
119
|
-
expect(result.bulkUpdateDocs.length).toBe(0);
|
|
120
|
-
expect(result.errors.length).toBe(1);
|
|
121
|
-
expect(result.errors[0].status).toBe(409);
|
|
122
|
-
expect('documentInDb' in result.errors[0]).toBe(true);
|
|
123
|
-
if ('documentInDb' in result.errors[0]) {
|
|
124
|
-
expect(result.errors[0].documentInDb).toEqual(existingDoc);
|
|
125
|
-
}
|
|
126
|
-
expect(result.eventBulk.events.length).toBe(0);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('UPDATE: previous matches DB revision → bulkUpdateDocs', () => {
|
|
130
|
-
const existingDoc: RxDocumentData<TestDoc> = {
|
|
131
|
-
id: 'doc1',
|
|
132
|
-
name: 'Old',
|
|
133
|
-
age: 30,
|
|
134
|
-
_deleted: false,
|
|
135
|
-
_attachments: {},
|
|
136
|
-
_rev: '1-a',
|
|
137
|
-
_meta: { lwt: 500 }
|
|
138
|
-
};
|
|
139
|
-
const docsInDb = new Map([['doc1', existingDoc]]);
|
|
140
|
-
const writeRows: BulkWriteRow<TestDoc>[] = [{
|
|
141
|
-
previous: existingDoc,
|
|
142
|
-
document: {
|
|
143
|
-
id: 'doc1',
|
|
144
|
-
name: 'Updated',
|
|
145
|
-
age: 31,
|
|
146
|
-
_deleted: false,
|
|
147
|
-
_attachments: {},
|
|
148
|
-
_rev: '2-b',
|
|
149
|
-
_meta: { lwt: 1000 }
|
|
150
|
-
}
|
|
151
|
-
}];
|
|
152
|
-
|
|
153
|
-
const result = categorizeBulkWriteRows(
|
|
154
|
-
createMockStorageInstance<TestDoc>(),
|
|
155
|
-
'id',
|
|
156
|
-
docsInDb,
|
|
157
|
-
writeRows,
|
|
158
|
-
'test-context'
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
expect(result.bulkInsertDocs.length).toBe(0);
|
|
162
|
-
expect(result.bulkUpdateDocs.length).toBe(1);
|
|
163
|
-
expect(result.errors.length).toBe(0);
|
|
164
|
-
expect(result.eventBulk.events.length).toBe(1);
|
|
165
|
-
expect(result.eventBulk.events[0].operation).toBe('UPDATE');
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test('UPDATE conflict: previous revision mismatch → error 409', () => {
|
|
169
|
-
const existingDoc: RxDocumentData<TestDoc> = {
|
|
170
|
-
id: 'doc1',
|
|
171
|
-
name: 'Current',
|
|
172
|
-
age: 30,
|
|
173
|
-
_deleted: false,
|
|
174
|
-
_attachments: {},
|
|
175
|
-
_rev: '2-b',
|
|
176
|
-
_meta: { lwt: 800 }
|
|
177
|
-
};
|
|
178
|
-
const docsInDb = new Map([['doc1', existingDoc]]);
|
|
179
|
-
const writeRows: BulkWriteRow<TestDoc>[] = [{
|
|
180
|
-
previous: {
|
|
181
|
-
id: 'doc1',
|
|
182
|
-
name: 'Old',
|
|
183
|
-
age: 30,
|
|
184
|
-
_deleted: false,
|
|
185
|
-
_attachments: {},
|
|
186
|
-
_rev: '1-a',
|
|
187
|
-
_meta: { lwt: 500 }
|
|
188
|
-
},
|
|
189
|
-
document: {
|
|
190
|
-
id: 'doc1',
|
|
191
|
-
name: 'Updated',
|
|
192
|
-
age: 31,
|
|
193
|
-
_deleted: false,
|
|
194
|
-
_attachments: {},
|
|
195
|
-
_rev: '3-c',
|
|
196
|
-
_meta: { lwt: 1000 }
|
|
197
|
-
}
|
|
198
|
-
}];
|
|
199
|
-
|
|
200
|
-
const result = categorizeBulkWriteRows(
|
|
201
|
-
createMockStorageInstance<TestDoc>(),
|
|
202
|
-
'id',
|
|
203
|
-
docsInDb,
|
|
204
|
-
writeRows,
|
|
205
|
-
'test-context'
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
expect(result.bulkInsertDocs.length).toBe(0);
|
|
209
|
-
expect(result.bulkUpdateDocs.length).toBe(0);
|
|
210
|
-
expect(result.errors.length).toBe(1);
|
|
211
|
-
expect(result.errors[0].status).toBe(409);
|
|
212
|
-
expect('documentInDb' in result.errors[0]).toBe(true);
|
|
213
|
-
if ('documentInDb' in result.errors[0]) {
|
|
214
|
-
expect(result.errors[0].documentInDb).toEqual(existingDoc);
|
|
215
|
-
}
|
|
216
|
-
expect(result.eventBulk.events.length).toBe(0);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test('DELETE operation: previous not deleted, document deleted → DELETE event', () => {
|
|
220
|
-
const existingDoc: RxDocumentData<TestDoc> = {
|
|
221
|
-
id: 'doc1',
|
|
222
|
-
name: 'Active',
|
|
223
|
-
age: 30,
|
|
224
|
-
_deleted: false,
|
|
225
|
-
_attachments: {},
|
|
226
|
-
_rev: '1-a',
|
|
227
|
-
_meta: { lwt: 500 }
|
|
228
|
-
};
|
|
229
|
-
const docsInDb = new Map([['doc1', existingDoc]]);
|
|
230
|
-
const writeRows: BulkWriteRow<TestDoc>[] = [{
|
|
231
|
-
previous: existingDoc,
|
|
232
|
-
document: {
|
|
233
|
-
id: 'doc1',
|
|
234
|
-
name: 'Active',
|
|
235
|
-
age: 30,
|
|
236
|
-
_deleted: true,
|
|
237
|
-
_attachments: {},
|
|
238
|
-
_rev: '2-b',
|
|
239
|
-
_meta: { lwt: 1000 }
|
|
240
|
-
}
|
|
241
|
-
}];
|
|
242
|
-
|
|
243
|
-
const result = categorizeBulkWriteRows(
|
|
244
|
-
createMockStorageInstance<TestDoc>(),
|
|
245
|
-
'id',
|
|
246
|
-
docsInDb,
|
|
247
|
-
writeRows,
|
|
248
|
-
'test-context'
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
expect(result.bulkUpdateDocs.length).toBe(1);
|
|
252
|
-
expect(result.eventBulk.events[0].operation).toBe('DELETE');
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test('checkpoint: uses newestRow document for checkpoint', () => {
|
|
256
|
-
const docsInDb = new Map<string, RxDocumentData<TestDoc>>();
|
|
257
|
-
const writeRows: BulkWriteRow<TestDoc>[] = [
|
|
258
|
-
{
|
|
259
|
-
document: {
|
|
260
|
-
id: 'doc1',
|
|
261
|
-
name: 'First',
|
|
262
|
-
age: 25,
|
|
263
|
-
_deleted: false,
|
|
264
|
-
_attachments: {},
|
|
265
|
-
_rev: '1-a',
|
|
266
|
-
_meta: { lwt: 1000 }
|
|
267
|
-
}
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
document: {
|
|
271
|
-
id: 'doc2',
|
|
272
|
-
name: 'Last',
|
|
273
|
-
age: 30,
|
|
274
|
-
_deleted: false,
|
|
275
|
-
_attachments: {},
|
|
276
|
-
_rev: '1-b',
|
|
277
|
-
_meta: { lwt: 2000 }
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
];
|
|
281
|
-
|
|
282
|
-
const result = categorizeBulkWriteRows(
|
|
283
|
-
createMockStorageInstance<TestDoc>(),
|
|
284
|
-
'id',
|
|
285
|
-
docsInDb,
|
|
286
|
-
writeRows,
|
|
287
|
-
'test-context'
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
expect(result.newestRow).toBeDefined();
|
|
291
|
-
expect(result.newestRow?.document.id).toBe('doc2');
|
|
292
|
-
expect(result.newestRow?.document._meta.lwt).toBe(2000);
|
|
293
|
-
|
|
294
|
-
if (result.newestRow) {
|
|
295
|
-
result.eventBulk.checkpoint = {
|
|
296
|
-
id: result.newestRow.document.id,
|
|
297
|
-
lwt: result.newestRow.document._meta.lwt
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
expect(result.eventBulk.checkpoint).toEqual({ id: 'doc2', lwt: 2000 });
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
describe('ensureRxStorageInstanceParamsAreCorrect', () => {
|
|
306
|
-
test('throws UT6 when schema uses encryption but no password', () => {
|
|
307
|
-
const params: RxStorageInstanceCreationParams<TestDoc, TestOptions> = {
|
|
308
|
-
schema: createTestSchema({ encrypted: ['field1'] }),
|
|
309
|
-
password: undefined,
|
|
310
|
-
databaseInstanceToken: 'test-token',
|
|
311
|
-
databaseName: 'testdb',
|
|
312
|
-
collectionName: 'testcol',
|
|
313
|
-
options: {},
|
|
314
|
-
multiInstance: false,
|
|
315
|
-
devMode: false
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
expect(() => ensureRxStorageInstanceParamsAreCorrect(params)).toThrow('UT6');
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
test('does not throw when schema has no encryption', () => {
|
|
322
|
-
const params: RxStorageInstanceCreationParams<TestDoc, TestOptions> = {
|
|
323
|
-
schema: createTestSchema(),
|
|
324
|
-
databaseInstanceToken: 'test-token',
|
|
325
|
-
databaseName: 'testdb',
|
|
326
|
-
collectionName: 'testcol',
|
|
327
|
-
options: {},
|
|
328
|
-
multiInstance: false,
|
|
329
|
-
devMode: false
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
expect(() => ensureRxStorageInstanceParamsAreCorrect(params)).not.toThrow();
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
test('throws UT5 when schema uses keyCompression', () => {
|
|
336
|
-
const params: RxStorageInstanceCreationParams<TestDoc, TestOptions> = {
|
|
337
|
-
schema: createTestSchema({ keyCompression: true }),
|
|
338
|
-
databaseInstanceToken: 'test-token',
|
|
339
|
-
databaseName: 'testdb',
|
|
340
|
-
collectionName: 'testcol',
|
|
341
|
-
options: {},
|
|
342
|
-
multiInstance: false,
|
|
343
|
-
devMode: false
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
expect(() => ensureRxStorageInstanceParamsAreCorrect(params)).toThrow('UT5');
|
|
347
|
-
});
|
|
348
|
-
});
|