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.
- package/README.md +34 -0
- package/dist/dignity.cjs.js +899 -8
- package/dist/dignity.cjs.js.map +4 -4
- package/dist/dignity.esm.js +899 -8
- package/dist/dignity.esm.js.map +3 -3
- package/dist/dignity.min.js +18 -18
- package/docs/assets/dignity.esm.js +899 -8
- package/docs/assets/playground-demos.js +63 -3
- package/docs/assets/playground.css +70 -4
- package/docs/assets/playground.js +49 -2
- package/docs/assets/styles.css +2 -2
- package/docs/index.html +65 -9
- package/docs/openapi-like.json +17 -4
- package/package.json +2 -2
- package/src/core/dignity-p2p.js +428 -6
- package/src/cqrs/bulk-relay.js +35 -0
- package/src/cqrs/domain-events.js +285 -0
- package/src/cqrs/peer-group-tiers.js +46 -0
- package/src/cqrs/query-replica.js +213 -0
- package/src/gossip/peer-group.js +1 -1
- package/src/index.js +34 -1
|
@@ -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('
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
221
|
+
initEditor();
|
|
222
|
+
updateHighlight();
|
|
176
223
|
|
|
177
224
|
els.featureSelect.addEventListener('change', () => {
|
|
178
225
|
loadDemo(getDemoById(els.featureSelect.value));
|
package/docs/assets/styles.css
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
--green: #1a7f37;
|
|
15
15
|
--green-bg: #dafbe1;
|
|
16
16
|
--sidebar-width: 280px;
|
|
17
|
-
--header-height:
|
|
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:
|
|
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
|
+
<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.
|
|
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:
|
|
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 & query replicas (v0.8+)</h3>
|
|
556
|
+
<p>
|
|
557
|
+
For audiences above ~5 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 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>
|
|
577
|
-
<td>Covers large graphs
|
|
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 (
|
|
1022
|
+
<pre><code class="language-bash"># Run tests (289+ passing)
|
|
967
1023
|
npm test
|
|
968
1024
|
|
|
969
|
-
# PeerGroup unit +
|
|
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.
|
|
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>
|
package/docs/openapi-like.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dignity.js",
|
|
3
|
-
"version": "0.
|
|
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
|
|
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
|
|
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.
|
|
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",
|