@bod.ee/db 0.12.2 → 0.12.6

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.
@@ -165,7 +165,7 @@ describe('Per-path replication topology', () => {
165
165
 
166
166
  // --- Emit filtering ---
167
167
 
168
- it('primary-mode path emits to _repl', () => {
168
+ it('primary-mode path emits to _repl', async () => {
169
169
  const db = new BodDB({
170
170
  path: ':memory:',
171
171
  sweepInterval: 0,
@@ -175,16 +175,17 @@ describe('Per-path replication topology', () => {
175
175
  },
176
176
  });
177
177
  instances.push(db);
178
- db.replication!.start();
178
+ await db.replication!.start();
179
179
 
180
180
  db.set('_vfs/file1', { data: 'hello' });
181
+ await new Promise(r => setTimeout(r, 50));
181
182
  const repl = db.get('_repl');
182
183
  expect(repl).toBeTruthy();
183
184
  const events = Object.values(repl as Record<string, any>);
184
185
  expect(events.some((e: any) => e.path === '_vfs/file1')).toBe(true);
185
186
  });
186
187
 
187
- it('non-emitting modes do NOT emit', () => {
188
+ it('emitting modes + fallback all emit to _repl', async () => {
188
189
  const db = new BodDB({
189
190
  path: ':memory:',
190
191
  sweepInterval: 0,
@@ -197,12 +198,15 @@ describe('Per-path replication topology', () => {
197
198
  },
198
199
  });
199
200
  instances.push(db);
200
- db.replication!.start();
201
+ await db.replication!.start();
201
202
 
202
203
  db.set('local/data', { v: 1 });
203
204
  db.set('telemetry/t1', { event: 'click' });
204
205
  db.set('other/data', { v: 2 });
205
206
 
207
+ // Wait for deferred setTimeout emits
208
+ await new Promise(r => setTimeout(r, 50));
209
+
206
210
  const repl = db.get('_repl');
207
211
  expect(repl).toBeTruthy();
208
212
  const events = Object.values(repl as Record<string, any>);
@@ -211,7 +215,43 @@ describe('Per-path replication topology', () => {
211
215
  expect(events.some((e: any) => e.path === 'other/data')).toBe(true);
212
216
  });
213
217
 
214
- it('writeonly path emits', () => {
218
+ it('replica and readonly modes do NOT emit to _repl', async () => {
219
+ const pPort = getPort();
220
+ const primary = createNode({ port: pPort, replication: { role: 'primary' } });
221
+ await primary.replication!.start();
222
+
223
+ const db = new BodDB({
224
+ path: ':memory:',
225
+ sweepInterval: 0,
226
+ replication: {
227
+ role: 'primary',
228
+ primaryUrl: `ws://localhost:${pPort}`,
229
+ paths: [
230
+ { path: 'cached', mode: 'replica' },
231
+ { path: 'feeds', mode: 'readonly' },
232
+ { path: 'local', mode: 'primary' },
233
+ ],
234
+ },
235
+ });
236
+ instances.push(db);
237
+ await db.replication!.start();
238
+
239
+ // Write directly (bypassing transport proxy) to test emit filtering
240
+ db.set('local/data', { v: 1 });
241
+ db.set('cached/data', { v: 2 });
242
+ db.set('feeds/data', { v: 3 });
243
+
244
+ // Wait for deferred emits
245
+ await new Promise(r => setTimeout(r, 50));
246
+
247
+ const repl = db.get('_repl');
248
+ const events = repl ? Object.values(repl as Record<string, any>) : [];
249
+ expect(events.some((e: any) => e.path === 'local/data')).toBe(true);
250
+ expect(events.some((e: any) => e.path === 'cached/data')).toBe(false);
251
+ expect(events.some((e: any) => e.path === 'feeds/data')).toBe(false);
252
+ });
253
+
254
+ it('writeonly path emits', async () => {
215
255
  const db = new BodDB({
216
256
  path: ':memory:',
217
257
  sweepInterval: 0,
@@ -221,9 +261,10 @@ describe('Per-path replication topology', () => {
221
261
  },
222
262
  });
223
263
  instances.push(db);
224
- db.replication!.start();
264
+ await db.replication!.start();
225
265
 
226
266
  db.set('telemetry/t1', { event: 'click' });
267
+ await new Promise(r => setTimeout(r, 50));
227
268
  const repl = db.get('_repl');
228
269
  expect(repl).toBeTruthy();
229
270
  const events = Object.values(repl as Record<string, any>);
@@ -796,7 +837,7 @@ describe('Per-path replication topology', () => {
796
837
  it('sync path written locally AND emitted (end-to-end)', async () => {
797
838
  const pPort = getPort();
798
839
  const primary = createNode({ port: pPort, replication: { role: 'primary' } });
799
- primary.replication!.start();
840
+ await primary.replication!.start();
800
841
 
801
842
  const rPort = getPort();
802
843
  const replica = createNode({
@@ -813,6 +854,7 @@ describe('Per-path replication topology', () => {
813
854
  replica.set('config/theme', 'dark');
814
855
  expect(replica.get('config/theme')).toBe('dark');
815
856
 
857
+ await new Promise(r => setTimeout(r, 50));
816
858
  const repl = replica.get('_repl');
817
859
  expect(repl).toBeTruthy();
818
860
  const events = Object.values(repl as Record<string, any>);
@@ -8,6 +8,7 @@ let nextPort = 24400 + Math.floor(Math.random() * 1000);
8
8
  describe('ReplicationEngine', () => {
9
9
  const instances: BodDB[] = [];
10
10
  const clients: BodClient[] = [];
11
+ const tick = () => new Promise(r => setTimeout(r, 50));
11
12
 
12
13
  afterEach(() => {
13
14
  for (const c of clients) c.disconnect();
@@ -48,7 +49,7 @@ describe('ReplicationEngine', () => {
48
49
  }
49
50
 
50
51
  // 1. Primary emits events to _repl on set/delete/update/push
51
- it('primary emits replication events on set/delete/update/push', () => {
52
+ it('primary emits replication events on set/delete/update/push', async () => {
52
53
  const db = new BodDB({
53
54
  path: ':memory:',
54
55
  sweepInterval: 0,
@@ -61,6 +62,7 @@ describe('ReplicationEngine', () => {
61
62
  db.update({ 'users/u2/age': 30 });
62
63
  db.delete('users/u1');
63
64
  db.push('logs', { msg: 'hello' });
65
+ await tick();
64
66
 
65
67
  const replData = db.get('_repl');
66
68
  expect(replData).toBeTruthy();
@@ -76,7 +78,7 @@ describe('ReplicationEngine', () => {
76
78
  });
77
79
 
78
80
  // 2. Loop prevention — _replaying flag prevents re-emission
79
- it('does not emit events when replaying', () => {
81
+ it('does not emit events when replaying', async () => {
80
82
  const db = new BodDB({
81
83
  path: ':memory:',
82
84
  sweepInterval: 0,
@@ -88,13 +90,14 @@ describe('ReplicationEngine', () => {
88
90
  db.setReplaying(true);
89
91
  db.set('users/u1', { name: 'Test' });
90
92
  db.setReplaying(false);
93
+ await tick();
91
94
 
92
95
  const replData = db.get('_repl');
93
96
  expect(replData).toBeNull();
94
97
  });
95
98
 
96
99
  // 3. Excluded prefixes are not replicated
97
- it('skips excluded prefixes', () => {
100
+ it('skips excluded prefixes', async () => {
98
101
  const db = new BodDB({
99
102
  path: ':memory:',
100
103
  sweepInterval: 0,
@@ -105,6 +108,7 @@ describe('ReplicationEngine', () => {
105
108
 
106
109
  db.set('internal/config', { foo: 1 });
107
110
  db.set('users/u1', { name: 'Eli' });
111
+ await tick();
108
112
 
109
113
  const replData = db.get('_repl') as Record<string, any>;
110
114
  const entries = Object.values(replData);
@@ -160,7 +164,7 @@ describe('ReplicationEngine', () => {
160
164
  });
161
165
 
162
166
  // 7. Transaction events are replicated
163
- it('transaction write events are replicated', () => {
167
+ it('transaction write events are replicated', async () => {
164
168
  const db = new BodDB({
165
169
  path: ':memory:',
166
170
  sweepInterval: 0,
@@ -173,6 +177,7 @@ describe('ReplicationEngine', () => {
173
177
  tx.set('a/1', 'one');
174
178
  tx.set('a/2', 'two');
175
179
  });
180
+ await tick();
176
181
 
177
182
  const replData = db.get('_repl') as Record<string, any>;
178
183
  const entries = Object.values(replData);
@@ -211,7 +216,7 @@ describe('ReplicationEngine', () => {
211
216
  });
212
217
 
213
218
  // 10. Multi-path update exclusion check (all paths checked, not just first)
214
- it('update with mixed excluded/non-excluded paths emits only non-excluded', () => {
219
+ it('update with mixed excluded/non-excluded paths emits only non-excluded', async () => {
215
220
  const db = new BodDB({
216
221
  path: ':memory:',
217
222
  sweepInterval: 0,
@@ -221,6 +226,7 @@ describe('ReplicationEngine', () => {
221
226
  db.replication!.start();
222
227
 
223
228
  db.update({ '_internal/x': 1, 'data/y': 2 });
229
+ await tick();
224
230
 
225
231
  const replData = db.get('_repl') as Record<string, any>;
226
232
  const entries = Object.values(replData);
@@ -229,7 +235,7 @@ describe('ReplicationEngine', () => {
229
235
  });
230
236
 
231
237
  // 11. Sweep fires delete events for expired paths
232
- it('sweep expired paths are replicated as deletes', () => {
238
+ it('sweep expired paths are replicated as deletes', async () => {
233
239
  const db = new BodDB({
234
240
  path: ':memory:',
235
241
  sweepInterval: 0,
@@ -242,6 +248,7 @@ describe('ReplicationEngine', () => {
242
248
  // Force expiry by manipulating the DB directly
243
249
  db.storage.db.prepare('UPDATE nodes SET expires_at = 1 WHERE path LIKE ?').run('sessions/s1%');
244
250
  db.sweep();
251
+ await tick();
245
252
 
246
253
  const replData = db.get('_repl') as Record<string, any>;
247
254
  const entries = Object.values(replData);
@@ -251,7 +258,7 @@ describe('ReplicationEngine', () => {
251
258
  });
252
259
 
253
260
  // 12. Recursion guard — emit doesn't infinite loop
254
- it('emit does not cause infinite recursion', () => {
261
+ it('emit does not cause infinite recursion', async () => {
255
262
  const db = new BodDB({
256
263
  path: ':memory:',
257
264
  sweepInterval: 0,
@@ -262,6 +269,7 @@ describe('ReplicationEngine', () => {
262
269
 
263
270
  db.set('users/u1', 'test');
264
271
  expect(db.get('users/u1')).toBe('test');
272
+ await tick();
265
273
  const replData = db.get('_repl') as Record<string, any>;
266
274
  expect(Object.values(replData).length).toBe(1);
267
275
  });
@@ -333,6 +341,7 @@ describe('ReplicationEngine', () => {
333
341
  expect(local.get('ext/users/u1')).toEqual({ name: 'Eli' });
334
342
 
335
343
  local.set('mydata/x', 'hello');
344
+ await tick();
336
345
  const replData = local.get('_repl') as Record<string, any>;
337
346
  const entries = Object.values(replData);
338
347
  expect(entries.some((e: any) => e.path === 'mydata/x')).toBe(true);