dignity.js 0.7.0 → 0.8.0

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.
@@ -144,8 +144,10 @@ const bob = new DignityP2P({ nodeId: 'bob', networkAdapter: new InMemoryNetworkA
144
144
  helpers.track(alice, bob);
145
145
 
146
146
  let received = null;
147
- bob.on('directmessage', (event) => {
148
- received = event.payload;
147
+ bob.on('message', (event) => {
148
+ if (event.senderId === 'alice' && event.type === 'chat') {
149
+ received = event.payload;
150
+ }
149
151
  });
150
152
 
151
153
  await alice.start();
@@ -321,11 +323,13 @@ const rotationResult = await revokeAndRotateIdentity({
321
323
  await alice.start();
322
324
  await bob.start();
323
325
 
326
+ await alice.adoptDerivedIdentityKeyPair(gen1, { generation: 1 });
324
327
  bob.registerPeerPublicKey('alice', keyPairToPublicBundle(gen1), { generation: 1 });
325
328
  log('bob gen before:', bob.getPeerIdentityGeneration('alice'));
326
329
 
327
- await alice.adoptDerivedIdentityKeyPair(rotationResult.nextKeyPair, { generation: 2 });
330
+ // Broadcast while alice still signs with gen-1 keys; then adopt gen-2 locally.
328
331
  await alice.broadcastIdentityRotation(rotationResult.rotation, { broadcastScope: 'identity:alice' });
332
+ await alice.adoptDerivedIdentityKeyPair(rotationResult.nextKeyPair, { generation: 2 });
329
333
  await helpers.sleep(50);
330
334
 
331
335
  log('bob gen after:', bob.getPeerIdentityGeneration('alice'));
@@ -334,5 +338,61 @@ log('keys upgraded:', bob.getPeerIdentityState('alice')?.publicKey?.signingPubli
334
338
 
335
339
  await alice.stop();
336
340
  await bob.stop();`
341
+ },
342
+ {
343
+ id: 'cqrs-replica',
344
+ title: 'CQRS — publisher + query replica',
345
+ description: 'Domain events from a tiered PeerGroup hydrate a read-only DignityQueryReplica.',
346
+ code: `const { DignityP2P, DignityQueryReplica, InMemoryNetworkHub, InMemoryNetworkAdapter } = dignity;
347
+
348
+ const hub = new InMemoryNetworkHub();
349
+ const security = helpers.fastSecurity();
350
+
351
+ const publisher = new DignityP2P({
352
+ nodeId: 'publisher',
353
+ networkAdapter: new InMemoryNetworkAdapter(hub),
354
+ security
355
+ });
356
+ const reader = new DignityP2P({
357
+ nodeId: 'reader',
358
+ networkAdapter: new InMemoryNetworkAdapter(hub),
359
+ security
360
+ });
361
+
362
+ helpers.track(publisher, reader);
363
+ await publisher.start();
364
+ await reader.start();
365
+
366
+ await publisher.joinPeerGroup('feed:demo', {
367
+ role: 'publisher',
368
+ tiered: true,
369
+ liveCap: 100,
370
+ domainEvents: true,
371
+ fanout: 2,
372
+ maxActivePeers: 4
373
+ });
374
+
375
+ const replica = new DignityQueryReplica(reader, {
376
+ groupId: 'feed:demo',
377
+ collections: ['posts'],
378
+ publisherId: 'publisher'
379
+ });
380
+ await replica.start({ bootstrapPeerIds: ['publisher'] });
381
+ await helpers.sleep(40);
382
+
383
+ await publisher.create('posts', { text: 'CQRS demo post' }, {
384
+ id: 'p1',
385
+ peerGroupId: 'feed:demo'
386
+ });
387
+ await helpers.sleep(60);
388
+
389
+ const record = replica.read('posts', 'p1');
390
+ log('replica read:', JSON.stringify(record?.data));
391
+ log('chain ok:', replica.verifyChain().ok);
392
+ log('stats:', JSON.stringify(replica.getViewStats()));
393
+
394
+ await replica.stop();
395
+ await publisher.stop();
396
+ await reader.stop();`
337
397
  }
338
398
  ];
@@ -155,20 +155,68 @@
155
155
  color: #cf222e;
156
156
  }
157
157
 
158
- #code-editor {
158
+ .playground-editor-wrap {
159
+ position: relative;
159
160
  flex: 1;
161
+ min-height: 360px;
162
+ overflow: hidden;
163
+ display: grid;
164
+ background: var(--bg);
165
+ }
166
+
167
+ .playground-editor-highlight,
168
+ #code-editor {
169
+ grid-area: 1 / 1;
160
170
  width: 100%;
171
+ height: 100%;
161
172
  min-height: 360px;
162
173
  margin: 0;
163
174
  padding: 16px;
164
175
  border: 0;
165
- resize: none;
166
176
  font-family: var(--font-mono);
167
177
  font-size: 0.875rem;
168
178
  line-height: 1.55;
169
- color: var(--text);
170
- background: var(--bg);
171
179
  tab-size: 2;
180
+ overflow: auto;
181
+ white-space: pre;
182
+ word-wrap: normal;
183
+ overflow-wrap: normal;
184
+ box-sizing: border-box;
185
+ }
186
+
187
+ .playground-editor-highlight {
188
+ pointer-events: none;
189
+ z-index: 0;
190
+ background: var(--bg);
191
+ color: var(--text);
192
+ }
193
+
194
+ .playground-editor-highlight code {
195
+ display: block;
196
+ min-height: calc(100% - 32px);
197
+ padding: 0;
198
+ margin: 0;
199
+ font: inherit;
200
+ line-height: inherit;
201
+ background: transparent !important;
202
+ border: 0 !important;
203
+ border-radius: 0 !important;
204
+ color: inherit;
205
+ }
206
+
207
+ .playground-editor-highlight code.hljs {
208
+ padding: 0 !important;
209
+ border: 0 !important;
210
+ border-left: 0 !important;
211
+ }
212
+
213
+ #code-editor {
214
+ resize: none;
215
+ z-index: 1;
216
+ color: transparent !important;
217
+ -webkit-text-fill-color: transparent !important;
218
+ caret-color: var(--text);
219
+ background: transparent;
172
220
  }
173
221
 
174
222
  #code-editor:focus {
@@ -176,6 +224,24 @@
176
224
  box-shadow: inset 0 0 0 2px var(--accent-soft);
177
225
  }
178
226
 
227
+ #code-editor::selection {
228
+ background: rgba(91, 127, 255, 0.35);
229
+ color: transparent;
230
+ -webkit-text-fill-color: transparent;
231
+ }
232
+
233
+ @media (prefers-color-scheme: dark) {
234
+ .playground-editor-wrap,
235
+ .playground-editor-highlight {
236
+ background: #0d1117;
237
+ color: #e6edf3;
238
+ }
239
+
240
+ #code-editor {
241
+ caret-color: #e6edf3;
242
+ }
243
+ }
244
+
179
245
  #output {
180
246
  flex: 1;
181
247
  min-height: 360px;
@@ -6,6 +6,8 @@ const els = {
6
6
  featureSelect: document.getElementById('feature-select'),
7
7
  featureDesc: document.getElementById('feature-desc'),
8
8
  editor: document.getElementById('code-editor'),
9
+ highlightCode: document.getElementById('code-highlight'),
10
+ highlightPre: document.querySelector('.playground-editor-highlight'),
9
11
  runBtn: document.getElementById('run-btn'),
10
12
  resetBtn: document.getElementById('reset-btn'),
11
13
  clearBtn: document.getElementById('clear-btn'),
@@ -13,6 +15,8 @@ const els = {
13
15
  status: document.getElementById('run-status')
14
16
  };
15
17
 
18
+ let highlightTimer = null;
19
+
16
20
  let activeCleanup = null;
17
21
  let running = false;
18
22
 
@@ -26,10 +30,48 @@ function populateFeatureSelect() {
26
30
  ).join('');
27
31
  }
28
32
 
33
+ function syncEditorScroll() {
34
+ if (!els.highlightPre) {
35
+ return;
36
+ }
37
+ els.highlightPre.scrollTop = els.editor.scrollTop;
38
+ els.highlightPre.scrollLeft = els.editor.scrollLeft;
39
+ }
40
+
41
+ function updateHighlight() {
42
+ if (!els.highlightCode || typeof hljs === 'undefined') {
43
+ return;
44
+ }
45
+
46
+ const source = els.editor.value;
47
+ els.highlightCode.textContent = source.endsWith('\n') ? source : `${source}\n`;
48
+
49
+ if (typeof hljs.highlight === 'function') {
50
+ const { value } = hljs.highlight(source, { language: 'javascript' });
51
+ els.highlightCode.innerHTML = value;
52
+ els.highlightCode.classList.add('hljs', 'language-javascript');
53
+ } else {
54
+ hljs.highlightElement(els.highlightCode);
55
+ }
56
+
57
+ syncEditorScroll();
58
+ }
59
+
60
+ function scheduleHighlight() {
61
+ if (highlightTimer) {
62
+ cancelAnimationFrame(highlightTimer);
63
+ }
64
+ highlightTimer = requestAnimationFrame(() => {
65
+ highlightTimer = null;
66
+ updateHighlight();
67
+ });
68
+ }
69
+
29
70
  function loadDemo(demo, { pushHash = true } = {}) {
30
71
  els.featureSelect.value = demo.id;
31
72
  els.featureDesc.textContent = demo.description;
32
73
  els.editor.value = demo.code;
74
+ scheduleHighlight();
33
75
  if (pushHash) {
34
76
  history.replaceState(null, '', `#${demo.id}`);
35
77
  }
@@ -151,7 +193,10 @@ async function runCode() {
151
193
  }
152
194
  }
153
195
 
154
- function initEditorTabs() {
196
+ function initEditor() {
197
+ els.editor.addEventListener('input', scheduleHighlight);
198
+ els.editor.addEventListener('scroll', syncEditorScroll);
199
+
155
200
  els.editor.addEventListener('keydown', (event) => {
156
201
  if (event.key !== 'Tab') {
157
202
  return;
@@ -161,6 +206,7 @@ function initEditorTabs() {
161
206
  const next = `${value.slice(0, selectionStart)} ${value.slice(selectionEnd)}`;
162
207
  els.editor.value = next;
163
208
  els.editor.selectionStart = els.editor.selectionEnd = selectionStart + 2;
209
+ scheduleHighlight();
164
210
  });
165
211
  }
166
212
 
@@ -172,7 +218,8 @@ function initFromHash() {
172
218
 
173
219
  populateFeatureSelect();
174
220
  initFromHash();
175
- initEditorTabs();
221
+ initEditor();
222
+ updateHighlight();
176
223
 
177
224
  els.featureSelect.addEventListener('change', () => {
178
225
  loadDemo(getDemoById(els.featureSelect.value));
@@ -14,7 +14,7 @@
14
14
  --green: #1a7f37;
15
15
  --green-bg: #dafbe1;
16
16
  --sidebar-width: 280px;
17
- --header-height: 56px;
17
+ --header-height: 64px;
18
18
  --content-max: 920px;
19
19
  --radius: 8px;
20
20
  --shadow: 0 1px 3px rgba(27, 31, 36, 0.08);
@@ -135,7 +135,7 @@ pre code.hljs {
135
135
  }
136
136
 
137
137
  .site-header__brand img {
138
- height: 40px;
138
+ height: 54px;
139
139
  width: auto;
140
140
  }
141
141
 
package/docs/index.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <meta name="description" content="dignity.js v0.6.0 — REST-like P2P object API for decentralized JavaScript applications." />
6
+ <meta name="description" content="dignity.js v0.8.0 — REST-like P2P object API for decentralized JavaScript applications." />
7
7
  <title>dignity.js · Documentation</title>
8
8
  <link rel="icon" href="./assets/favicon.svg" type="image/svg+xml" />
9
9
  <link rel="icon" href="./favicon.ico" sizes="32x32" />
@@ -16,7 +16,7 @@
16
16
  <a class="site-header__brand" href="#overview">
17
17
  <img src="./assets/dignity-logo.svg" alt="" width="344" height="80" />
18
18
  <!-- <span>dignity.js</span> -->
19
- <span class="site-header__version">v0.6.0</span>
19
+ <span class="site-header__version">v0.8.0</span>
20
20
  </a>
21
21
  <div class="site-header__links">
22
22
  <a href="https://www.npmjs.com/package/dignity.js" target="_blank" rel="noopener noreferrer">npm</a>
@@ -47,6 +47,7 @@
47
47
  <a href="#object-api">Object API</a>
48
48
  <a href="#discovery">Room discovery</a>
49
49
  <a href="#peer-groups">PeerGroup gossip</a>
50
+ <a href="#cqrs-tiers">CQRS tiers</a>
50
51
  <a href="#messaging">Direct messaging</a>
51
52
  <a href="#concurrency">Concurrency</a>
52
53
  <a href="#content-hashes">Content hashes</a>
@@ -96,6 +97,10 @@
96
97
  <h3>Content hashes</h3>
97
98
  <p><code>record.hash</code> — <code>sha512:</code> digests over canonical <code>data</code> via <code>stableStringify</code>.</p>
98
99
  </article>
100
+ <article class="card">
101
+ <h3>CQRS PeerGroups (v0.8+)</h3>
102
+ <p>Live + bulk tiers, signed domain events, and <code>DignityQueryReplica</code> read views for massive audiences.</p>
103
+ </article>
99
104
  </div>
100
105
  </section>
101
106
 
@@ -480,7 +485,7 @@ await node.leaveDiscovery('team:red');</code></pre>
480
485
  bootstrapPeerIds: [hostPeerId],
481
486
  fanout: 3,
482
487
  maxActivePeers: 8,
483
- maxHops: 6,
488
+ maxHops: 64,
484
489
  metadata: { role: 'spectator' }
485
490
  });
486
491
 
@@ -521,6 +526,14 @@ node.getPeerGroupStats();
521
526
  <td><code>record:snapshot</code></td>
522
527
  <td>Late-joiner catch-up</td>
523
528
  </tr>
529
+ <tr>
530
+ <td><code>domain:event</code></td>
531
+ <td>Signed, versioned write event (auto-published on CRUD when <code>domainEvents: true</code>)</td>
532
+ </tr>
533
+ <tr>
534
+ <td><code>domain:checkpoint</code></td>
535
+ <td>Bulk-tier catch-up anchor (<code>lastEventHash</code>, <code>recordCount</code>)</td>
536
+ </tr>
524
537
  <tr>
525
538
  <td>App-defined</td>
526
539
  <td>Custom payloads via <code>publishToPeerGroup</code> (emits <code>peergroupmessage</code>)</td>
@@ -539,6 +552,46 @@ node.getPeerGroupStats();
539
552
  <li>Set <code>relayEnabled: false</code> on a group to receive updates without re-forwarding.</li>
540
553
  </ul>
541
554
 
555
+ <h3 id="cqrs-tiers">CQRS tiers &amp; query replicas (v0.8+)</h3>
556
+ <p>
557
+ For audiences above ~5&nbsp;000 subscribers <em>per publisher</em>, use the command/query split:
558
+ publishers write locally and emit signed domain events; subscribers maintain read-only materialized views.
559
+ </p>
560
+ <ul>
561
+ <li><strong>Live tier</strong> — first <code>liveCap</code> subscribers (default 5&nbsp;000) receive real-time gossip via <code>publishToPeerGroup</code>.</li>
562
+ <li><strong>Bulk tier</strong> — overflow subscribers receive batched updates via <code>publishPeerGroupBulk</code> and elected bulk relays.</li>
563
+ <li><strong>Domain events</strong> — every <code>create</code> / <code>update</code> / <code>remove</code> on a publisher group auto-emits <code>domain:event</code> with hash-chain linking.</li>
564
+ <li><strong>Query replica</strong> — <code>DignityQueryReplica</code> hydrates local <code>read</code> / <code>list</code> views without contacting the owner.</li>
565
+ </ul>
566
+ <div class="code-block">
567
+ <pre><code class="language-javascript">const { DignityP2P, DignityQueryReplica } = dignity;
568
+
569
+ // Publisher (command path)
570
+ await publisher.joinPeerGroup('feed:alice', {
571
+ role: 'publisher',
572
+ tiered: true,
573
+ liveCap: 5000,
574
+ domainEvents: true
575
+ });
576
+ await publisher.create('posts', { text: 'hello' }, {
577
+ id: 'p1',
578
+ peerGroupId: 'feed:alice' // auto-publishes domain:event
579
+ });
580
+
581
+ // Read-only replica (query path)
582
+ const replica = new DignityQueryReplica(reader, {
583
+ groupId: 'feed:alice',
584
+ collections: ['posts'],
585
+ publisherId: 'alice'
586
+ });
587
+ await replica.start({ bootstrapPeerIds: ['alice'] });
588
+ replica.read('posts', 'p1');
589
+ replica.verifyChain(); // hash-chain consistency</code></pre>
590
+ </div>
591
+ <p>
592
+ Try the <a href="./playground/index.html">live playground</a> demo <em>CQRS — publisher + query replica</em>.
593
+ </p>
594
+
542
595
  <h3>Chess spectators</h3>
543
596
  <p>
544
597
  The <a href="./chess/index.html">3D Chess demo</a> mixes both replication modes:
@@ -573,8 +626,8 @@ node.getPeerGroupStats();
573
626
  </tr>
574
627
  <tr>
575
628
  <td><code>maxHops</code></td>
576
- <td>6</td>
577
- <td>Covers large graphs with low diameter</td>
629
+ <td>64</td>
630
+ <td>Covers large graphs (fanout³×⁶⁴) without per-group tuning</td>
578
631
  </tr>
579
632
  <tr>
580
633
  <td><code>globalMaxOpenConnections</code></td>
@@ -591,6 +644,9 @@ node.getPeerGroupStats();
591
644
  </div>
592
645
 
593
646
  <h3>Roadmap</h3>
647
+ <p><strong>v0.8.0 (current)</strong> — CQRS tiered PeerGroups, domain events, DignityQueryReplica, hash chains, bulk relay, maxHops 64.</p>
648
+ <p><strong>v0.7.1</strong> — live docs playground, demo fixes, syntax highlighting.</p>
649
+ <p><strong>v0.7.0 (shipped)</strong> — credential-derived keys, identity rotation, PeerGroup hardening, stress harness.</p>
594
650
  <p><strong>v0.6.0 (shipped)</strong> — core gossip, chess spectators, unit + e2e tests.</p>
595
651
  <p><strong>v0.6.x polish</strong> — <code>subscribeObjectFeed</code> wrapper, connection LRU trim, IndexedDB joined-group persistence, React <code>usePeerGroup</code> hook, 50+ node integration test.</p>
596
652
  <p><strong>Future</strong> — publisher rate limits, partial snapshot / delta feeds, cross-group fan-in metrics.</p>
@@ -912,7 +968,7 @@ const pool = createDefaultSignalingPool({
912
968
  <tbody>
913
969
  <tr>
914
970
  <td><code>dignity.js</code></td>
915
- <td><code>DignityP2P</code>, <code>IndexedDBPersistence</code>, signaling providers, in-memory adapters, security utilities</td>
971
+ <td><code>DignityP2P</code>, <code>DignityQueryReplica</code>, <code>IndexedDBPersistence</code>, signaling providers, in-memory adapters, PeerGroup + CQRS helpers (<code>domain-events</code>, <code>peer-group-tiers</code>, <code>bulk-relay</code>), security utilities</td>
916
972
  </tr>
917
973
  <tr>
918
974
  <td><code>dignity.js/react</code></td>
@@ -963,10 +1019,10 @@ const pool = createDefaultSignalingPool({
963
1019
  <section id="development" class="section">
964
1020
  <h2>Development</h2>
965
1021
  <div class="code-block">
966
- <pre><code class="language-bash"># Run tests (237+ passing, ~91% line coverage)
1022
+ <pre><code class="language-bash"># Run tests (289+ passing)
967
1023
  npm test
968
1024
 
969
- # PeerGroup unit + e2e tests only
1025
+ # PeerGroup + CQRS unit + integration tests
970
1026
  npm run test:peer-group
971
1027
 
972
1028
  # Build browser + Node bundles
@@ -983,7 +1039,7 @@ npm run example:chess</code></pre>
983
1039
 
984
1040
  <footer class="site-footer">
985
1041
  <p>
986
- dignity.js v0.6.0 ·
1042
+ dignity.js v0.8.0 ·
987
1043
  <a href="https://github.com/jose-compu/dignity.js/blob/main/LICENSE">Apache 2.0</a> ·
988
1044
  <a href="https://github.com/jose-compu/dignity.js">GitHub</a> ·
989
1045
  <a href="https://www.npmjs.com/package/dignity.js">npm</a>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dignity.js",
3
- "version": "0.5.4",
3
+ "version": "0.8.0",
4
4
  "description": "REST-like object API over peer-to-peer replication",
5
5
  "resources": {
6
6
  "collections/{collection}/{id}": {
@@ -60,18 +60,31 @@
60
60
  "broadcastMessage": "custom app messages; options.connectToPeers"
61
61
  },
62
62
  "peerGroups": {
63
- "joinPeerGroup": "join scalable gossip group (multiplexed); options.fanout, maxActivePeers, bootstrapPeerIds",
63
+ "joinPeerGroup": "join scalable gossip group; options: fanout, maxActivePeers, maxHops (default 64), role, tiered, liveCap, tierMode, domainEvents, publisherId",
64
64
  "leavePeerGroup": "leave gossip group and scoped presence",
65
65
  "listPeerGroupMembers": "list presence in gossip:{groupId} scope",
66
- "publishToPeerGroup": "epidemic publish with bounded fanout; inner types: operation, record:snapshot, custom",
66
+ "publishToPeerGroup": "epidemic publish to live tier when tiered; inner types: operation, record:snapshot, domain:event, custom",
67
+ "publishPeerGroupBulk": "bulk-tier publish (publisher only); batched domain events and checkpoints",
68
+ "publishPeerGroupCheckpoint": "publish domain-event chain checkpoint to bulk tier",
67
69
  "publishRecordToPeerGroup": "publish normalized record snapshot into gossip group",
70
+ "getPeerGroupConfig": "returns joined group config including tier and domainEvents flags",
68
71
  "getPeerGroupStats": "{ joinedGroups, seenGossipCount, openConnectionCount, globalMaxOpenConnections }"
72
+ },
73
+ "cqrs": {
74
+ "DignityQueryReplica": "read-only materialized view from domain events; methods: start, stop, read, list, verifyChain, getViewStats",
75
+ "domainEvents": "operationToDomainEvent, verifyDomainEvent, verifyEventChain, DOMAIN_EVENT_SCHEMA_VERSION",
76
+ "tiers": "assignPeerGroupTier, DEFAULT_LIVE_CAP (5000), filterPeersByTier",
77
+ "bulkRelay": "electBulkRelays, DEFAULT_BULK_RELAY_COUNT"
69
78
  }
70
79
  },
71
80
  "events": {
72
81
  "change": "object create/update/delete/snapshot applied",
73
82
  "conflict": "local or remote version mismatch",
74
- "warning": "orphan-operation, peer-connect-failed, presence failures, content-hash-mismatch",
83
+ "warning": "orphan-operation, peer-connect-failed, presence failures, content-hash-mismatch, domain-event-rejected",
84
+ "domainevent": "signed domain event applied or emitted locally",
85
+ "chainbroken": "domain event hash chain verification failed",
86
+ "bulkrelaychanged": "bulk relay peer set changed for a tiered group",
87
+ "checkpointpublished": "publisher published domain-event checkpoint",
75
88
  "peerdiscovered": "peer joined discovery scope",
76
89
  "peerleft": "peer left or timed out",
77
90
  "message": "custom decrypted message received"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dignity.js",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "P2P object API for decentralized JavaScript applications",
5
5
  "homepage": "https://jose-compu.github.io/dignity.js/",
6
6
  "repository": {
@@ -41,7 +41,7 @@
41
41
  "scripts": {
42
42
  "test": "jest --coverage",
43
43
  "test:unit": "jest tests/unit --runInBand",
44
- "test:peer-group": "jest tests/unit/peer-group-gossip.test.js tests/unit/peer-group-helpers.test.js tests/integration/peer-group-gossip-e2e.test.js --runInBand --coverage=false",
44
+ "test:peer-group": "jest tests/unit/peer-group-gossip.test.js tests/unit/peer-group-helpers.test.js tests/unit/domain-events.test.js tests/unit/peer-group-tiers.test.js tests/unit/query-replica.test.js tests/unit/view-consistency.test.js tests/integration/peer-group-gossip-e2e.test.js tests/integration/cqrs-tiered-peergroup.test.js --runInBand --coverage=false",
45
45
  "test:stress-peer-group": "RUN_STRESS_TESTS=1 jest tests/stress/peer-group-scale.test.js --runInBand --coverage=false",
46
46
  "stress:peer-group": "node scripts/stress-peer-group.js",
47
47
  "test:cloudflare-live": "RUN_CLOUDFLARE_LIVE_TESTS=1 jest tests/integration/cloudflare-signaling-live.test.js --runInBand",