@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 +5 -0
- package/dist/index.d.ts +56 -9
- package/dist/index.js +138 -52
- package/package.json +12 -12
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.
|
|
798
|
-
*
|
|
799
|
-
*
|
|
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.
|
|
914
|
-
*
|
|
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
|
-
/**
|
|
951
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
this.
|
|
1351
|
-
|
|
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)
|
|
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.
|
|
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
|
-
"
|
|
16
|
-
"
|
|
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": "
|
|
32
|
-
"@xnetjs/crypto": "
|
|
33
|
-
"@xnetjs/data": "
|
|
34
|
-
"@xnetjs/data-bridge": "
|
|
35
|
-
"@xnetjs/history": "
|
|
36
|
-
"@xnetjs/identity": "
|
|
37
|
-
"@xnetjs/plugins": "
|
|
38
|
-
"@xnetjs/storage": "
|
|
39
|
-
"@xnetjs/sync": "
|
|
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",
|