@xnetjs/runtime 0.5.4 → 0.6.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 CHANGED
@@ -6,6 +6,11 @@ node-format validation, signing, encryption, SQLite-backed storage, and the live
6
6
  query/mutate/subscribe loop — is usable from **any** framework, a CLI, a worker,
7
7
  or a plain Node service.
8
8
 
9
+ > **Alpha software.** xNet is released but early: this package is on npm and
10
+ > usable today, but its API can change between releases, sometimes without a
11
+ > migration path. Pin your version. See the
12
+ > [project README](https://github.com/crs48/xNet#readme) for what alpha means here.
13
+
9
14
  Nothing here imports React. The `@xnetjs/react` hooks, the CLI, and other-framework
10
15
  adapters are thin bindings over this surface.
11
16
 
package/dist/index.d.ts CHANGED
@@ -794,11 +794,21 @@ declare function createNodePool(config: NodePoolConfig): NodePool;
794
794
  /**
795
795
  * Trust class of a replication destination. A `trusted` hub holds plaintext and
796
796
  * can index/search/serve; a `zero-knowledge` hub holds only recipient-scoped
797
- * ciphertext and can relay but not read. Carried on the manifest shape here;
798
- * the plaintext-vs-ciphertext *gate* is a later phase (0258) and not yet
799
- * enforced.
797
+ * ciphertext and can relay but not read. The plaintext gate is ENFORCED at the
798
+ * publish path (`MultiHubSyncManager.publishScoped` withholds plaintext from
799
+ * zero-knowledge destinations — 0258, closed by 0383 W4); this predicate is
800
+ * the single definition of the rule.
800
801
  */
801
802
  type ReplicaTrust = 'trusted' | 'zero-knowledge';
803
+ /** Payload classification for the plaintext gate. */
804
+ type PayloadClass = 'plaintext' | 'ciphertext';
805
+ /**
806
+ * May a destination of this trust class receive a payload of this class?
807
+ * Undefined trust is treated as `trusted` for compatibility with existing
808
+ * configs that never declared a class — tightening that default is a breaking
809
+ * change to make deliberately, not silently.
810
+ */
811
+ declare function mayReceivePayload(trust: ReplicaTrust | undefined, payload: PayloadClass): boolean;
802
812
  /** The namespace for a Space's content — the routing key for the planner. */
803
813
  declare function spaceNamespace(ownerDID: string, spaceId: string): string;
804
814
  /**
@@ -910,8 +920,8 @@ interface HubConnection {
910
920
  /** The multiplexed transport for this hub. */
911
921
  transport: HubTransport;
912
922
  /**
913
- * Trust class of this hub. Reserved for the plaintext vs zero-knowledge gate
914
- * (0258); surfaced on `plannedHubs` but not yet enforced.
923
+ * Trust class of this hub. ENFORCED at `publishScoped` (0258/0383 W4): a
924
+ * `zero-knowledge` destination never receives a plaintext payload.
915
925
  */
916
926
  trust?: ReplicaTrust;
917
927
  }
@@ -947,8 +957,20 @@ interface MultiHubSyncManager {
947
957
  destinationsFor(namespace: string): string[];
948
958
  /** Join a node's room on exactly the hubs the policy selects. */
949
959
  joinScopedRoom(nodeId: string, namespace: string, handler: (data: Record<string, unknown>) => void): ScopedRoomHandle;
950
- /** Publish to a room on exactly the hubs the namespace routes to. */
951
- publishScoped(namespace: string, room: string, data: object): void;
960
+ /**
961
+ * Publish to a room on exactly the hubs the namespace routes to. The 0258
962
+ * trust gate is enforced here: plaintext payloads (the default class) are
963
+ * WITHHELD from `zero-knowledge` destinations; pass `payload: 'ciphertext'`
964
+ * for recipient-scoped envelopes, which may go anywhere. Returns which hubs
965
+ * received and which were withheld, so callers can surface the gap instead
966
+ * of silently under-replicating.
967
+ */
968
+ publishScoped(namespace: string, room: string, data: object, opts?: {
969
+ payload?: PayloadClass;
970
+ }): {
971
+ published: string[];
972
+ withheld: string[];
973
+ };
952
974
  /** Replace the routing policy; live rooms re-route to match (manifest-as-data). */
953
975
  setReplication(replication: SyncReplicationConfig | undefined): void;
954
976
  /** Connect every hub transport. */
@@ -1001,6 +1023,11 @@ type NodeSyncResponse = {
1001
1023
  room: string;
1002
1024
  changes: SerializedNodeChange[];
1003
1025
  highWaterMark: number;
1026
+ /**
1027
+ * The hub capped this response and more changes remain past `highWaterMark`.
1028
+ * Older hubs never send it, in which case one response is the whole catch-up.
1029
+ */
1030
+ hasMore?: boolean;
1004
1031
  };
1005
1032
  /**
1006
1033
  * Listener for unknown change type events.
@@ -1041,7 +1068,14 @@ declare class NodeStoreSyncProvider {
1041
1068
  private queuedHashes;
1042
1069
  private sendTimer;
1043
1070
  private sentInWindow;
1071
+ /**
1072
+ * Whether the connected hub advertised `batch-push` in its handshake.
1073
+ * Stays false against older hubs, which keeps the one-change-per-frame
1074
+ * path as the compatible default.
1075
+ */
1076
+ private batchPushSupported;
1044
1077
  private syncResponseResolver;
1078
+ private catchUpPages;
1045
1079
  private structuralRejections;
1046
1080
  private outboundHalted;
1047
1081
  private clearResolver;
@@ -1136,7 +1170,20 @@ declare class NodeStoreSyncProvider {
1136
1170
  private enqueueChange;
1137
1171
  private scheduleDrain;
1138
1172
  private drain;
1173
+ /**
1174
+ * Batched drain (exploration 0357): pack the queue into slabs of up to
1175
+ * MAX_BATCH_CHANGES and send one frame per slab. `sentInWindow` counts
1176
+ * CHANGES, not frames, matching how the hub charges its rate limit — so the
1177
+ * budget stays a real bound on work rather than a bound on syscalls.
1178
+ */
1179
+ private drainBatched;
1139
1180
  private publishChange;
1181
+ /**
1182
+ * Send many changes in one frame. The hub verifies and authorizes each one
1183
+ * individually and re-broadcasts them as ordinary `node-change` messages, so
1184
+ * this is purely a transport-level batch.
1185
+ */
1186
+ private publishChangeBatch;
1140
1187
  private clearSendQueue;
1141
1188
  private serializeChange;
1142
1189
  private deserializeChange;
@@ -1298,7 +1345,7 @@ type InitialSyncManager = {
1298
1345
  declare function createInitialSyncManager(): InitialSyncManager;
1299
1346
 
1300
1347
  /**
1301
- * The umbrella XNet Protocol Version.
1348
+ * The umbrella xNet Protocol Version.
1302
1349
  *
1303
1350
  * Five subsystems version independently (the change record, the Yjs sync
1304
1351
  * envelope, awareness, schemas, the crypto level). To avoid a five-way
@@ -1369,4 +1416,4 @@ declare function negotiateProtocolVersion(ours: readonly string[], theirs: reado
1369
1416
  */
1370
1417
  declare function isProtocolCompatible(theirs: readonly string[]): boolean;
1371
1418
 
1372
- export { type AdapterConformanceCheck, AdapterConformanceError, type AdapterConformanceResult, type BlobStoreForSync, type ConformanceClientFactory, type ConnectionManager, type ConnectionManagerConfig, type ConnectionStatus, type CreateXNetClientOptions, type HubConnection, type HubTransport, type InitialSyncManager, type InitialSyncMessage, type LiveQuery, type LiveQueryValue, METABRIDGE_ORIGIN, METABRIDGE_SEED_ORIGIN, type MetaBridge, type MultiHubConnectionManagerConfig, type MultiHubSyncManager, type MultiHubSyncManagerConfig, type NodePool, type NodePoolConfig, NodeStoreSyncProvider, type NodeSyncResponse, type OfflineQueue, type OfflineQueueConfig, type PlannedHub, type PoolEntryState, type ProgressListener, type QueueEntry, type Registry, type RegistryConfig, type RegistryStorage, type ReplicaTrust, type ReplicationDestinationSpec, type ReplicationScopeNode, type ScopedRoomHandle, type SerializedNodeChange, type SpaceReplicationPolicy, type SyncBlockedListener, type SyncBlockedReason, type SyncManager, type SyncManagerConfig, type SyncPhase, type SyncProgress, type SyncReconciliationOptions, type SyncReconciliationReport, type SyncStatus, type TrackedNode, WebSocketSyncProvider, type WebSocketSyncProviderOptions, XNET_AWARENESS_VERSION, XNET_DATA_MODEL_VERSION, XNET_PROTOCOL_VERSION, XNET_SCHEMA_VERSION, XNET_SUPPORTED_PROTOCOL_VERSIONS, XNET_SYNC_ENVELOPE_VERSION, XNET_UCAN_PROFILE, type XNetClient, type XNetClientBridgeMode, type XNetClientPluginOptions, type XNetClientRuntimePhase, type XNetClientRuntimeStatus, type XNetClientSyncOptions, type XNetClientTelemetry, type XNetClientUndoOptions, type XNetProtocolBundle, channelShareRoom, createConnectionManager, createInitialSyncManager, createMetaBridge, createMultiHubConnectionManager, createMultiHubSyncManager, createNodePool, createOfflineQueue, createRegistry, createSyncManager, createXNetClient, isProtocolCompatible, liveQuery, namespaceForNode, negotiateProtocolVersion, replicationConfigFromPolicies, runAdapterConformance, spaceNamespace, systemNamespace, workspaceShareRoom };
1419
+ export { type AdapterConformanceCheck, AdapterConformanceError, type AdapterConformanceResult, type BlobStoreForSync, type ConformanceClientFactory, type ConnectionManager, type ConnectionManagerConfig, type ConnectionStatus, type CreateXNetClientOptions, type HubConnection, type HubTransport, type InitialSyncManager, type InitialSyncMessage, type LiveQuery, type LiveQueryValue, METABRIDGE_ORIGIN, METABRIDGE_SEED_ORIGIN, type MetaBridge, type MultiHubConnectionManagerConfig, type MultiHubSyncManager, type MultiHubSyncManagerConfig, type NodePool, type NodePoolConfig, NodeStoreSyncProvider, type NodeSyncResponse, type OfflineQueue, type OfflineQueueConfig, type PayloadClass, type PlannedHub, type PoolEntryState, type ProgressListener, type QueueEntry, type Registry, type RegistryConfig, type RegistryStorage, type ReplicaTrust, type ReplicationDestinationSpec, type ReplicationScopeNode, type ScopedRoomHandle, type SerializedNodeChange, type SpaceReplicationPolicy, type SyncBlockedListener, type SyncBlockedReason, type SyncManager, type SyncManagerConfig, type SyncPhase, type SyncProgress, type SyncReconciliationOptions, type SyncReconciliationReport, type SyncStatus, type TrackedNode, WebSocketSyncProvider, type WebSocketSyncProviderOptions, XNET_AWARENESS_VERSION, XNET_DATA_MODEL_VERSION, XNET_PROTOCOL_VERSION, XNET_SCHEMA_VERSION, XNET_SUPPORTED_PROTOCOL_VERSIONS, XNET_SYNC_ENVELOPE_VERSION, XNET_UCAN_PROFILE, type XNetClient, type XNetClientBridgeMode, type XNetClientPluginOptions, type XNetClientRuntimePhase, type XNetClientRuntimeStatus, type XNetClientSyncOptions, type XNetClientTelemetry, type XNetClientUndoOptions, type XNetProtocolBundle, channelShareRoom, createConnectionManager, createInitialSyncManager, createMetaBridge, createMultiHubConnectionManager, createMultiHubSyncManager, createNodePool, createOfflineQueue, createRegistry, createSyncManager, createXNetClient, isProtocolCompatible, liveQuery, mayReceivePayload, namespaceForNode, negotiateProtocolVersion, replicationConfigFromPolicies, runAdapterConformance, spaceNamespace, systemNamespace, workspaceShareRoom };
package/dist/index.js CHANGED
@@ -934,9 +934,12 @@ import { base64ToBytes, bytesToBase64 } from "@xnetjs/crypto";
934
934
  var KNOWN_CHANGE_TYPES = /* @__PURE__ */ new Set(["node-change"]);
935
935
  var MAX_SENDS_PER_WINDOW = 40;
936
936
  var SEND_WINDOW_MS = 1e3;
937
+ var MAX_BATCH_CHANGES = 1e3;
938
+ var MAX_BATCHED_CHANGES_PER_WINDOW = 5e3;
937
939
  var SYNC_RESPONSE_TIMEOUT_MS = 4e3;
938
940
  var STRUCTURAL_REJECTION_CODES = /* @__PURE__ */ new Set(["INVALID_HASH", "INVALID_SIGNATURE", "INVALID_CHANGE"]);
939
941
  var MAX_STRUCTURAL_REJECTIONS = 5;
942
+ var MAX_CATCH_UP_PAGES = 500;
940
943
  var OUTBOUND_ENQUEUE_BATCH = 1024;
941
944
  var HEAVY_RESYNC_CHANGES = 5e3;
942
945
  var HEAVY_RESYNC_MS = 250;
@@ -981,8 +984,16 @@ var NodeStoreSyncProvider = class {
981
984
  queuedHashes = /* @__PURE__ */ new Set();
982
985
  sendTimer = null;
983
986
  sentInWindow = 0;
987
+ /**
988
+ * Whether the connected hub advertised `batch-push` in its handshake.
989
+ * Stays false against older hubs, which keeps the one-change-per-frame
990
+ * path as the compatible default.
991
+ */
992
+ batchPushSupported = false;
984
993
  // Request-sync-first: resolver for the in-flight node-sync-response wait.
985
994
  syncResponseResolver = null;
995
+ // Pages walked in the current catch-up burst (see MAX_CATCH_UP_PAGES).
996
+ catchUpPages = 0;
986
997
  // Protocol-skew circuit breaker state. `structuralRejections` counts
987
998
  // consecutive structural node-errors (reset on forward progress); once it
988
999
  // trips, `outboundHalted` stops all pushes until the next reconnect.
@@ -1081,6 +1092,7 @@ var NodeStoreSyncProvider = class {
1081
1092
  async onConnected() {
1082
1093
  this.outboundHalted = false;
1083
1094
  this.structuralRejections = 0;
1095
+ this.catchUpPages = 0;
1084
1096
  await this.ensureCursorLoaded();
1085
1097
  if (!this.connection || this.connection.status !== "connected") return;
1086
1098
  this.requestSync();
@@ -1090,6 +1102,7 @@ var NodeStoreSyncProvider = class {
1090
1102
  onDisconnected() {
1091
1103
  this.clearSendQueue();
1092
1104
  this.resolveSyncResponse();
1105
+ this.batchPushSupported = false;
1093
1106
  }
1094
1107
  async ensureCursorLoaded() {
1095
1108
  if (this.cursorLoaded) return;
@@ -1148,6 +1161,11 @@ var NodeStoreSyncProvider = class {
1148
1161
  }
1149
1162
  }
1150
1163
  handleDirectMessage(message) {
1164
+ if (message.type === "handshake") {
1165
+ const features = message.features;
1166
+ this.batchPushSupported = Array.isArray(features) && features.includes("batch-push");
1167
+ return;
1168
+ }
1151
1169
  if (message.type === "node-cleared") {
1152
1170
  if (message.room === this.room) {
1153
1171
  this.resolveClear(typeof message.cleared === "number" ? message.cleared : 0);
@@ -1285,7 +1303,8 @@ var NodeStoreSyncProvider = class {
1285
1303
  this.pushedThrough = response.highWaterMark;
1286
1304
  void this.syncLocalChanges();
1287
1305
  }
1288
- if (response.highWaterMark > this.lastSyncedLamport) {
1306
+ const advanced = response.highWaterMark > this.lastSyncedLamport;
1307
+ if (advanced) {
1289
1308
  this.lastSyncedLamport = response.highWaterMark;
1290
1309
  this.pushedThrough = Math.max(this.pushedThrough, this.lastSyncedLamport);
1291
1310
  this.structuralRejections = 0;
@@ -1295,6 +1314,17 @@ var NodeStoreSyncProvider = class {
1295
1314
  console.warn("[NodeStoreSync] failed to persist sync cursor:", err);
1296
1315
  }
1297
1316
  }
1317
+ if (response.hasMore === true && advanced && this.catchUpPages < MAX_CATCH_UP_PAGES && this.connection?.status === "connected") {
1318
+ this.catchUpPages += 1;
1319
+ this.requestSync();
1320
+ return;
1321
+ }
1322
+ if (response.hasMore === true && this.catchUpPages >= MAX_CATCH_UP_PAGES) {
1323
+ console.warn(
1324
+ `[NodeStoreSync] stopping catch-up for ${this.room} after ${MAX_CATCH_UP_PAGES} pages (cursor ${this.lastSyncedLamport}); the remainder resumes on the next sync.`
1325
+ );
1326
+ }
1327
+ this.catchUpPages = 0;
1298
1328
  this.resolveSyncResponse();
1299
1329
  }
1300
1330
  // ─── Outbound (throttled) ────────────────────────────────────────────────
@@ -1344,16 +1374,40 @@ var NodeStoreSyncProvider = class {
1344
1374
  if (this.outboundHalted) return;
1345
1375
  if (!this.connection || this.connection.status !== "connected") return;
1346
1376
  this.sentInWindow = 0;
1347
- while (this.sendQueue.length > 0 && this.sentInWindow < MAX_SENDS_PER_WINDOW) {
1348
- const change = this.sendQueue.shift();
1349
- this.queuedHashes.delete(change.hash);
1350
- this.publishChange(change);
1351
- this.sentInWindow += 1;
1377
+ if (this.batchPushSupported) {
1378
+ this.drainBatched();
1379
+ } else {
1380
+ while (this.sendQueue.length > 0 && this.sentInWindow < MAX_SENDS_PER_WINDOW) {
1381
+ const change = this.sendQueue.shift();
1382
+ this.queuedHashes.delete(change.hash);
1383
+ this.publishChange(change);
1384
+ this.sentInWindow += 1;
1385
+ }
1352
1386
  }
1353
1387
  if (this.sendQueue.length > 0) {
1354
1388
  this.scheduleDrain(SEND_WINDOW_MS);
1355
1389
  }
1356
1390
  }
1391
+ /**
1392
+ * Batched drain (exploration 0357): pack the queue into slabs of up to
1393
+ * MAX_BATCH_CHANGES and send one frame per slab. `sentInWindow` counts
1394
+ * CHANGES, not frames, matching how the hub charges its rate limit — so the
1395
+ * budget stays a real bound on work rather than a bound on syscalls.
1396
+ */
1397
+ drainBatched() {
1398
+ while (this.sendQueue.length > 0 && this.sentInWindow < MAX_BATCHED_CHANGES_PER_WINDOW) {
1399
+ const queued = this.sendQueue.length;
1400
+ const slabSize = Math.min(
1401
+ MAX_BATCH_CHANGES,
1402
+ queued,
1403
+ MAX_BATCHED_CHANGES_PER_WINDOW - this.sentInWindow
1404
+ );
1405
+ const slab = this.sendQueue.splice(0, slabSize);
1406
+ for (const change of slab) this.queuedHashes.delete(change.hash);
1407
+ this.publishChangeBatch(slab);
1408
+ this.sentInWindow += slab.length;
1409
+ }
1410
+ }
1357
1411
  publishChange(change) {
1358
1412
  if (!this.connection) return;
1359
1413
  this.connection.publish(this.room, {
@@ -1363,6 +1417,22 @@ var NodeStoreSyncProvider = class {
1363
1417
  });
1364
1418
  this.pushedThrough = Math.max(this.pushedThrough, change.lamport);
1365
1419
  }
1420
+ /**
1421
+ * Send many changes in one frame. The hub verifies and authorizes each one
1422
+ * individually and re-broadcasts them as ordinary `node-change` messages, so
1423
+ * this is purely a transport-level batch.
1424
+ */
1425
+ publishChangeBatch(changes) {
1426
+ if (!this.connection || changes.length === 0) return;
1427
+ this.connection.publish(this.room, {
1428
+ type: "node-change-batch",
1429
+ room: this.room,
1430
+ changes: changes.map((change) => this.serializeChange(change))
1431
+ });
1432
+ for (const change of changes) {
1433
+ this.pushedThrough = Math.max(this.pushedThrough, change.lamport);
1434
+ }
1435
+ }
1366
1436
  clearSendQueue() {
1367
1437
  if (this.sendTimer) {
1368
1438
  clearTimeout(this.sendTimer);
@@ -3320,6 +3390,54 @@ var WebSocketSyncProvider = class {
3320
3390
  };
3321
3391
  };
3322
3392
 
3393
+ // src/sync/replication-scope.ts
3394
+ function mayReceivePayload(trust, payload) {
3395
+ if (payload === "ciphertext") return true;
3396
+ return trust !== "zero-knowledge";
3397
+ }
3398
+ function spaceNamespace(ownerDID, spaceId) {
3399
+ return `xnet://${ownerDID}/space/${spaceId}/`;
3400
+ }
3401
+ function systemNamespace(ownerDID) {
3402
+ return `xnet://${ownerDID}/sys/`;
3403
+ }
3404
+ function namespaceForNode(node, fallbackOwnerDID) {
3405
+ const owner = node.createdBy ?? fallbackOwnerDID ?? "unknown";
3406
+ return node.space ? spaceNamespace(owner, node.space) : spaceNamespace(owner, node.id);
3407
+ }
3408
+ function replicationConfigFromPolicies(policies) {
3409
+ const hubsById = /* @__PURE__ */ new Map();
3410
+ const namespacePolicies = [];
3411
+ for (const policy of policies) {
3412
+ const includeHubIds = [];
3413
+ let minReplicas = 0;
3414
+ for (const destination of policy.destinations) {
3415
+ if (!hubsById.has(destination.hubId)) {
3416
+ hubsById.set(destination.hubId, {
3417
+ id: destination.hubId,
3418
+ url: destination.url,
3419
+ ...destination.priority === void 0 ? {} : { priority: destination.priority }
3420
+ });
3421
+ }
3422
+ includeHubIds.push(destination.hubId);
3423
+ if (destination.minReplicas && destination.minReplicas > minReplicas) {
3424
+ minReplicas = destination.minReplicas;
3425
+ }
3426
+ }
3427
+ namespacePolicies.push({
3428
+ namespace: spaceNamespace(policy.ownerDID, policy.space),
3429
+ includeHubIds,
3430
+ ...minReplicas > 0 ? { minHubs: minReplicas } : {}
3431
+ });
3432
+ }
3433
+ return {
3434
+ federation: {
3435
+ hubs: [...hubsById.values()],
3436
+ namespacePolicies
3437
+ }
3438
+ };
3439
+ }
3440
+
3323
3441
  // src/sync/MultiHubSyncManager.ts
3324
3442
  import {
3325
3443
  planReplicationDestinations
@@ -3410,10 +3528,21 @@ function createMultiHubSyncManager(config) {
3410
3528
  }
3411
3529
  };
3412
3530
  },
3413
- publishScoped(namespace, room, data) {
3531
+ publishScoped(namespace, room, data, opts) {
3532
+ const payload = opts?.payload ?? "plaintext";
3533
+ const published = [];
3534
+ const withheld = [];
3414
3535
  for (const hubId of reachableHubIds(namespace)) {
3415
- hubs.get(hubId)?.transport.publish(room, data);
3536
+ const hub = hubs.get(hubId);
3537
+ if (!hub) continue;
3538
+ if (!mayReceivePayload(hub.trust, payload)) {
3539
+ withheld.push(hubId);
3540
+ continue;
3541
+ }
3542
+ hub.transport.publish(room, data);
3543
+ published.push(hubId);
3416
3544
  }
3545
+ return { published, withheld };
3417
3546
  },
3418
3547
  setReplication(next) {
3419
3548
  replication = next;
@@ -3433,50 +3562,6 @@ function createMultiHubSyncManager(config) {
3433
3562
  };
3434
3563
  }
3435
3564
 
3436
- // src/sync/replication-scope.ts
3437
- function spaceNamespace(ownerDID, spaceId) {
3438
- return `xnet://${ownerDID}/space/${spaceId}/`;
3439
- }
3440
- function systemNamespace(ownerDID) {
3441
- return `xnet://${ownerDID}/sys/`;
3442
- }
3443
- function namespaceForNode(node, fallbackOwnerDID) {
3444
- const owner = node.createdBy ?? fallbackOwnerDID ?? "unknown";
3445
- return node.space ? spaceNamespace(owner, node.space) : spaceNamespace(owner, node.id);
3446
- }
3447
- function replicationConfigFromPolicies(policies) {
3448
- const hubsById = /* @__PURE__ */ new Map();
3449
- const namespacePolicies = [];
3450
- for (const policy of policies) {
3451
- const includeHubIds = [];
3452
- let minReplicas = 0;
3453
- for (const destination of policy.destinations) {
3454
- if (!hubsById.has(destination.hubId)) {
3455
- hubsById.set(destination.hubId, {
3456
- id: destination.hubId,
3457
- url: destination.url,
3458
- ...destination.priority === void 0 ? {} : { priority: destination.priority }
3459
- });
3460
- }
3461
- includeHubIds.push(destination.hubId);
3462
- if (destination.minReplicas && destination.minReplicas > minReplicas) {
3463
- minReplicas = destination.minReplicas;
3464
- }
3465
- }
3466
- namespacePolicies.push({
3467
- namespace: spaceNamespace(policy.ownerDID, policy.space),
3468
- includeHubIds,
3469
- ...minReplicas > 0 ? { minHubs: minReplicas } : {}
3470
- });
3471
- }
3472
- return {
3473
- federation: {
3474
- hubs: [...hubsById.values()],
3475
- namespacePolicies
3476
- }
3477
- };
3478
- }
3479
-
3480
3565
  // src/sync/InitialSyncManager.ts
3481
3566
  function createInitialSyncManager() {
3482
3567
  let progress = {
@@ -3618,6 +3703,7 @@ export {
3618
3703
  createXNetClient,
3619
3704
  isProtocolCompatible,
3620
3705
  liveQuery,
3706
+ mayReceivePayload,
3621
3707
  namespaceForNode,
3622
3708
  negotiateProtocolVersion,
3623
3709
  replicationConfigFromPolicies,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnetjs/runtime",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "description": "Framework-agnostic xNet runtime: createXNetClient() and sync orchestration, usable from any framework, a CLI, a worker, or a Node service",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -12,8 +12,8 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "exports": {
14
14
  ".": {
15
- "import": "./dist/index.js",
16
- "types": "./dist/index.d.ts"
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
17
  }
18
18
  },
19
19
  "files": [
@@ -28,15 +28,15 @@
28
28
  "dependencies": {
29
29
  "y-protocols": "^1.0.6",
30
30
  "yjs": "^13.6.24",
31
- "@xnetjs/core": "2.4.0",
32
- "@xnetjs/crypto": "2.4.0",
33
- "@xnetjs/data": "2.4.0",
34
- "@xnetjs/data-bridge": "2.4.0",
35
- "@xnetjs/history": "2.4.0",
36
- "@xnetjs/identity": "2.4.0",
37
- "@xnetjs/plugins": "2.4.0",
38
- "@xnetjs/storage": "2.4.0",
39
- "@xnetjs/sync": "2.4.0"
31
+ "@xnetjs/core": "3.0.0",
32
+ "@xnetjs/crypto": "3.0.0",
33
+ "@xnetjs/data": "3.0.0",
34
+ "@xnetjs/data-bridge": "3.0.0",
35
+ "@xnetjs/history": "3.0.0",
36
+ "@xnetjs/identity": "3.0.0",
37
+ "@xnetjs/plugins": "3.0.0",
38
+ "@xnetjs/storage": "3.0.0",
39
+ "@xnetjs/sync": "3.0.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "jsdom": "^26.0.0",