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.
@@ -3449,7 +3449,7 @@ var require_peer_group = __commonJS({
3449
3449
  var DEFAULT_PEER_GROUP_OPTIONS = {
3450
3450
  fanout: 3,
3451
3451
  maxActivePeers: 8,
3452
- maxHops: 6,
3452
+ maxHops: 64,
3453
3453
  relayEnabled: true
3454
3454
  };
3455
3455
  function peerGroupScope(groupId) {
@@ -3501,6 +3501,327 @@ var require_peer_group = __commonJS({
3501
3501
  }
3502
3502
  });
3503
3503
 
3504
+ // src/cqrs/domain-events.js
3505
+ var require_domain_events = __commonJS({
3506
+ "src/cqrs/domain-events.js"(exports, module) {
3507
+ var nacl = require_nacl_fast();
3508
+ var naclUtil = require_nacl_util();
3509
+ var { stableStringify } = require_message_security_service();
3510
+ var DOMAIN_EVENT_SCHEMA_VERSION = 1;
3511
+ var OPERATION_KIND_TO_EVENT_KIND = {
3512
+ create: "record:created",
3513
+ update: "record:updated",
3514
+ delete: "record:removed",
3515
+ "transfer-ownership": "ownership:transferred"
3516
+ };
3517
+ function computeContentHash(data) {
3518
+ const canonical = stableStringify(data || {});
3519
+ const bytes = naclUtil.decodeUTF8(canonical);
3520
+ const hash = nacl.hash(bytes);
3521
+ const hex = Array.from(hash, (b) => b.toString(16).padStart(2, "0")).join("");
3522
+ return `sha512:${hex}`;
3523
+ }
3524
+ function canonicalEventBody(event) {
3525
+ return stableStringify({
3526
+ schemaVersion: event.schemaVersion,
3527
+ eventId: event.eventId,
3528
+ groupId: event.groupId,
3529
+ publisherId: event.publisherId,
3530
+ kind: event.kind,
3531
+ collectionName: event.collectionName,
3532
+ id: event.id,
3533
+ payload: event.payload,
3534
+ timestamp: event.timestamp,
3535
+ baseVersion: event.baseVersion,
3536
+ prevHash: event.prevHash || null,
3537
+ newOwnerId: event.newOwnerId || null
3538
+ });
3539
+ }
3540
+ function computeEventHash(event) {
3541
+ const canonical = canonicalEventBody(event);
3542
+ const bytes = naclUtil.decodeUTF8(canonical);
3543
+ const hash = nacl.hash(bytes);
3544
+ const hex = Array.from(hash, (b) => b.toString(16).padStart(2, "0")).join("");
3545
+ return `sha512:${hex}`;
3546
+ }
3547
+ function operationToDomainEvent(operation, { publisherId, groupId, prevHash, eventIdGenerator }) {
3548
+ if (!operation || !publisherId || !groupId) {
3549
+ throw new Error("operationToDomainEvent requires operation, publisherId, and groupId");
3550
+ }
3551
+ const kind = OPERATION_KIND_TO_EVENT_KIND[operation.kind];
3552
+ if (!kind) {
3553
+ throw new Error(`Unsupported operation kind for domain event: ${operation.kind}`);
3554
+ }
3555
+ const event = {
3556
+ schemaVersion: DOMAIN_EVENT_SCHEMA_VERSION,
3557
+ eventId: eventIdGenerator ? eventIdGenerator() : `${Date.now()}-${Math.random().toString(16).slice(2)}`,
3558
+ groupId,
3559
+ publisherId,
3560
+ kind,
3561
+ collectionName: operation.collectionName,
3562
+ id: operation.id,
3563
+ payload: operation.payload || {},
3564
+ timestamp: operation.timestamp,
3565
+ baseVersion: operation.baseVersion || null,
3566
+ prevHash: prevHash || null,
3567
+ newOwnerId: operation.newOwnerId || null,
3568
+ eventHash: null,
3569
+ signature: null
3570
+ };
3571
+ event.eventHash = computeEventHash(event);
3572
+ return event;
3573
+ }
3574
+ function signDomainEvent(event, signingSecretKey) {
3575
+ if (!signingSecretKey) {
3576
+ return { ...event };
3577
+ }
3578
+ const unsigned = { ...event, signature: null };
3579
+ const eventHash = computeEventHash(unsigned);
3580
+ const signature = nacl.sign.detached(
3581
+ naclUtil.decodeUTF8(eventHash),
3582
+ signingSecretKey
3583
+ );
3584
+ return {
3585
+ ...unsigned,
3586
+ eventHash,
3587
+ signature: naclUtil.encodeBase64(signature)
3588
+ };
3589
+ }
3590
+ function verifyDomainEventSignature(event, signingPublicKey) {
3591
+ if (!event || !event.eventHash) {
3592
+ return { ok: false, reason: "missing-event-hash" };
3593
+ }
3594
+ const recomputed = computeEventHash({ ...event, signature: null });
3595
+ if (recomputed !== event.eventHash) {
3596
+ return { ok: false, reason: "event-hash-mismatch" };
3597
+ }
3598
+ if (!event.signature) {
3599
+ return { ok: true, unsigned: true };
3600
+ }
3601
+ if (!signingPublicKey) {
3602
+ return { ok: false, reason: "missing-public-key" };
3603
+ }
3604
+ const keyBytes = typeof signingPublicKey === "string" ? naclUtil.decodeBase64(signingPublicKey) : signingPublicKey;
3605
+ const valid = nacl.sign.detached.verify(
3606
+ naclUtil.decodeUTF8(event.eventHash),
3607
+ naclUtil.decodeBase64(event.signature),
3608
+ keyBytes
3609
+ );
3610
+ return valid ? { ok: true } : { ok: false, reason: "invalid-signature" };
3611
+ }
3612
+ function verifyDomainEvent(event, { signingPublicKey, supportedVersions } = {}) {
3613
+ if (!event || typeof event !== "object") {
3614
+ return { ok: false, reason: "invalid-event" };
3615
+ }
3616
+ const versions = supportedVersions || [DOMAIN_EVENT_SCHEMA_VERSION];
3617
+ if (!versions.includes(event.schemaVersion)) {
3618
+ return { ok: false, reason: "unsupported-schema-version", schemaVersion: event.schemaVersion };
3619
+ }
3620
+ if (!event.eventId || !event.groupId || !event.publisherId || !event.kind) {
3621
+ return { ok: false, reason: "missing-required-fields" };
3622
+ }
3623
+ return verifyDomainEventSignature(event, signingPublicKey);
3624
+ }
3625
+ function createEmptyView(collections = []) {
3626
+ const view = /* @__PURE__ */ new Map();
3627
+ for (const name of collections) {
3628
+ view.set(name, /* @__PURE__ */ new Map());
3629
+ }
3630
+ return view;
3631
+ }
3632
+ function ensureCollectionView(view, collectionName) {
3633
+ if (!view.has(collectionName)) {
3634
+ view.set(collectionName, /* @__PURE__ */ new Map());
3635
+ }
3636
+ return view.get(collectionName);
3637
+ }
3638
+ function applyDomainEventToView(view, event, { collectionsFilter } = {}) {
3639
+ if (!event || !event.collectionName) {
3640
+ return { applied: false, reason: "invalid-event" };
3641
+ }
3642
+ if (Array.isArray(collectionsFilter) && collectionsFilter.length > 0 && !collectionsFilter.includes(event.collectionName)) {
3643
+ return { applied: false, reason: "collection-filtered" };
3644
+ }
3645
+ const collection = ensureCollectionView(view, event.collectionName);
3646
+ if (event.kind === "record:created") {
3647
+ if (collection.has(event.id)) {
3648
+ return { applied: false, reason: "already-exists" };
3649
+ }
3650
+ collection.set(event.id, {
3651
+ id: event.id,
3652
+ ownerId: event.publisherId,
3653
+ data: { ...event.payload || {} },
3654
+ hash: computeContentHash(event.payload || {}),
3655
+ createdAt: event.timestamp,
3656
+ updatedAt: event.timestamp,
3657
+ deletedAt: null,
3658
+ version: 1
3659
+ });
3660
+ return { applied: true, kind: event.kind };
3661
+ }
3662
+ const current = collection.get(event.id);
3663
+ if (!current || current.deletedAt) {
3664
+ if (event.kind === "record:removed") {
3665
+ return { applied: false, reason: "not-found" };
3666
+ }
3667
+ return { applied: false, reason: "missing-record" };
3668
+ }
3669
+ if (event.kind === "record:updated") {
3670
+ if (typeof event.baseVersion === "number" && current.version !== event.baseVersion) {
3671
+ return { applied: false, reason: "version-conflict", currentVersion: current.version };
3672
+ }
3673
+ current.data = { ...current.data, ...event.payload || {} };
3674
+ current.hash = computeContentHash(current.data);
3675
+ current.updatedAt = event.timestamp;
3676
+ current.version += 1;
3677
+ return { applied: true, kind: event.kind };
3678
+ }
3679
+ if (event.kind === "record:removed") {
3680
+ if (typeof event.baseVersion === "number" && current.version !== event.baseVersion) {
3681
+ return { applied: false, reason: "version-conflict", currentVersion: current.version };
3682
+ }
3683
+ current.deletedAt = event.timestamp;
3684
+ current.version += 1;
3685
+ return { applied: true, kind: event.kind };
3686
+ }
3687
+ if (event.kind === "ownership:transferred") {
3688
+ if (typeof event.baseVersion === "number" && current.version !== event.baseVersion) {
3689
+ return { applied: false, reason: "version-conflict", currentVersion: current.version };
3690
+ }
3691
+ current.ownerId = event.newOwnerId;
3692
+ current.updatedAt = event.timestamp;
3693
+ current.version += 1;
3694
+ return { applied: true, kind: event.kind };
3695
+ }
3696
+ return { applied: false, reason: "unknown-kind" };
3697
+ }
3698
+ function verifyEventChain(events, { genesisHash = null } = {}) {
3699
+ if (!Array.isArray(events) || events.length === 0) {
3700
+ return { ok: true, length: 0 };
3701
+ }
3702
+ let expectedPrev = genesisHash;
3703
+ for (let index = 0; index < events.length; index += 1) {
3704
+ const event = events[index];
3705
+ const prevHash = event.prevHash || null;
3706
+ if (prevHash !== expectedPrev) {
3707
+ return {
3708
+ ok: false,
3709
+ reason: "chain-break",
3710
+ index,
3711
+ expectedPrev,
3712
+ actualPrev: prevHash
3713
+ };
3714
+ }
3715
+ const hashCheck = verifyDomainEventSignature(event, null);
3716
+ if (!hashCheck.ok) {
3717
+ return { ok: false, reason: hashCheck.reason, index };
3718
+ }
3719
+ expectedPrev = event.eventHash;
3720
+ }
3721
+ return { ok: true, length: events.length, lastHash: expectedPrev };
3722
+ }
3723
+ function buildCheckpoint(groupId, events, { publisherId } = {}) {
3724
+ const chain = verifyEventChain(events);
3725
+ return {
3726
+ schemaVersion: DOMAIN_EVENT_SCHEMA_VERSION,
3727
+ groupId,
3728
+ publisherId: publisherId || null,
3729
+ lastEventHash: chain.lastHash || null,
3730
+ recordCount: events.length,
3731
+ timestamp: Date.now()
3732
+ };
3733
+ }
3734
+ module.exports = {
3735
+ DOMAIN_EVENT_SCHEMA_VERSION,
3736
+ OPERATION_KIND_TO_EVENT_KIND,
3737
+ computeEventHash,
3738
+ operationToDomainEvent,
3739
+ signDomainEvent,
3740
+ verifyDomainEvent,
3741
+ verifyDomainEventSignature,
3742
+ createEmptyView,
3743
+ applyDomainEventToView,
3744
+ verifyEventChain,
3745
+ buildCheckpoint
3746
+ };
3747
+ }
3748
+ });
3749
+
3750
+ // src/cqrs/peer-group-tiers.js
3751
+ var require_peer_group_tiers = __commonJS({
3752
+ "src/cqrs/peer-group-tiers.js"(exports, module) {
3753
+ var DEFAULT_LIVE_CAP = 5e3;
3754
+ var DEFAULT_BULK_INTERVAL_MS = 3e4;
3755
+ function assignPeerGroupTier({ joinIndex, liveCap = DEFAULT_LIVE_CAP, requestedTier, role }) {
3756
+ if (role === "publisher") {
3757
+ return "live";
3758
+ }
3759
+ if (requestedTier === "live" || requestedTier === "bulk") {
3760
+ if (requestedTier === "live" && joinIndex >= liveCap) {
3761
+ return "bulk";
3762
+ }
3763
+ return requestedTier;
3764
+ }
3765
+ return joinIndex < liveCap ? "live" : "bulk";
3766
+ }
3767
+ function getPeerTier(peer) {
3768
+ return peer?.metadata?.peerGroupTier || peer?.peerGroupTier || null;
3769
+ }
3770
+ function filterPeersByTier(peers, tier) {
3771
+ if (!tier) {
3772
+ return peers;
3773
+ }
3774
+ return peers.filter((peer) => getPeerTier(peer) === tier);
3775
+ }
3776
+ function countLivePeers(peers) {
3777
+ return peers.filter((peer) => getPeerTier(peer) === "live").length;
3778
+ }
3779
+ function countBulkPeers(peers) {
3780
+ return peers.filter((peer) => getPeerTier(peer) === "bulk").length;
3781
+ }
3782
+ module.exports = {
3783
+ DEFAULT_LIVE_CAP,
3784
+ DEFAULT_BULK_INTERVAL_MS,
3785
+ assignPeerGroupTier,
3786
+ getPeerTier,
3787
+ filterPeersByTier,
3788
+ countLivePeers,
3789
+ countBulkPeers
3790
+ };
3791
+ }
3792
+ });
3793
+
3794
+ // src/cqrs/bulk-relay.js
3795
+ var require_bulk_relay = __commonJS({
3796
+ "src/cqrs/bulk-relay.js"(exports, module) {
3797
+ var { getPeerTier } = require_peer_group_tiers();
3798
+ var DEFAULT_BULK_RELAY_COUNT = 3;
3799
+ function electBulkRelays(peers, { count = DEFAULT_BULK_RELAY_COUNT } = {}) {
3800
+ const bulkPeers = peers.filter((peer) => getPeerTier(peer) === "bulk").map((peer) => peer.peerId || peer).filter(Boolean).sort();
3801
+ return bulkPeers.slice(0, Math.max(0, count));
3802
+ }
3803
+ function isBulkRelay(metadata) {
3804
+ return metadata?.bulkRelay === true;
3805
+ }
3806
+ function applyBulkRelayFlags(peers, relayPeerIds) {
3807
+ const relaySet = new Set(relayPeerIds);
3808
+ return peers.map((peer) => ({
3809
+ ...peer,
3810
+ metadata: {
3811
+ ...peer.metadata || {},
3812
+ bulkRelay: relaySet.has(peer.peerId)
3813
+ }
3814
+ }));
3815
+ }
3816
+ module.exports = {
3817
+ DEFAULT_BULK_RELAY_COUNT,
3818
+ electBulkRelays,
3819
+ isBulkRelay,
3820
+ applyBulkRelayFlags
3821
+ };
3822
+ }
3823
+ });
3824
+
3504
3825
  // src/core/dignity-p2p.js
3505
3826
  var require_dignity_p2p = __commonJS({
3506
3827
  "src/core/dignity-p2p.js"(exports, module) {
@@ -3521,8 +3842,24 @@ var require_dignity_p2p = __commonJS({
3521
3842
  var {
3522
3843
  DEFAULT_PEER_GROUP_OPTIONS,
3523
3844
  peerGroupScope,
3845
+ parsePeerGroupScope,
3524
3846
  selectFanoutPeers
3525
3847
  } = require_peer_group();
3848
+ var {
3849
+ operationToDomainEvent,
3850
+ signDomainEvent,
3851
+ verifyDomainEvent,
3852
+ applyDomainEventToView,
3853
+ createEmptyView,
3854
+ buildCheckpoint
3855
+ } = require_domain_events();
3856
+ var {
3857
+ DEFAULT_LIVE_CAP,
3858
+ DEFAULT_BULK_INTERVAL_MS,
3859
+ assignPeerGroupTier,
3860
+ filterPeersByTier
3861
+ } = require_peer_group_tiers();
3862
+ var { electBulkRelays } = require_bulk_relay();
3526
3863
  function computeContentHash(data) {
3527
3864
  const canonical = stableStringify(data || {});
3528
3865
  const bytes = naclUtil.decodeUTF8(canonical);
@@ -3566,6 +3903,10 @@ var require_dignity_p2p = __commonJS({
3566
3903
  this.gossipPublishMinIntervalMs = security && typeof security.gossipPublishMinIntervalMs === "number" ? security.gossipPublishMinIntervalMs : 0;
3567
3904
  this.lastGossipPublishAt = /* @__PURE__ */ new Map();
3568
3905
  this.maxAppliedOperations = security && typeof security.maxAppliedOperations === "number" ? security.maxAppliedOperations : 5e4;
3906
+ this.domainEventLogs = /* @__PURE__ */ new Map();
3907
+ this.lastEventHashByGroup = /* @__PURE__ */ new Map();
3908
+ this.bulkRelayByGroup = /* @__PURE__ */ new Map();
3909
+ this.replicaViews = /* @__PURE__ */ new Map();
3569
3910
  this.state = /* @__PURE__ */ new Map();
3570
3911
  this.appliedOperations = /* @__PURE__ */ new Map();
3571
3912
  this.boundMessageHandler = this.handleIncomingMessage.bind(this);
@@ -3704,6 +4045,7 @@ var require_dignity_p2p = __commonJS({
3704
4045
  payload: { ...data || {} }
3705
4046
  };
3706
4047
  this.applyOperation(operation);
4048
+ await this.maybePublishDomainEvent(operation, options);
3707
4049
  await this.broadcastMessage("operation", operation, {
3708
4050
  broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
3709
4051
  messageType: "operation",
@@ -3781,6 +4123,7 @@ var require_dignity_p2p = __commonJS({
3781
4123
  operation.collaboratorIds = this.normalizeCollaboratorIds(options.collaborators);
3782
4124
  }
3783
4125
  this.applyOperation(operation);
4126
+ await this.maybePublishDomainEvent(operation, options);
3784
4127
  await this.broadcastMessage("operation", operation, {
3785
4128
  broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
3786
4129
  messageType: "operation",
@@ -3835,6 +4178,7 @@ var require_dignity_p2p = __commonJS({
3835
4178
  keepPreviousOwnerAsCollaborator: options.keepAsCollaborator !== false
3836
4179
  };
3837
4180
  this.applyOperation(operation);
4181
+ await this.maybePublishDomainEvent(operation, options);
3838
4182
  await this.broadcastMessage("operation", operation, {
3839
4183
  broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
3840
4184
  messageType: "operation",
@@ -3866,6 +4210,7 @@ var require_dignity_p2p = __commonJS({
3866
4210
  baseVersion: existing.version
3867
4211
  };
3868
4212
  this.applyOperation(operation);
4213
+ await this.maybePublishDomainEvent(operation, options);
3869
4214
  await this.broadcastMessage("operation", operation, {
3870
4215
  broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
3871
4216
  messageType: "operation",
@@ -4156,9 +4501,16 @@ var require_dignity_p2p = __commonJS({
4156
4501
  }
4157
4502
  return [];
4158
4503
  }
4159
- selectPeerGroupFanout(groupId, count, excludePeerIds = []) {
4504
+ selectPeerGroupFanout(groupId, count, excludePeerIds = [], fanoutOptions = {}) {
4160
4505
  const scope = this.peerGroupScopeFor(groupId);
4161
- const peers = this.listPeers(scope, { includeSelf: false });
4506
+ const group = this.peerGroups.get(groupId);
4507
+ let peers = this.listPeers(scope, { includeSelf: false });
4508
+ if (group && group.tiered && fanoutOptions.tier) {
4509
+ peers = filterPeersByTier(peers, fanoutOptions.tier);
4510
+ }
4511
+ if (fanoutOptions.bulkRelayOnly) {
4512
+ peers = peers.filter((peer) => peer.metadata?.bulkRelay === true);
4513
+ }
4162
4514
  return selectFanoutPeers({
4163
4515
  peers,
4164
4516
  count,
@@ -4190,15 +4542,45 @@ var require_dignity_p2p = __commonJS({
4190
4542
  throw new Error("joinPeerGroup requires groupId");
4191
4543
  }
4192
4544
  const scope = this.peerGroupScopeFor(groupId);
4545
+ const role = options.role || options.metadata?.role || "subscriber";
4546
+ const tierMode = options.tierMode || "auto";
4547
+ const tiered = options.tiered === true || options.tierMode !== void 0 || options.role !== void 0 || typeof options.liveCap === "number";
4548
+ const liveCap = typeof options.liveCap === "number" ? options.liveCap : DEFAULT_LIVE_CAP;
4549
+ const existingMembers = this.listPeerGroupMembers(groupId, { includeSelf: false });
4550
+ const existingSubscriberCount = existingMembers.filter((member) => {
4551
+ const memberRole = member.metadata?.peerGroupRole || member.metadata?.role;
4552
+ return memberRole !== "publisher";
4553
+ }).length;
4554
+ let assignedTier = tiered ? assignPeerGroupTier({
4555
+ joinIndex: existingSubscriberCount,
4556
+ liveCap,
4557
+ requestedTier: tierMode === "auto" ? null : tierMode,
4558
+ role
4559
+ }) : null;
4560
+ const publisherId = options.publisherId || (role === "publisher" ? this.nodeId : null);
4193
4561
  const config = {
4194
4562
  fanout: typeof options.fanout === "number" ? options.fanout : this.defaultPeerGroupFanout,
4195
4563
  maxActivePeers: typeof options.maxActivePeers === "number" ? options.maxActivePeers : this.defaultPeerGroupMaxActivePeers,
4196
4564
  maxHops: typeof options.maxHops === "number" ? options.maxHops : this.defaultGossipMaxHops,
4197
- relayEnabled: options.relayEnabled !== false
4565
+ relayEnabled: options.relayEnabled !== false,
4566
+ tiered,
4567
+ tierMode,
4568
+ liveCap,
4569
+ bulkIntervalMs: typeof options.bulkIntervalMs === "number" ? options.bulkIntervalMs : DEFAULT_BULK_INTERVAL_MS,
4570
+ domainEvents: options.domainEvents !== false,
4571
+ autoPublishDomainEvents: options.autoPublishDomainEvents !== false,
4572
+ role,
4573
+ publisherId,
4574
+ commandCapable: options.commandCapable !== false,
4575
+ peerGroupTier: assignedTier
4198
4576
  };
4199
4577
  await this.joinDiscovery(scope, {
4200
4578
  metadata: {
4201
4579
  peerGroup: groupId,
4580
+ ...assignedTier ? { peerGroupTier: assignedTier } : {},
4581
+ peerGroupRole: role,
4582
+ ...publisherId ? { publisherId } : {},
4583
+ bulkRelay: false,
4202
4584
  ...options.metadata || {}
4203
4585
  },
4204
4586
  bootstrapPeerIds: options.bootstrapPeerIds,
@@ -4206,9 +4588,61 @@ var require_dignity_p2p = __commonJS({
4206
4588
  ttlMs: options.ttlMs
4207
4589
  });
4208
4590
  this.peerGroups.set(groupId, config);
4209
- this.emit("peergroupjoined", { groupId, config });
4591
+ if (!this.domainEventLogs.has(groupId)) {
4592
+ this.domainEventLogs.set(groupId, []);
4593
+ }
4594
+ if (!this.replicaViews.has(groupId)) {
4595
+ this.replicaViews.set(groupId, createEmptyView());
4596
+ }
4597
+ this.refreshBulkRelays(groupId);
4598
+ if (tiered && tierMode === "auto" && role === "subscriber") {
4599
+ assignedTier = this.recalculateOwnPeerGroupTier(groupId) || assignedTier;
4600
+ config.peerGroupTier = assignedTier;
4601
+ }
4602
+ this.emit("peergroupjoined", { groupId, config, tier: assignedTier });
4210
4603
  return config;
4211
4604
  }
4605
+ recalculateOwnPeerGroupTier(groupId) {
4606
+ const group = this.peerGroups.get(groupId);
4607
+ if (!group || !group.tiered || group.role !== "subscriber") {
4608
+ return group ? group.peerGroupTier : null;
4609
+ }
4610
+ if (group.tierMode !== "auto") {
4611
+ return group.peerGroupTier;
4612
+ }
4613
+ const scope = this.peerGroupScopeFor(groupId);
4614
+ const members = this.listPeerGroupMembers(groupId, { includeSelf: true });
4615
+ const subscribers = members.filter((member) => {
4616
+ const memberRole = member.metadata?.peerGroupRole || member.metadata?.role;
4617
+ return memberRole !== "publisher";
4618
+ }).map((member) => member.peerId).sort();
4619
+ const joinIndex = subscribers.indexOf(this.nodeId);
4620
+ if (joinIndex < 0) {
4621
+ return group.peerGroupTier;
4622
+ }
4623
+ const newTier = assignPeerGroupTier({
4624
+ joinIndex,
4625
+ liveCap: group.liveCap,
4626
+ requestedTier: null,
4627
+ role: "subscriber"
4628
+ });
4629
+ if (newTier === group.peerGroupTier) {
4630
+ return newTier;
4631
+ }
4632
+ group.peerGroupTier = newTier;
4633
+ const room = this.discoveryRooms.get(scope);
4634
+ if (room) {
4635
+ room.metadata = {
4636
+ ...room.metadata || {},
4637
+ peerGroupTier: newTier
4638
+ };
4639
+ this.upsertPresence(scope, this.nodeId, room.metadata, room.ttlMs, this.now());
4640
+ this.announcePresence(scope).catch((error) => {
4641
+ this.emit("warning", { type: "tier-announce-failed", groupId, error });
4642
+ });
4643
+ }
4644
+ return newTier;
4645
+ }
4212
4646
  async leavePeerGroup(groupId) {
4213
4647
  if (!groupId) {
4214
4648
  return;
@@ -4216,6 +4650,7 @@ var require_dignity_p2p = __commonJS({
4216
4650
  const scope = this.peerGroupScopeFor(groupId);
4217
4651
  await this.leaveDiscovery(scope);
4218
4652
  this.peerGroups.delete(groupId);
4653
+ this.bulkRelayByGroup.delete(groupId);
4219
4654
  this.emit("peergroupleft", { groupId });
4220
4655
  }
4221
4656
  async publishToPeerGroup(groupId, innerMessageType, innerPayload, options = {}) {
@@ -4238,7 +4673,11 @@ var require_dignity_p2p = __commonJS({
4238
4673
  const fanout = typeof options.fanout === "number" ? options.fanout : group ? group.fanout : this.defaultPeerGroupFanout;
4239
4674
  const maxActivePeers = group ? group.maxActivePeers : this.defaultPeerGroupMaxActivePeers;
4240
4675
  const maxHop = typeof options.maxHops === "number" ? options.maxHops : group ? group.maxHops : this.defaultGossipMaxHops;
4241
- const fanoutPeerIds = this.selectPeerGroupFanout(groupId, fanout, [this.nodeId]);
4676
+ const fanoutOptions = {};
4677
+ if (group && group.tiered && options.tier !== "bulk") {
4678
+ fanoutOptions.tier = options.tier || "live";
4679
+ }
4680
+ const fanoutPeerIds = this.selectPeerGroupFanout(groupId, fanout, [this.nodeId], fanoutOptions);
4242
4681
  if (fanoutPeerIds.length > 0) {
4243
4682
  await this.ensureConnectedToPeers(fanoutPeerIds.slice(0, maxActivePeers));
4244
4683
  await this.enforceConnectionBudget();
@@ -4260,6 +4699,204 @@ var require_dignity_p2p = __commonJS({
4260
4699
  });
4261
4700
  return { gossipId, fanoutPeerIds };
4262
4701
  }
4702
+ async publishPeerGroupBulk(groupId, innerMessageType, innerPayload, options = {}) {
4703
+ const group = this.peerGroups.get(groupId);
4704
+ if (!group && options.allowUnjoined !== true) {
4705
+ throw new Error(`PeerGroup ${groupId} has not been joined`);
4706
+ }
4707
+ if (group && group.role !== "publisher") {
4708
+ throw new Error(`Only publisher can bulk-publish to PeerGroup ${groupId}`);
4709
+ }
4710
+ const fanout = typeof options.fanout === "number" ? options.fanout : group ? group.fanout : this.defaultPeerGroupFanout;
4711
+ const maxActivePeers = group ? group.maxActivePeers : this.defaultPeerGroupMaxActivePeers;
4712
+ const maxHop = typeof options.maxHops === "number" ? options.maxHops : group ? group.maxHops : this.defaultGossipMaxHops;
4713
+ const fanoutPeerIds = this.selectPeerGroupFanout(
4714
+ groupId,
4715
+ fanout,
4716
+ [this.nodeId],
4717
+ { tier: "bulk", bulkRelayOnly: group?.tiered === true }
4718
+ );
4719
+ if (fanoutPeerIds.length > 0) {
4720
+ await this.ensureConnectedToPeers(fanoutPeerIds.slice(0, maxActivePeers));
4721
+ await this.enforceConnectionBudget();
4722
+ }
4723
+ const gossipId = options.gossipId || this.idGenerator();
4724
+ this.markSeenGossip(gossipId);
4725
+ await this.broadcastMessage("peer-group:gossip", {
4726
+ groupId,
4727
+ gossipId,
4728
+ publisherId: this.nodeId,
4729
+ hop: 0,
4730
+ maxHop,
4731
+ deliveryTier: "bulk",
4732
+ innerMessageType,
4733
+ innerPayload
4734
+ }, {
4735
+ broadcastScope: this.peerGroupScopeFor(groupId),
4736
+ fanoutPeerIds
4737
+ });
4738
+ return { gossipId, fanoutPeerIds };
4739
+ }
4740
+ async publishPeerGroupCheckpoint(groupId, options = {}) {
4741
+ const group = this.peerGroups.get(groupId);
4742
+ if (!group) {
4743
+ throw new Error(`PeerGroup ${groupId} has not been joined`);
4744
+ }
4745
+ const events = this.domainEventLogs.get(groupId) || [];
4746
+ const checkpoint = buildCheckpoint(groupId, events, {
4747
+ publisherId: options.publisherId || group.publisherId || this.nodeId
4748
+ });
4749
+ await this.publishPeerGroupBulk(groupId, "domain:checkpoint", checkpoint, options);
4750
+ this.emit("checkpointpublished", { groupId, checkpoint });
4751
+ return checkpoint;
4752
+ }
4753
+ resolvePublisherGroupIds(options = {}) {
4754
+ if (options.peerGroupId) {
4755
+ return [options.peerGroupId];
4756
+ }
4757
+ const groups = [];
4758
+ for (const [groupId, config] of this.peerGroups.entries()) {
4759
+ if (config.domainEvents && config.autoPublishDomainEvents && config.role === "publisher") {
4760
+ groups.push(groupId);
4761
+ }
4762
+ }
4763
+ return groups;
4764
+ }
4765
+ async maybePublishDomainEvent(operation, options = {}) {
4766
+ const groupIds = this.resolvePublisherGroupIds(options);
4767
+ if (groupIds.length === 0) {
4768
+ return;
4769
+ }
4770
+ for (const groupId of groupIds) {
4771
+ await this.publishDomainEventForOperation(groupId, operation);
4772
+ }
4773
+ }
4774
+ async publishDomainEventForOperation(groupId, operation) {
4775
+ const group = this.peerGroups.get(groupId);
4776
+ if (!group || !group.domainEvents) {
4777
+ return null;
4778
+ }
4779
+ if (group.role !== "publisher") {
4780
+ throw new Error(`Only publisher can emit domain events for PeerGroup ${groupId}`);
4781
+ }
4782
+ const prevHash = this.lastEventHashByGroup.get(groupId) || null;
4783
+ let event = operationToDomainEvent(operation, {
4784
+ publisherId: this.nodeId,
4785
+ groupId,
4786
+ prevHash,
4787
+ eventIdGenerator: () => this.idGenerator()
4788
+ });
4789
+ if (this.securityService.options.signingEnabled && this.securityService.signingSecretKey) {
4790
+ event = signDomainEvent(event, this.securityService.signingSecretKey);
4791
+ }
4792
+ const log = this.domainEventLogs.get(groupId) || [];
4793
+ log.push(event);
4794
+ this.domainEventLogs.set(groupId, log);
4795
+ this.lastEventHashByGroup.set(groupId, event.eventHash);
4796
+ this.emit("domainevent", event);
4797
+ if (group.autoPublishDomainEvents) {
4798
+ await this.publishToPeerGroup(groupId, "domain:event", event, { tier: "live" });
4799
+ if (group.tiered) {
4800
+ await this.publishPeerGroupBulk(groupId, "domain:event", event);
4801
+ }
4802
+ }
4803
+ return event;
4804
+ }
4805
+ refreshBulkRelays(groupId) {
4806
+ const group = this.peerGroups.get(groupId);
4807
+ if (!group || !group.tiered) {
4808
+ return [];
4809
+ }
4810
+ const peers = this.listPeerGroupMembers(groupId, { includeSelf: false });
4811
+ const relays = electBulkRelays(peers);
4812
+ const previous = this.bulkRelayByGroup.get(groupId) || [];
4813
+ this.bulkRelayByGroup.set(groupId, relays);
4814
+ const changed = previous.length !== relays.length || previous.some((id, index) => id !== relays[index]);
4815
+ if (changed) {
4816
+ this.emit("bulkrelaychanged", { groupId, relays, previous });
4817
+ }
4818
+ return relays;
4819
+ }
4820
+ ingestRemoteDomainEvent(event, context = {}) {
4821
+ const groupId = event.groupId || context.groupId;
4822
+ if (!groupId) {
4823
+ return false;
4824
+ }
4825
+ const group = this.peerGroups.get(groupId);
4826
+ if (!group) {
4827
+ return false;
4828
+ }
4829
+ const publisherId = event.publisherId || context.publisherId;
4830
+ if (group.publisherId && publisherId !== group.publisherId) {
4831
+ this.emit("warning", {
4832
+ type: "domain-event-rejected",
4833
+ groupId,
4834
+ reason: "publisher-mismatch",
4835
+ eventId: event.eventId,
4836
+ expectedPublisher: group.publisherId,
4837
+ actualPublisher: publisherId
4838
+ });
4839
+ return false;
4840
+ }
4841
+ let signingPublicKey = null;
4842
+ if (this.securityService.options.signingEnabled && publisherId) {
4843
+ const peerKey = this.securityService.resolvePeerPublicKey(publisherId, null);
4844
+ signingPublicKey = peerKey ? peerKey.signingPublicKey : null;
4845
+ if (!signingPublicKey) {
4846
+ this.emit("warning", {
4847
+ type: "domain-event-rejected",
4848
+ groupId,
4849
+ reason: "missing-publisher-key",
4850
+ eventId: event.eventId,
4851
+ publisherId
4852
+ });
4853
+ return false;
4854
+ }
4855
+ }
4856
+ const verified = verifyDomainEvent(event, { signingPublicKey });
4857
+ if (!verified.ok) {
4858
+ this.emit("warning", {
4859
+ type: "domain-event-rejected",
4860
+ groupId,
4861
+ reason: verified.reason,
4862
+ eventId: event.eventId
4863
+ });
4864
+ return false;
4865
+ }
4866
+ if (this.securityService.options.signingEnabled && verified.unsigned) {
4867
+ this.emit("warning", {
4868
+ type: "domain-event-rejected",
4869
+ groupId,
4870
+ reason: "unsigned-event",
4871
+ eventId: event.eventId
4872
+ });
4873
+ return false;
4874
+ }
4875
+ const log = this.domainEventLogs.get(groupId) || [];
4876
+ if (log.some((entry) => entry.eventId === event.eventId)) {
4877
+ return false;
4878
+ }
4879
+ const expectedPrev = log.length > 0 ? log[log.length - 1].eventHash : null;
4880
+ if (event.prevHash !== expectedPrev) {
4881
+ this.emit("chainbroken", {
4882
+ groupId,
4883
+ expectedPrev,
4884
+ actualPrev: event.prevHash,
4885
+ eventId: event.eventId
4886
+ });
4887
+ return false;
4888
+ }
4889
+ log.push(event);
4890
+ this.domainEventLogs.set(groupId, log);
4891
+ this.lastEventHashByGroup.set(groupId, event.eventHash);
4892
+ if (!group.commandCapable) {
4893
+ const view = this.replicaViews.get(groupId) || createEmptyView();
4894
+ applyDomainEventToView(view, event);
4895
+ this.replicaViews.set(groupId, view);
4896
+ }
4897
+ this.emit("domainevent", event);
4898
+ return true;
4899
+ }
4263
4900
  async publishRecordToPeerGroup(groupId, collectionName, id, options = {}) {
4264
4901
  const collection = this.getCollection(collectionName);
4265
4902
  const raw = collection.get(id);
@@ -4299,15 +4936,26 @@ var require_dignity_p2p = __commonJS({
4299
4936
  publisherId
4300
4937
  });
4301
4938
  const group = this.peerGroups.get(groupId);
4939
+ const deliveryTier = payload.deliveryTier || "live";
4940
+ if (group && group.tiered && group.peerGroupTier === "bulk" && deliveryTier !== "bulk") {
4941
+ return;
4942
+ }
4302
4943
  const configuredMaxHop = group ? group.maxHops : this.defaultGossipMaxHops;
4303
4944
  const maxHop = typeof payloadMaxHop === "number" ? Math.min(payloadMaxHop, configuredMaxHop) : configuredMaxHop;
4304
4945
  if (!group || group.relayEnabled === false || hop >= maxHop) {
4305
4946
  return;
4306
4947
  }
4948
+ const relayOptions = {};
4949
+ if (group.tiered) {
4950
+ relayOptions.tier = deliveryTier === "bulk" ? "bulk" : "live";
4951
+ if (deliveryTier === "bulk") {
4952
+ relayOptions.bulkRelayOnly = true;
4953
+ }
4954
+ }
4307
4955
  const relayPeers = this.selectPeerGroupFanout(groupId, group.fanout, [
4308
4956
  decrypted.senderId,
4309
4957
  this.nodeId
4310
- ]);
4958
+ ], relayOptions);
4311
4959
  if (relayPeers.length === 0) {
4312
4960
  return;
4313
4961
  }
@@ -4319,6 +4967,7 @@ var require_dignity_p2p = __commonJS({
4319
4967
  publisherId,
4320
4968
  hop: hop + 1,
4321
4969
  maxHop,
4970
+ deliveryTier,
4322
4971
  innerMessageType,
4323
4972
  innerPayload
4324
4973
  }, {
@@ -4361,6 +5010,19 @@ var require_dignity_p2p = __commonJS({
4361
5010
  }
4362
5011
  return;
4363
5012
  }
5013
+ if (innerMessageType === "domain:event") {
5014
+ this.ingestRemoteDomainEvent(innerPayload, context);
5015
+ return;
5016
+ }
5017
+ if (innerMessageType === "domain:checkpoint") {
5018
+ this.emit("peergroupmessage", {
5019
+ groupId: context.groupId,
5020
+ senderId: context.senderId,
5021
+ type: "domain:checkpoint",
5022
+ payload: innerPayload
5023
+ });
5024
+ return;
5025
+ }
4364
5026
  if (innerMessageType === "record:snapshot") {
4365
5027
  const { collectionName, record } = innerPayload || {};
4366
5028
  if (collectionName && record) {
@@ -4406,6 +5068,13 @@ var require_dignity_p2p = __commonJS({
4406
5068
  };
4407
5069
  map.set(peerId, next);
4408
5070
  this.trustPeerFromMetadata(peerId, next.metadata);
5071
+ const groupId = parsePeerGroupScope(scope);
5072
+ if (groupId && this.peerGroups.has(groupId)) {
5073
+ this.refreshBulkRelays(groupId);
5074
+ if (peerId !== this.nodeId) {
5075
+ this.recalculateOwnPeerGroupTier(groupId);
5076
+ }
5077
+ }
4409
5078
  if (!existing) {
4410
5079
  this.emit("peerdiscovered", { scope, peerId, metadata: next.metadata });
4411
5080
  }
@@ -12442,6 +13111,195 @@ var require_indexeddb_persistence = __commonJS({
12442
13111
  }
12443
13112
  });
12444
13113
 
13114
+ // src/cqrs/query-replica.js
13115
+ var require_query_replica = __commonJS({
13116
+ "src/cqrs/query-replica.js"(exports, module) {
13117
+ var EventEmitter = require_event_emitter();
13118
+ var {
13119
+ createEmptyView,
13120
+ applyDomainEventToView,
13121
+ verifyEventChain,
13122
+ verifyDomainEvent,
13123
+ DOMAIN_EVENT_SCHEMA_VERSION
13124
+ } = require_domain_events();
13125
+ var DignityQueryReplica = class extends EventEmitter {
13126
+ constructor(dignityP2P, { groupId, collections = [], tierMode = "auto", publisherId = null } = {}) {
13127
+ super();
13128
+ if (!dignityP2P) {
13129
+ throw new Error("DignityQueryReplica requires dignityP2P");
13130
+ }
13131
+ if (!groupId) {
13132
+ throw new Error("DignityQueryReplica requires groupId");
13133
+ }
13134
+ this.dignity = dignityP2P;
13135
+ this.groupId = groupId;
13136
+ this.collections = [...collections];
13137
+ this.tierMode = tierMode;
13138
+ this.publisherId = publisherId;
13139
+ this.view = createEmptyView(this.collections);
13140
+ this.eventLog = [];
13141
+ this.started = false;
13142
+ this.boundDomainHandler = this.handleDomainEvent.bind(this);
13143
+ this.boundPeerGroupHandler = this.handlePeerGroupMessage.bind(this);
13144
+ }
13145
+ async start(options = {}) {
13146
+ if (this.started) {
13147
+ return this;
13148
+ }
13149
+ await this.dignity.joinPeerGroup(this.groupId, {
13150
+ tierMode: this.tierMode,
13151
+ role: "subscriber",
13152
+ commandCapable: false,
13153
+ domainEvents: true,
13154
+ publisherId: this.publisherId,
13155
+ liveCap: options.liveCap,
13156
+ bulkIntervalMs: options.bulkIntervalMs,
13157
+ bootstrapPeerIds: options.bootstrapPeerIds,
13158
+ metadata: { role: "subscriber", replica: true }
13159
+ });
13160
+ this.dignity.on("domainevent", this.boundDomainHandler);
13161
+ this.dignity.on("peergroupmessage", this.boundPeerGroupHandler);
13162
+ this.started = true;
13163
+ this.emit("started", { groupId: this.groupId });
13164
+ return this;
13165
+ }
13166
+ async stop() {
13167
+ if (!this.started) {
13168
+ return;
13169
+ }
13170
+ this.dignity.off("domainevent", this.boundDomainHandler);
13171
+ this.dignity.off("peergroupmessage", this.boundPeerGroupHandler);
13172
+ await this.dignity.leavePeerGroup(this.groupId);
13173
+ this.started = false;
13174
+ this.emit("stopped", { groupId: this.groupId });
13175
+ }
13176
+ handleDomainEvent(event) {
13177
+ if (!event || event.groupId !== this.groupId) {
13178
+ return;
13179
+ }
13180
+ if (this.publisherId && event.publisherId !== this.publisherId) {
13181
+ return;
13182
+ }
13183
+ this.ingestEvent(event);
13184
+ }
13185
+ handlePeerGroupMessage(message) {
13186
+ if (!message || message.groupId !== this.groupId) {
13187
+ return;
13188
+ }
13189
+ if (message.type === "domain:checkpoint") {
13190
+ this.emit("checkpoint", message.payload);
13191
+ }
13192
+ }
13193
+ ingestEvent(event, { skipChainCheck = false } = {}) {
13194
+ const verified = verifyDomainEvent(event, {
13195
+ supportedVersions: [DOMAIN_EVENT_SCHEMA_VERSION]
13196
+ });
13197
+ if (!verified.ok) {
13198
+ this.emit("warning", { type: "domain-event-rejected", reason: verified.reason, event });
13199
+ return false;
13200
+ }
13201
+ if (!skipChainCheck && this.eventLog.length > 0) {
13202
+ const lastHash = this.eventLog[this.eventLog.length - 1].eventHash;
13203
+ if (event.prevHash !== lastHash) {
13204
+ this.emit("chainbroken", {
13205
+ groupId: this.groupId,
13206
+ expectedPrev: lastHash,
13207
+ actualPrev: event.prevHash,
13208
+ eventId: event.eventId
13209
+ });
13210
+ return false;
13211
+ }
13212
+ } else if (!skipChainCheck && this.eventLog.length === 0 && event.prevHash) {
13213
+ this.emit("chainbroken", {
13214
+ groupId: this.groupId,
13215
+ expectedPrev: null,
13216
+ actualPrev: event.prevHash,
13217
+ eventId: event.eventId
13218
+ });
13219
+ return false;
13220
+ }
13221
+ const duplicate = this.eventLog.some((entry) => entry.eventId === event.eventId);
13222
+ if (duplicate) {
13223
+ return false;
13224
+ }
13225
+ const result = applyDomainEventToView(this.view, event, {
13226
+ collectionsFilter: this.collections.length > 0 ? this.collections : null
13227
+ });
13228
+ if (result.applied || result.reason === "collection-filtered") {
13229
+ this.eventLog.push({ ...event });
13230
+ this.emit("change", { event, result });
13231
+ return true;
13232
+ }
13233
+ this.emit("warning", { type: "domain-event-not-applied", reason: result.reason, event });
13234
+ return false;
13235
+ }
13236
+ read(collectionName, id) {
13237
+ const collection = this.view.get(collectionName);
13238
+ if (!collection) {
13239
+ return null;
13240
+ }
13241
+ const record = collection.get(id);
13242
+ if (!record || record.deletedAt) {
13243
+ return null;
13244
+ }
13245
+ return { ...record, data: { ...record.data } };
13246
+ }
13247
+ list(collectionName, options = {}) {
13248
+ const collection = this.view.get(collectionName);
13249
+ if (!collection) {
13250
+ return [];
13251
+ }
13252
+ const includeDeleted = options.includeDeleted || false;
13253
+ const records = [];
13254
+ for (const record of collection.values()) {
13255
+ if (record.deletedAt && !includeDeleted) {
13256
+ continue;
13257
+ }
13258
+ if (record.deletedAt && includeDeleted) {
13259
+ records.push({
13260
+ id: record.id,
13261
+ ownerId: record.ownerId,
13262
+ deletedAt: record.deletedAt,
13263
+ version: record.version
13264
+ });
13265
+ continue;
13266
+ }
13267
+ records.push({ ...record, data: { ...record.data } });
13268
+ }
13269
+ return records;
13270
+ }
13271
+ verifyChain() {
13272
+ const result = verifyEventChain(this.eventLog);
13273
+ if (!result.ok) {
13274
+ this.emit("chainbroken", { groupId: this.groupId, ...result });
13275
+ }
13276
+ return result;
13277
+ }
13278
+ getViewStats() {
13279
+ const stats = {
13280
+ groupId: this.groupId,
13281
+ eventCount: this.eventLog.length,
13282
+ collections: {}
13283
+ };
13284
+ for (const [name, collection] of this.view.entries()) {
13285
+ let active = 0;
13286
+ let deleted = 0;
13287
+ for (const record of collection.values()) {
13288
+ if (record.deletedAt) {
13289
+ deleted += 1;
13290
+ } else {
13291
+ active += 1;
13292
+ }
13293
+ }
13294
+ stats.collections[name] = { active, deleted };
13295
+ }
13296
+ return stats;
13297
+ }
13298
+ };
13299
+ module.exports = DignityQueryReplica;
13300
+ }
13301
+ });
13302
+
12445
13303
  // src/index.js
12446
13304
  var require_index = __commonJS({
12447
13305
  "src/index.js"(exports, module) {
@@ -12488,6 +13346,24 @@ var require_index = __commonJS({
12488
13346
  parsePeerGroupScope,
12489
13347
  selectFanoutPeers
12490
13348
  } = require_peer_group();
13349
+ var {
13350
+ DOMAIN_EVENT_SCHEMA_VERSION,
13351
+ operationToDomainEvent,
13352
+ signDomainEvent,
13353
+ verifyDomainEvent,
13354
+ verifyEventChain,
13355
+ buildCheckpoint,
13356
+ createEmptyView,
13357
+ applyDomainEventToView
13358
+ } = require_domain_events();
13359
+ var {
13360
+ DEFAULT_LIVE_CAP,
13361
+ DEFAULT_BULK_INTERVAL_MS,
13362
+ assignPeerGroupTier,
13363
+ filterPeersByTier
13364
+ } = require_peer_group_tiers();
13365
+ var { electBulkRelays, DEFAULT_BULK_RELAY_COUNT } = require_bulk_relay();
13366
+ var DignityQueryReplica = require_query_replica();
12491
13367
  module.exports = {
12492
13368
  DignityP2P,
12493
13369
  createDefaultSignalingPool,
@@ -12521,7 +13397,22 @@ var require_index = __commonJS({
12521
13397
  DEFAULT_PEER_GROUP_OPTIONS,
12522
13398
  peerGroupScope,
12523
13399
  parsePeerGroupScope,
12524
- selectFanoutPeers
13400
+ selectFanoutPeers,
13401
+ DOMAIN_EVENT_SCHEMA_VERSION,
13402
+ operationToDomainEvent,
13403
+ signDomainEvent,
13404
+ verifyDomainEvent,
13405
+ verifyEventChain,
13406
+ buildCheckpoint,
13407
+ createEmptyView,
13408
+ applyDomainEventToView,
13409
+ DEFAULT_LIVE_CAP,
13410
+ DEFAULT_BULK_INTERVAL_MS,
13411
+ assignPeerGroupTier,
13412
+ filterPeersByTier,
13413
+ electBulkRelays,
13414
+ DEFAULT_BULK_RELAY_COUNT,
13415
+ DignityQueryReplica
12525
13416
  };
12526
13417
  }
12527
13418
  });