@affectively/aeon 1.0.0 → 1.2.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 +10 -0
- package/dist/compression/index.cjs +580 -0
- package/dist/compression/index.cjs.map +1 -0
- package/dist/compression/index.d.cts +189 -0
- package/dist/compression/index.d.ts +189 -0
- package/dist/compression/index.js +573 -0
- package/dist/compression/index.js.map +1 -0
- package/dist/core/index.d.cts +70 -5
- package/dist/core/index.d.ts +70 -5
- package/dist/crypto/index.cjs +100 -0
- package/dist/crypto/index.cjs.map +1 -0
- package/dist/crypto/index.d.cts +407 -0
- package/dist/crypto/index.d.ts +407 -0
- package/dist/crypto/index.js +96 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/distributed/index.cjs +420 -23
- package/dist/distributed/index.cjs.map +1 -1
- package/dist/distributed/index.d.cts +901 -2
- package/dist/distributed/index.d.ts +901 -2
- package/dist/distributed/index.js +420 -23
- package/dist/distributed/index.js.map +1 -1
- package/dist/index.cjs +1222 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -811
- package/dist/index.d.ts +11 -811
- package/dist/index.js +1221 -56
- package/dist/index.js.map +1 -1
- package/dist/offline/index.cjs +419 -0
- package/dist/offline/index.cjs.map +1 -0
- package/dist/offline/index.d.cts +148 -0
- package/dist/offline/index.d.ts +148 -0
- package/dist/offline/index.js +415 -0
- package/dist/offline/index.js.map +1 -0
- package/dist/optimization/index.cjs +797 -0
- package/dist/optimization/index.cjs.map +1 -0
- package/dist/optimization/index.d.cts +347 -0
- package/dist/optimization/index.d.ts +347 -0
- package/dist/optimization/index.js +787 -0
- package/dist/optimization/index.js.map +1 -0
- package/dist/persistence/index.cjs +145 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +63 -0
- package/dist/persistence/index.d.ts +63 -0
- package/dist/persistence/index.js +142 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/presence/index.cjs +489 -0
- package/dist/presence/index.cjs.map +1 -0
- package/dist/presence/index.d.cts +283 -0
- package/dist/presence/index.d.ts +283 -0
- package/dist/presence/index.js +485 -0
- package/dist/presence/index.js.map +1 -0
- package/dist/types-CMxO7QF0.d.cts +33 -0
- package/dist/types-CMxO7QF0.d.ts +33 -0
- package/dist/versioning/index.cjs +296 -14
- package/dist/versioning/index.cjs.map +1 -1
- package/dist/versioning/index.d.cts +66 -1
- package/dist/versioning/index.d.ts +66 -1
- package/dist/versioning/index.js +296 -14
- package/dist/versioning/index.js.map +1 -1
- package/package.json +51 -1
- package/dist/index-C_4CMV5c.d.cts +0 -1207
- package/dist/index-C_4CMV5c.d.ts +0 -1207
package/dist/index.js
CHANGED
|
@@ -54,6 +54,145 @@ var logger = {
|
|
|
54
54
|
error: (...args) => getLogger().error(...args)
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
// src/persistence/DashStorageAdapter.ts
|
|
58
|
+
var DashStorageAdapter = class {
|
|
59
|
+
backend;
|
|
60
|
+
syncClient;
|
|
61
|
+
syncDebounceMs;
|
|
62
|
+
maxPendingChanges;
|
|
63
|
+
onSyncError;
|
|
64
|
+
pendingChanges = /* @__PURE__ */ new Map();
|
|
65
|
+
syncTimer = null;
|
|
66
|
+
syncInFlight = false;
|
|
67
|
+
syncPending = false;
|
|
68
|
+
constructor(backend, options = {}) {
|
|
69
|
+
this.backend = backend;
|
|
70
|
+
this.syncClient = options.syncClient ?? null;
|
|
71
|
+
this.syncDebounceMs = options.syncDebounceMs ?? 50;
|
|
72
|
+
this.maxPendingChanges = options.maxPendingChanges ?? 5e3;
|
|
73
|
+
this.onSyncError = options.onSyncError ?? null;
|
|
74
|
+
}
|
|
75
|
+
async getItem(key) {
|
|
76
|
+
return await this.backend.get(key);
|
|
77
|
+
}
|
|
78
|
+
async setItem(key, value) {
|
|
79
|
+
await this.backend.set(key, value);
|
|
80
|
+
this.trackChange({
|
|
81
|
+
key,
|
|
82
|
+
operation: "set",
|
|
83
|
+
value,
|
|
84
|
+
timestamp: Date.now()
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async removeItem(key) {
|
|
88
|
+
await this.backend.delete(key);
|
|
89
|
+
this.trackChange({
|
|
90
|
+
key,
|
|
91
|
+
operation: "delete",
|
|
92
|
+
timestamp: Date.now()
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
getPendingSyncCount() {
|
|
96
|
+
return this.pendingChanges.size;
|
|
97
|
+
}
|
|
98
|
+
async flushSync() {
|
|
99
|
+
if (!this.syncClient || this.pendingChanges.size === 0) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (this.syncTimer) {
|
|
103
|
+
clearTimeout(this.syncTimer);
|
|
104
|
+
this.syncTimer = null;
|
|
105
|
+
}
|
|
106
|
+
await this.performSync();
|
|
107
|
+
}
|
|
108
|
+
trackChange(change) {
|
|
109
|
+
this.pendingChanges.set(change.key, change);
|
|
110
|
+
this.enforcePendingLimit();
|
|
111
|
+
this.scheduleSync();
|
|
112
|
+
}
|
|
113
|
+
enforcePendingLimit() {
|
|
114
|
+
if (this.pendingChanges.size <= this.maxPendingChanges) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const sorted = Array.from(this.pendingChanges.values()).sort(
|
|
118
|
+
(a, b) => a.timestamp - b.timestamp
|
|
119
|
+
);
|
|
120
|
+
const overflow = this.pendingChanges.size - this.maxPendingChanges;
|
|
121
|
+
for (let i = 0; i < overflow; i++) {
|
|
122
|
+
const toDrop = sorted[i];
|
|
123
|
+
if (toDrop) {
|
|
124
|
+
this.pendingChanges.delete(toDrop.key);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
scheduleSync() {
|
|
129
|
+
if (!this.syncClient) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (this.syncTimer) {
|
|
133
|
+
clearTimeout(this.syncTimer);
|
|
134
|
+
}
|
|
135
|
+
this.syncTimer = setTimeout(() => {
|
|
136
|
+
void this.performSync();
|
|
137
|
+
}, this.syncDebounceMs);
|
|
138
|
+
}
|
|
139
|
+
async performSync() {
|
|
140
|
+
if (!this.syncClient) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (this.syncInFlight) {
|
|
144
|
+
this.syncPending = true;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const changes = Array.from(this.pendingChanges.values()).sort(
|
|
148
|
+
(a, b) => a.timestamp - b.timestamp
|
|
149
|
+
);
|
|
150
|
+
if (changes.length === 0) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.pendingChanges.clear();
|
|
154
|
+
this.syncInFlight = true;
|
|
155
|
+
try {
|
|
156
|
+
await this.syncClient.syncChanges(changes);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
for (const change of changes) {
|
|
159
|
+
const current = this.pendingChanges.get(change.key);
|
|
160
|
+
if (!current || change.timestamp > current.timestamp) {
|
|
161
|
+
this.pendingChanges.set(change.key, change);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (this.onSyncError) {
|
|
165
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
166
|
+
this.onSyncError(normalizedError, changes);
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
this.syncInFlight = false;
|
|
170
|
+
const rerun = this.syncPending || this.pendingChanges.size > 0;
|
|
171
|
+
this.syncPending = false;
|
|
172
|
+
if (rerun) {
|
|
173
|
+
this.scheduleSync();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// src/persistence/InMemoryStorageAdapter.ts
|
|
180
|
+
var InMemoryStorageAdapter = class {
|
|
181
|
+
store = /* @__PURE__ */ new Map();
|
|
182
|
+
getItem(key) {
|
|
183
|
+
return this.store.get(key) ?? null;
|
|
184
|
+
}
|
|
185
|
+
setItem(key, value) {
|
|
186
|
+
this.store.set(key, value);
|
|
187
|
+
}
|
|
188
|
+
removeItem(key) {
|
|
189
|
+
this.store.delete(key);
|
|
190
|
+
}
|
|
191
|
+
clear() {
|
|
192
|
+
this.store.clear();
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
57
196
|
// src/versioning/SchemaVersionManager.ts
|
|
58
197
|
var SchemaVersionManager = class {
|
|
59
198
|
versions = /* @__PURE__ */ new Map();
|
|
@@ -105,7 +244,9 @@ var SchemaVersionManager = class {
|
|
|
105
244
|
*/
|
|
106
245
|
setCurrentVersion(version) {
|
|
107
246
|
if (!this.versions.has(this.versionToString(version))) {
|
|
108
|
-
throw new Error(
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Version ${this.versionToString(version)} not registered`
|
|
249
|
+
);
|
|
109
250
|
}
|
|
110
251
|
this.currentVersion = version;
|
|
111
252
|
logger.debug("[SchemaVersionManager] Current version set", {
|
|
@@ -391,7 +532,9 @@ var MigrationEngine = class {
|
|
|
391
532
|
id: migrationId,
|
|
392
533
|
error: result.errors[0]
|
|
393
534
|
});
|
|
394
|
-
throw new Error(
|
|
535
|
+
throw new Error(
|
|
536
|
+
`Rollback for ${migrationId} failed: ${result.errors[0]}`
|
|
537
|
+
);
|
|
395
538
|
}
|
|
396
539
|
}
|
|
397
540
|
/**
|
|
@@ -444,8 +587,14 @@ var MigrationEngine = class {
|
|
|
444
587
|
getStatistics() {
|
|
445
588
|
const successful = this.executedMigrations.filter((m) => m.success).length;
|
|
446
589
|
const failed = this.executedMigrations.filter((m) => !m.success).length;
|
|
447
|
-
const totalDuration = this.executedMigrations.reduce(
|
|
448
|
-
|
|
590
|
+
const totalDuration = this.executedMigrations.reduce(
|
|
591
|
+
(sum, m) => sum + m.duration,
|
|
592
|
+
0
|
|
593
|
+
);
|
|
594
|
+
const totalAffected = this.executedMigrations.reduce(
|
|
595
|
+
(sum, m) => sum + m.itemsAffected,
|
|
596
|
+
0
|
|
597
|
+
);
|
|
449
598
|
return {
|
|
450
599
|
totalExecuted: this.executedMigrations.length,
|
|
451
600
|
successful,
|
|
@@ -499,7 +648,9 @@ var DataTransformer = class {
|
|
|
499
648
|
return rule.transformer(value);
|
|
500
649
|
} catch (error) {
|
|
501
650
|
if (rule.required) {
|
|
502
|
-
throw new Error(
|
|
651
|
+
throw new Error(
|
|
652
|
+
`Failed to transform required field ${field}: ${error instanceof Error ? error.message : String(error)}`
|
|
653
|
+
);
|
|
503
654
|
}
|
|
504
655
|
return rule.defaultValue !== void 0 ? rule.defaultValue : value;
|
|
505
656
|
}
|
|
@@ -572,7 +723,9 @@ var DataTransformer = class {
|
|
|
572
723
|
validateTransformation(original, transformed) {
|
|
573
724
|
const issues = [];
|
|
574
725
|
if (original.length !== transformed.length) {
|
|
575
|
-
issues.push(
|
|
726
|
+
issues.push(
|
|
727
|
+
`Item count mismatch: ${original.length} -> ${transformed.length}`
|
|
728
|
+
);
|
|
576
729
|
}
|
|
577
730
|
for (let i = 0; i < Math.min(original.length, transformed.length); i++) {
|
|
578
731
|
const orig = original[i];
|
|
@@ -663,14 +816,40 @@ var DataTransformer = class {
|
|
|
663
816
|
};
|
|
664
817
|
|
|
665
818
|
// src/versioning/MigrationTracker.ts
|
|
666
|
-
var MigrationTracker = class {
|
|
819
|
+
var MigrationTracker = class _MigrationTracker {
|
|
820
|
+
static DEFAULT_PERSIST_KEY = "aeon:migration-tracker:v1";
|
|
821
|
+
static INTEGRITY_ROOT = "aeon:migration-integrity-root:v1";
|
|
667
822
|
migrations = [];
|
|
668
823
|
snapshots = /* @__PURE__ */ new Map();
|
|
824
|
+
persistence = null;
|
|
825
|
+
persistTimer = null;
|
|
826
|
+
persistInFlight = false;
|
|
827
|
+
persistPending = false;
|
|
828
|
+
constructor(options) {
|
|
829
|
+
if (options?.persistence) {
|
|
830
|
+
this.persistence = {
|
|
831
|
+
...options.persistence,
|
|
832
|
+
key: options.persistence.key ?? _MigrationTracker.DEFAULT_PERSIST_KEY,
|
|
833
|
+
autoPersist: options.persistence.autoPersist ?? true,
|
|
834
|
+
autoLoad: options.persistence.autoLoad ?? false,
|
|
835
|
+
persistDebounceMs: options.persistence.persistDebounceMs ?? 25
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
if (this.persistence?.autoLoad) {
|
|
839
|
+
void this.loadFromPersistence().catch((error) => {
|
|
840
|
+
logger.error("[MigrationTracker] Failed to load persistence", {
|
|
841
|
+
key: this.persistence?.key,
|
|
842
|
+
error: error instanceof Error ? error.message : String(error)
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
669
847
|
/**
|
|
670
848
|
* Track a new migration
|
|
671
849
|
*/
|
|
672
850
|
recordMigration(record) {
|
|
673
851
|
this.migrations.push({ ...record });
|
|
852
|
+
this.schedulePersist();
|
|
674
853
|
logger.debug("[MigrationTracker] Migration recorded", {
|
|
675
854
|
id: record.id,
|
|
676
855
|
migrationId: record.migrationId,
|
|
@@ -727,7 +906,9 @@ var MigrationTracker = class {
|
|
|
727
906
|
* Check if can rollback
|
|
728
907
|
*/
|
|
729
908
|
canRollback(fromVersion, toVersion) {
|
|
730
|
-
const fromIndex = this.migrations.findIndex(
|
|
909
|
+
const fromIndex = this.migrations.findIndex(
|
|
910
|
+
(m) => m.version === fromVersion
|
|
911
|
+
);
|
|
731
912
|
const toIndex = this.migrations.findIndex((m) => m.version === toVersion);
|
|
732
913
|
if (fromIndex === -1 || toIndex === -1) {
|
|
733
914
|
return false;
|
|
@@ -751,7 +932,9 @@ var MigrationTracker = class {
|
|
|
751
932
|
const affectedVersions = [];
|
|
752
933
|
let estimatedDuration = 0;
|
|
753
934
|
if (canRollback) {
|
|
754
|
-
const fromIndex = this.migrations.findIndex(
|
|
935
|
+
const fromIndex = this.migrations.findIndex(
|
|
936
|
+
(m) => m.version === fromVersion
|
|
937
|
+
);
|
|
755
938
|
const toIndex = this.migrations.findIndex((m) => m.version === toVersion);
|
|
756
939
|
for (let i = fromIndex; i > toIndex; i--) {
|
|
757
940
|
const migration = this.migrations[i];
|
|
@@ -807,12 +990,24 @@ var MigrationTracker = class {
|
|
|
807
990
|
* Get migration statistics
|
|
808
991
|
*/
|
|
809
992
|
getStatistics() {
|
|
810
|
-
const applied = this.migrations.filter(
|
|
993
|
+
const applied = this.migrations.filter(
|
|
994
|
+
(m) => m.status === "applied"
|
|
995
|
+
).length;
|
|
811
996
|
const failed = this.migrations.filter((m) => m.status === "failed").length;
|
|
812
|
-
const pending = this.migrations.filter(
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const
|
|
997
|
+
const pending = this.migrations.filter(
|
|
998
|
+
(m) => m.status === "pending"
|
|
999
|
+
).length;
|
|
1000
|
+
const rolledBack = this.migrations.filter(
|
|
1001
|
+
(m) => m.status === "rolled-back"
|
|
1002
|
+
).length;
|
|
1003
|
+
const totalDuration = this.migrations.reduce(
|
|
1004
|
+
(sum, m) => sum + m.duration,
|
|
1005
|
+
0
|
|
1006
|
+
);
|
|
1007
|
+
const totalAffected = this.migrations.reduce(
|
|
1008
|
+
(sum, m) => sum + m.itemsAffected,
|
|
1009
|
+
0
|
|
1010
|
+
);
|
|
816
1011
|
return {
|
|
817
1012
|
total: this.migrations.length,
|
|
818
1013
|
applied,
|
|
@@ -863,7 +1058,147 @@ var MigrationTracker = class {
|
|
|
863
1058
|
status,
|
|
864
1059
|
hasError: !!error
|
|
865
1060
|
});
|
|
1061
|
+
this.schedulePersist();
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Persist tracker state with integrity chain verification metadata.
|
|
1066
|
+
*/
|
|
1067
|
+
async saveToPersistence() {
|
|
1068
|
+
if (!this.persistence) {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
const normalizedMigrations = this.migrations.map((migration) => ({
|
|
1072
|
+
...migration,
|
|
1073
|
+
previousHash: void 0,
|
|
1074
|
+
integrityHash: void 0
|
|
1075
|
+
}));
|
|
1076
|
+
const integrityEntries = [];
|
|
1077
|
+
let previousHash = _MigrationTracker.INTEGRITY_ROOT;
|
|
1078
|
+
for (const migration of normalizedMigrations) {
|
|
1079
|
+
const hash = await this.computeDigestHex(
|
|
1080
|
+
`${previousHash}|${this.stableStringify(migration)}`
|
|
1081
|
+
);
|
|
1082
|
+
integrityEntries.push({
|
|
1083
|
+
recordId: migration.id,
|
|
1084
|
+
previousHash,
|
|
1085
|
+
hash
|
|
1086
|
+
});
|
|
1087
|
+
previousHash = hash;
|
|
1088
|
+
}
|
|
1089
|
+
const persistedMigrations = normalizedMigrations.map((migration, index) => ({
|
|
1090
|
+
...migration,
|
|
1091
|
+
previousHash: integrityEntries[index]?.previousHash,
|
|
1092
|
+
integrityHash: integrityEntries[index]?.hash
|
|
1093
|
+
}));
|
|
1094
|
+
const data = {
|
|
1095
|
+
migrations: persistedMigrations,
|
|
1096
|
+
snapshots: Array.from(this.snapshots.entries()).map(
|
|
1097
|
+
([recordId, snapshot]) => ({
|
|
1098
|
+
recordId,
|
|
1099
|
+
beforeHash: snapshot.beforeHash,
|
|
1100
|
+
afterHash: snapshot.afterHash,
|
|
1101
|
+
itemCount: snapshot.itemCount
|
|
1102
|
+
})
|
|
1103
|
+
),
|
|
1104
|
+
integrity: {
|
|
1105
|
+
algorithm: "sha256-chain-v1",
|
|
1106
|
+
entries: integrityEntries,
|
|
1107
|
+
rootHash: previousHash
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
const envelope = {
|
|
1111
|
+
version: 1,
|
|
1112
|
+
updatedAt: Date.now(),
|
|
1113
|
+
data
|
|
1114
|
+
};
|
|
1115
|
+
const serialize = this.persistence.serializer ?? ((value) => JSON.stringify(value));
|
|
1116
|
+
await this.persistence.adapter.setItem(this.persistence.key, serialize(envelope));
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Load tracker state and verify integrity chain.
|
|
1120
|
+
*/
|
|
1121
|
+
async loadFromPersistence() {
|
|
1122
|
+
if (!this.persistence) {
|
|
1123
|
+
return { migrations: 0, snapshots: 0 };
|
|
1124
|
+
}
|
|
1125
|
+
const raw = await this.persistence.adapter.getItem(this.persistence.key);
|
|
1126
|
+
if (!raw) {
|
|
1127
|
+
return { migrations: 0, snapshots: 0 };
|
|
1128
|
+
}
|
|
1129
|
+
const deserialize = this.persistence.deserializer ?? ((value) => JSON.parse(value));
|
|
1130
|
+
const envelope = deserialize(raw);
|
|
1131
|
+
if (envelope.version !== 1 || !envelope.data) {
|
|
1132
|
+
throw new Error("Invalid migration tracker persistence payload");
|
|
1133
|
+
}
|
|
1134
|
+
if (!Array.isArray(envelope.data.migrations) || !Array.isArray(envelope.data.snapshots) || !envelope.data.integrity || !Array.isArray(envelope.data.integrity.entries) || typeof envelope.data.integrity.rootHash !== "string") {
|
|
1135
|
+
throw new Error("Invalid migration tracker persistence structure");
|
|
1136
|
+
}
|
|
1137
|
+
if (envelope.data.integrity.algorithm !== "sha256-chain-v1") {
|
|
1138
|
+
throw new Error("Unsupported migration integrity algorithm");
|
|
1139
|
+
}
|
|
1140
|
+
if (envelope.data.integrity.entries.length !== envelope.data.migrations.length) {
|
|
1141
|
+
throw new Error("Migration integrity entry count mismatch");
|
|
1142
|
+
}
|
|
1143
|
+
const validatedMigrations = [];
|
|
1144
|
+
let previousHash = _MigrationTracker.INTEGRITY_ROOT;
|
|
1145
|
+
for (let i = 0; i < envelope.data.migrations.length; i++) {
|
|
1146
|
+
const migration = envelope.data.migrations[i];
|
|
1147
|
+
const integrity = envelope.data.integrity.entries[i];
|
|
1148
|
+
if (!this.isValidMigrationRecord(migration)) {
|
|
1149
|
+
throw new Error("Invalid persisted migration record");
|
|
1150
|
+
}
|
|
1151
|
+
if (!integrity || integrity.recordId !== migration.id || integrity.previousHash !== previousHash) {
|
|
1152
|
+
throw new Error("Migration integrity chain mismatch");
|
|
1153
|
+
}
|
|
1154
|
+
const expectedHash = await this.computeDigestHex(
|
|
1155
|
+
`${previousHash}|${this.stableStringify({
|
|
1156
|
+
...migration,
|
|
1157
|
+
previousHash: void 0,
|
|
1158
|
+
integrityHash: void 0
|
|
1159
|
+
})}`
|
|
1160
|
+
);
|
|
1161
|
+
if (expectedHash !== integrity.hash) {
|
|
1162
|
+
throw new Error("Migration integrity verification failed");
|
|
1163
|
+
}
|
|
1164
|
+
validatedMigrations.push({
|
|
1165
|
+
...migration,
|
|
1166
|
+
previousHash: integrity.previousHash,
|
|
1167
|
+
integrityHash: integrity.hash
|
|
1168
|
+
});
|
|
1169
|
+
previousHash = expectedHash;
|
|
1170
|
+
}
|
|
1171
|
+
if (previousHash !== envelope.data.integrity.rootHash) {
|
|
1172
|
+
throw new Error("Migration integrity root hash mismatch");
|
|
1173
|
+
}
|
|
1174
|
+
const validatedSnapshots = /* @__PURE__ */ new Map();
|
|
1175
|
+
for (const snapshot of envelope.data.snapshots) {
|
|
1176
|
+
if (typeof snapshot.recordId !== "string" || typeof snapshot.beforeHash !== "string" || typeof snapshot.afterHash !== "string" || typeof snapshot.itemCount !== "number") {
|
|
1177
|
+
throw new Error("Invalid persisted migration snapshot");
|
|
1178
|
+
}
|
|
1179
|
+
validatedSnapshots.set(snapshot.recordId, {
|
|
1180
|
+
beforeHash: snapshot.beforeHash,
|
|
1181
|
+
afterHash: snapshot.afterHash,
|
|
1182
|
+
itemCount: snapshot.itemCount
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
this.migrations = validatedMigrations;
|
|
1186
|
+
this.snapshots = validatedSnapshots;
|
|
1187
|
+
logger.debug("[MigrationTracker] Loaded from persistence", {
|
|
1188
|
+
key: this.persistence.key,
|
|
1189
|
+
migrations: this.migrations.length,
|
|
1190
|
+
snapshots: this.snapshots.size
|
|
1191
|
+
});
|
|
1192
|
+
return { migrations: this.migrations.length, snapshots: this.snapshots.size };
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Remove persisted migration tracker state.
|
|
1196
|
+
*/
|
|
1197
|
+
async clearPersistence() {
|
|
1198
|
+
if (!this.persistence) {
|
|
1199
|
+
return;
|
|
866
1200
|
}
|
|
1201
|
+
await this.persistence.adapter.removeItem(this.persistence.key);
|
|
867
1202
|
}
|
|
868
1203
|
/**
|
|
869
1204
|
* Clear history (for testing)
|
|
@@ -871,6 +1206,7 @@ var MigrationTracker = class {
|
|
|
871
1206
|
clear() {
|
|
872
1207
|
this.migrations = [];
|
|
873
1208
|
this.snapshots.clear();
|
|
1209
|
+
this.schedulePersist();
|
|
874
1210
|
}
|
|
875
1211
|
/**
|
|
876
1212
|
* Get total migrations tracked
|
|
@@ -889,6 +1225,91 @@ var MigrationTracker = class {
|
|
|
889
1225
|
return time >= start && time <= end;
|
|
890
1226
|
});
|
|
891
1227
|
}
|
|
1228
|
+
schedulePersist() {
|
|
1229
|
+
if (!this.persistence || this.persistence.autoPersist === false) {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (this.persistTimer) {
|
|
1233
|
+
clearTimeout(this.persistTimer);
|
|
1234
|
+
}
|
|
1235
|
+
this.persistTimer = setTimeout(() => {
|
|
1236
|
+
void this.persistSafely();
|
|
1237
|
+
}, this.persistence.persistDebounceMs ?? 25);
|
|
1238
|
+
}
|
|
1239
|
+
async persistSafely() {
|
|
1240
|
+
if (!this.persistence) {
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (this.persistInFlight) {
|
|
1244
|
+
this.persistPending = true;
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
this.persistInFlight = true;
|
|
1248
|
+
try {
|
|
1249
|
+
await this.saveToPersistence();
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
logger.error("[MigrationTracker] Persistence write failed", {
|
|
1252
|
+
key: this.persistence.key,
|
|
1253
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1254
|
+
});
|
|
1255
|
+
} finally {
|
|
1256
|
+
this.persistInFlight = false;
|
|
1257
|
+
const shouldRunAgain = this.persistPending;
|
|
1258
|
+
this.persistPending = false;
|
|
1259
|
+
if (shouldRunAgain) {
|
|
1260
|
+
void this.persistSafely();
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
isValidMigrationRecord(value) {
|
|
1265
|
+
if (typeof value !== "object" || value === null) {
|
|
1266
|
+
return false;
|
|
1267
|
+
}
|
|
1268
|
+
const record = value;
|
|
1269
|
+
const validDirection = record.direction === "up" || record.direction === "down";
|
|
1270
|
+
const validStatus = record.status === "pending" || record.status === "applied" || record.status === "failed" || record.status === "rolled-back";
|
|
1271
|
+
return typeof record.id === "string" && typeof record.migrationId === "string" && typeof record.timestamp === "string" && typeof record.version === "string" && validDirection && validStatus && typeof record.duration === "number" && typeof record.itemsAffected === "number" && typeof record.appliedBy === "string";
|
|
1272
|
+
}
|
|
1273
|
+
stableStringify(value) {
|
|
1274
|
+
if (value === null || typeof value !== "object") {
|
|
1275
|
+
return JSON.stringify(value);
|
|
1276
|
+
}
|
|
1277
|
+
if (Array.isArray(value)) {
|
|
1278
|
+
return `[${value.map((item) => this.stableStringify(item)).join(",")}]`;
|
|
1279
|
+
}
|
|
1280
|
+
const entries = Object.entries(value).sort(
|
|
1281
|
+
([a], [b]) => a.localeCompare(b)
|
|
1282
|
+
);
|
|
1283
|
+
return `{${entries.map(
|
|
1284
|
+
([key, entryValue]) => `${JSON.stringify(key)}:${this.stableStringify(entryValue)}`
|
|
1285
|
+
).join(",")}}`;
|
|
1286
|
+
}
|
|
1287
|
+
async computeDigestHex(value) {
|
|
1288
|
+
if (globalThis.crypto?.subtle) {
|
|
1289
|
+
const bytes = new TextEncoder().encode(value);
|
|
1290
|
+
const normalized = bytes.buffer.slice(
|
|
1291
|
+
bytes.byteOffset,
|
|
1292
|
+
bytes.byteOffset + bytes.byteLength
|
|
1293
|
+
);
|
|
1294
|
+
const digest = await globalThis.crypto.subtle.digest(
|
|
1295
|
+
"SHA-256",
|
|
1296
|
+
normalized
|
|
1297
|
+
);
|
|
1298
|
+
return this.toHex(new Uint8Array(digest));
|
|
1299
|
+
}
|
|
1300
|
+
return this.fallbackDigestHex(value);
|
|
1301
|
+
}
|
|
1302
|
+
toHex(bytes) {
|
|
1303
|
+
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1304
|
+
}
|
|
1305
|
+
fallbackDigestHex(value) {
|
|
1306
|
+
let hash = 2166136261;
|
|
1307
|
+
for (let i = 0; i < value.length; i++) {
|
|
1308
|
+
hash ^= value.charCodeAt(i);
|
|
1309
|
+
hash = Math.imul(hash, 16777619);
|
|
1310
|
+
}
|
|
1311
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
1312
|
+
}
|
|
892
1313
|
};
|
|
893
1314
|
var SyncCoordinator = class extends EventEmitter {
|
|
894
1315
|
nodes = /* @__PURE__ */ new Map();
|
|
@@ -1246,7 +1667,9 @@ var SyncCoordinator = class extends EventEmitter {
|
|
|
1246
1667
|
* Get active sync sessions
|
|
1247
1668
|
*/
|
|
1248
1669
|
getActiveSyncSessions() {
|
|
1249
|
-
return Array.from(this.sessions.values()).filter(
|
|
1670
|
+
return Array.from(this.sessions.values()).filter(
|
|
1671
|
+
(s) => s.status === "active"
|
|
1672
|
+
);
|
|
1250
1673
|
}
|
|
1251
1674
|
/**
|
|
1252
1675
|
* Get sessions for a node
|
|
@@ -1264,8 +1687,14 @@ var SyncCoordinator = class extends EventEmitter {
|
|
|
1264
1687
|
const completed = sessions.filter((s) => s.status === "completed").length;
|
|
1265
1688
|
const failed = sessions.filter((s) => s.status === "failed").length;
|
|
1266
1689
|
const active = sessions.filter((s) => s.status === "active").length;
|
|
1267
|
-
const totalItemsSynced = sessions.reduce(
|
|
1268
|
-
|
|
1690
|
+
const totalItemsSynced = sessions.reduce(
|
|
1691
|
+
(sum, s) => sum + s.itemsSynced,
|
|
1692
|
+
0
|
|
1693
|
+
);
|
|
1694
|
+
const totalConflicts = sessions.reduce(
|
|
1695
|
+
(sum, s) => sum + s.conflictsDetected,
|
|
1696
|
+
0
|
|
1697
|
+
);
|
|
1269
1698
|
return {
|
|
1270
1699
|
totalNodes: this.nodes.size,
|
|
1271
1700
|
onlineNodes: this.getOnlineNodes().length,
|
|
@@ -1332,7 +1761,9 @@ var SyncCoordinator = class extends EventEmitter {
|
|
|
1332
1761
|
}
|
|
1333
1762
|
}
|
|
1334
1763
|
}, interval);
|
|
1335
|
-
logger.debug("[SyncCoordinator] Heartbeat monitoring started", {
|
|
1764
|
+
logger.debug("[SyncCoordinator] Heartbeat monitoring started", {
|
|
1765
|
+
interval
|
|
1766
|
+
});
|
|
1336
1767
|
}
|
|
1337
1768
|
/**
|
|
1338
1769
|
* Stop heartbeat monitoring
|
|
@@ -1365,7 +1796,8 @@ var SyncCoordinator = class extends EventEmitter {
|
|
|
1365
1796
|
};
|
|
1366
1797
|
|
|
1367
1798
|
// src/distributed/ReplicationManager.ts
|
|
1368
|
-
var ReplicationManager = class {
|
|
1799
|
+
var ReplicationManager = class _ReplicationManager {
|
|
1800
|
+
static DEFAULT_PERSIST_KEY = "aeon:replication-state:v1";
|
|
1369
1801
|
replicas = /* @__PURE__ */ new Map();
|
|
1370
1802
|
policies = /* @__PURE__ */ new Map();
|
|
1371
1803
|
replicationEvents = [];
|
|
@@ -1374,6 +1806,29 @@ var ReplicationManager = class {
|
|
|
1374
1806
|
cryptoProvider = null;
|
|
1375
1807
|
replicasByDID = /* @__PURE__ */ new Map();
|
|
1376
1808
|
// DID -> replicaId
|
|
1809
|
+
persistence = null;
|
|
1810
|
+
persistTimer = null;
|
|
1811
|
+
persistInFlight = false;
|
|
1812
|
+
persistPending = false;
|
|
1813
|
+
constructor(options) {
|
|
1814
|
+
if (options?.persistence) {
|
|
1815
|
+
this.persistence = {
|
|
1816
|
+
...options.persistence,
|
|
1817
|
+
key: options.persistence.key ?? _ReplicationManager.DEFAULT_PERSIST_KEY,
|
|
1818
|
+
autoPersist: options.persistence.autoPersist ?? true,
|
|
1819
|
+
autoLoad: options.persistence.autoLoad ?? false,
|
|
1820
|
+
persistDebounceMs: options.persistence.persistDebounceMs ?? 25
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
if (this.persistence?.autoLoad) {
|
|
1824
|
+
void this.loadFromPersistence().catch((error) => {
|
|
1825
|
+
logger.error("[ReplicationManager] Failed to load persistence", {
|
|
1826
|
+
key: this.persistence?.key,
|
|
1827
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1828
|
+
});
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1377
1832
|
/**
|
|
1378
1833
|
* Configure cryptographic provider for encrypted replication
|
|
1379
1834
|
*/
|
|
@@ -1418,6 +1873,7 @@ var ReplicationManager = class {
|
|
|
1418
1873
|
details: { did: replica.did, encrypted, authenticated: true }
|
|
1419
1874
|
};
|
|
1420
1875
|
this.replicationEvents.push(event);
|
|
1876
|
+
this.schedulePersist();
|
|
1421
1877
|
logger.debug("[ReplicationManager] Authenticated replica registered", {
|
|
1422
1878
|
replicaId: replica.id,
|
|
1423
1879
|
did: replica.did,
|
|
@@ -1447,7 +1903,10 @@ var ReplicationManager = class {
|
|
|
1447
1903
|
throw new Error("Crypto provider not initialized");
|
|
1448
1904
|
}
|
|
1449
1905
|
const dataBytes = new TextEncoder().encode(JSON.stringify(data));
|
|
1450
|
-
const encrypted = await this.cryptoProvider.encrypt(
|
|
1906
|
+
const encrypted = await this.cryptoProvider.encrypt(
|
|
1907
|
+
dataBytes,
|
|
1908
|
+
targetReplicaDID
|
|
1909
|
+
);
|
|
1451
1910
|
const localDID = this.cryptoProvider.getLocalDID();
|
|
1452
1911
|
return {
|
|
1453
1912
|
ct: encrypted.ct,
|
|
@@ -1516,10 +1975,13 @@ var ReplicationManager = class {
|
|
|
1516
1975
|
}))
|
|
1517
1976
|
});
|
|
1518
1977
|
if (!result.authorized) {
|
|
1519
|
-
logger.warn(
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1978
|
+
logger.warn(
|
|
1979
|
+
"[ReplicationManager] Replica capability verification failed",
|
|
1980
|
+
{
|
|
1981
|
+
replicaDID,
|
|
1982
|
+
error: result.error
|
|
1983
|
+
}
|
|
1984
|
+
);
|
|
1523
1985
|
}
|
|
1524
1986
|
return result;
|
|
1525
1987
|
}
|
|
@@ -1538,6 +2000,7 @@ var ReplicationManager = class {
|
|
|
1538
2000
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1539
2001
|
};
|
|
1540
2002
|
this.replicationEvents.push(event);
|
|
2003
|
+
this.schedulePersist();
|
|
1541
2004
|
logger.debug("[ReplicationManager] Replica registered", {
|
|
1542
2005
|
replicaId: replica.id,
|
|
1543
2006
|
nodeId: replica.nodeId,
|
|
@@ -1560,6 +2023,7 @@ var ReplicationManager = class {
|
|
|
1560
2023
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1561
2024
|
};
|
|
1562
2025
|
this.replicationEvents.push(event);
|
|
2026
|
+
this.schedulePersist();
|
|
1563
2027
|
logger.debug("[ReplicationManager] Replica removed", { replicaId });
|
|
1564
2028
|
}
|
|
1565
2029
|
/**
|
|
@@ -1575,6 +2039,7 @@ var ReplicationManager = class {
|
|
|
1575
2039
|
maxReplicationLag
|
|
1576
2040
|
};
|
|
1577
2041
|
this.policies.set(policy.id, policy);
|
|
2042
|
+
this.schedulePersist();
|
|
1578
2043
|
logger.debug("[ReplicationManager] Policy created", {
|
|
1579
2044
|
policyId: policy.id,
|
|
1580
2045
|
name,
|
|
@@ -1617,12 +2082,15 @@ var ReplicationManager = class {
|
|
|
1617
2082
|
lagBytes,
|
|
1618
2083
|
lagMillis
|
|
1619
2084
|
});
|
|
2085
|
+
this.schedulePersist();
|
|
1620
2086
|
}
|
|
1621
2087
|
/**
|
|
1622
2088
|
* Get replicas for node
|
|
1623
2089
|
*/
|
|
1624
2090
|
getReplicasForNode(nodeId) {
|
|
1625
|
-
return Array.from(this.replicas.values()).filter(
|
|
2091
|
+
return Array.from(this.replicas.values()).filter(
|
|
2092
|
+
(r) => r.nodeId === nodeId
|
|
2093
|
+
);
|
|
1626
2094
|
}
|
|
1627
2095
|
/**
|
|
1628
2096
|
* Get healthy replicas
|
|
@@ -1636,13 +2104,17 @@ var ReplicationManager = class {
|
|
|
1636
2104
|
* Get syncing replicas
|
|
1637
2105
|
*/
|
|
1638
2106
|
getSyncingReplicas() {
|
|
1639
|
-
return Array.from(this.replicas.values()).filter(
|
|
2107
|
+
return Array.from(this.replicas.values()).filter(
|
|
2108
|
+
(r) => r.status === "syncing"
|
|
2109
|
+
);
|
|
1640
2110
|
}
|
|
1641
2111
|
/**
|
|
1642
2112
|
* Get failed replicas
|
|
1643
2113
|
*/
|
|
1644
2114
|
getFailedReplicas() {
|
|
1645
|
-
return Array.from(this.replicas.values()).filter(
|
|
2115
|
+
return Array.from(this.replicas.values()).filter(
|
|
2116
|
+
(r) => r.status === "failed"
|
|
2117
|
+
);
|
|
1646
2118
|
}
|
|
1647
2119
|
/**
|
|
1648
2120
|
* Check replication health for policy
|
|
@@ -1703,7 +2175,9 @@ var ReplicationManager = class {
|
|
|
1703
2175
|
const syncing = this.getSyncingReplicas().length;
|
|
1704
2176
|
const failed = this.getFailedReplicas().length;
|
|
1705
2177
|
const total = this.replicas.size;
|
|
1706
|
-
const replicationLags = Array.from(this.replicas.values()).map(
|
|
2178
|
+
const replicationLags = Array.from(this.replicas.values()).map(
|
|
2179
|
+
(r) => r.lagMillis
|
|
2180
|
+
);
|
|
1707
2181
|
const avgLag = replicationLags.length > 0 ? replicationLags.reduce((a, b) => a + b) / replicationLags.length : 0;
|
|
1708
2182
|
const maxLag = replicationLags.length > 0 ? Math.max(...replicationLags) : 0;
|
|
1709
2183
|
return {
|
|
@@ -1777,6 +2251,155 @@ var ReplicationManager = class {
|
|
|
1777
2251
|
return false;
|
|
1778
2252
|
}
|
|
1779
2253
|
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Persist current replication state snapshot.
|
|
2256
|
+
*/
|
|
2257
|
+
async saveToPersistence() {
|
|
2258
|
+
if (!this.persistence) {
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
const data = {
|
|
2262
|
+
replicas: this.getAllReplicas(),
|
|
2263
|
+
policies: this.getAllPolicies(),
|
|
2264
|
+
syncStatus: Array.from(this.syncStatus.entries()).map(
|
|
2265
|
+
([nodeId, state]) => ({
|
|
2266
|
+
nodeId,
|
|
2267
|
+
synced: state.synced,
|
|
2268
|
+
failed: state.failed
|
|
2269
|
+
})
|
|
2270
|
+
)
|
|
2271
|
+
};
|
|
2272
|
+
const envelope = {
|
|
2273
|
+
version: 1,
|
|
2274
|
+
updatedAt: Date.now(),
|
|
2275
|
+
data
|
|
2276
|
+
};
|
|
2277
|
+
const serialize = this.persistence.serializer ?? ((value) => JSON.stringify(value));
|
|
2278
|
+
await this.persistence.adapter.setItem(this.persistence.key, serialize(envelope));
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Load replication snapshot from persistence.
|
|
2282
|
+
*/
|
|
2283
|
+
async loadFromPersistence() {
|
|
2284
|
+
if (!this.persistence) {
|
|
2285
|
+
return { replicas: 0, policies: 0, syncStatus: 0 };
|
|
2286
|
+
}
|
|
2287
|
+
const raw = await this.persistence.adapter.getItem(this.persistence.key);
|
|
2288
|
+
if (!raw) {
|
|
2289
|
+
return { replicas: 0, policies: 0, syncStatus: 0 };
|
|
2290
|
+
}
|
|
2291
|
+
const deserialize = this.persistence.deserializer ?? ((value) => JSON.parse(value));
|
|
2292
|
+
const envelope = deserialize(raw);
|
|
2293
|
+
if (envelope.version !== 1 || !envelope.data) {
|
|
2294
|
+
throw new Error("Invalid replication persistence payload");
|
|
2295
|
+
}
|
|
2296
|
+
if (!Array.isArray(envelope.data.replicas) || !Array.isArray(envelope.data.policies) || !Array.isArray(envelope.data.syncStatus)) {
|
|
2297
|
+
throw new Error("Invalid replication persistence structure");
|
|
2298
|
+
}
|
|
2299
|
+
this.replicas.clear();
|
|
2300
|
+
this.policies.clear();
|
|
2301
|
+
this.syncStatus.clear();
|
|
2302
|
+
this.replicasByDID.clear();
|
|
2303
|
+
let importedReplicas = 0;
|
|
2304
|
+
for (const replica of envelope.data.replicas) {
|
|
2305
|
+
if (this.isValidReplica(replica)) {
|
|
2306
|
+
this.replicas.set(replica.id, replica);
|
|
2307
|
+
if (replica.did) {
|
|
2308
|
+
this.replicasByDID.set(replica.did, replica.id);
|
|
2309
|
+
}
|
|
2310
|
+
importedReplicas++;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
let importedPolicies = 0;
|
|
2314
|
+
for (const policy of envelope.data.policies) {
|
|
2315
|
+
if (this.isValidPolicy(policy)) {
|
|
2316
|
+
this.policies.set(policy.id, policy);
|
|
2317
|
+
importedPolicies++;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
let importedSyncStatus = 0;
|
|
2321
|
+
for (const status of envelope.data.syncStatus) {
|
|
2322
|
+
if (typeof status.nodeId === "string" && typeof status.synced === "number" && typeof status.failed === "number") {
|
|
2323
|
+
this.syncStatus.set(status.nodeId, {
|
|
2324
|
+
synced: status.synced,
|
|
2325
|
+
failed: status.failed
|
|
2326
|
+
});
|
|
2327
|
+
importedSyncStatus++;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
logger.debug("[ReplicationManager] Loaded from persistence", {
|
|
2331
|
+
key: this.persistence.key,
|
|
2332
|
+
replicas: importedReplicas,
|
|
2333
|
+
policies: importedPolicies,
|
|
2334
|
+
syncStatus: importedSyncStatus
|
|
2335
|
+
});
|
|
2336
|
+
return {
|
|
2337
|
+
replicas: importedReplicas,
|
|
2338
|
+
policies: importedPolicies,
|
|
2339
|
+
syncStatus: importedSyncStatus
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
/**
|
|
2343
|
+
* Remove persisted replication snapshot.
|
|
2344
|
+
*/
|
|
2345
|
+
async clearPersistence() {
|
|
2346
|
+
if (!this.persistence) {
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
await this.persistence.adapter.removeItem(this.persistence.key);
|
|
2350
|
+
}
|
|
2351
|
+
schedulePersist() {
|
|
2352
|
+
if (!this.persistence || this.persistence.autoPersist === false) {
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
if (this.persistTimer) {
|
|
2356
|
+
clearTimeout(this.persistTimer);
|
|
2357
|
+
}
|
|
2358
|
+
this.persistTimer = setTimeout(() => {
|
|
2359
|
+
void this.persistSafely();
|
|
2360
|
+
}, this.persistence.persistDebounceMs ?? 25);
|
|
2361
|
+
}
|
|
2362
|
+
async persistSafely() {
|
|
2363
|
+
if (!this.persistence) {
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
if (this.persistInFlight) {
|
|
2367
|
+
this.persistPending = true;
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
this.persistInFlight = true;
|
|
2371
|
+
try {
|
|
2372
|
+
await this.saveToPersistence();
|
|
2373
|
+
} catch (error) {
|
|
2374
|
+
logger.error("[ReplicationManager] Persistence write failed", {
|
|
2375
|
+
key: this.persistence.key,
|
|
2376
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2377
|
+
});
|
|
2378
|
+
} finally {
|
|
2379
|
+
this.persistInFlight = false;
|
|
2380
|
+
const shouldRunAgain = this.persistPending;
|
|
2381
|
+
this.persistPending = false;
|
|
2382
|
+
if (shouldRunAgain) {
|
|
2383
|
+
void this.persistSafely();
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
isValidReplica(value) {
|
|
2388
|
+
if (typeof value !== "object" || value === null) {
|
|
2389
|
+
return false;
|
|
2390
|
+
}
|
|
2391
|
+
const candidate = value;
|
|
2392
|
+
const validStatus = candidate.status === "primary" || candidate.status === "secondary" || candidate.status === "syncing" || candidate.status === "failed";
|
|
2393
|
+
return typeof candidate.id === "string" && typeof candidate.nodeId === "string" && validStatus && typeof candidate.lastSyncTime === "string" && typeof candidate.lagBytes === "number" && typeof candidate.lagMillis === "number";
|
|
2394
|
+
}
|
|
2395
|
+
isValidPolicy(value) {
|
|
2396
|
+
if (typeof value !== "object" || value === null) {
|
|
2397
|
+
return false;
|
|
2398
|
+
}
|
|
2399
|
+
const candidate = value;
|
|
2400
|
+
const validConsistency = candidate.consistencyLevel === "eventual" || candidate.consistencyLevel === "read-after-write" || candidate.consistencyLevel === "strong";
|
|
2401
|
+
return typeof candidate.id === "string" && typeof candidate.name === "string" && typeof candidate.replicationFactor === "number" && validConsistency && typeof candidate.syncInterval === "number" && typeof candidate.maxReplicationLag === "number";
|
|
2402
|
+
}
|
|
1780
2403
|
/**
|
|
1781
2404
|
* Clear all state (for testing)
|
|
1782
2405
|
*/
|
|
@@ -1787,6 +2410,7 @@ var ReplicationManager = class {
|
|
|
1787
2410
|
this.syncStatus.clear();
|
|
1788
2411
|
this.replicasByDID.clear();
|
|
1789
2412
|
this.cryptoProvider = null;
|
|
2413
|
+
this.schedulePersist();
|
|
1790
2414
|
}
|
|
1791
2415
|
/**
|
|
1792
2416
|
* Get the crypto provider (for advanced usage)
|
|
@@ -1797,7 +2421,8 @@ var ReplicationManager = class {
|
|
|
1797
2421
|
};
|
|
1798
2422
|
|
|
1799
2423
|
// src/distributed/SyncProtocol.ts
|
|
1800
|
-
var SyncProtocol = class {
|
|
2424
|
+
var SyncProtocol = class _SyncProtocol {
|
|
2425
|
+
static DEFAULT_PERSIST_KEY = "aeon:sync-protocol:v1";
|
|
1801
2426
|
version = "1.0.0";
|
|
1802
2427
|
messageQueue = [];
|
|
1803
2428
|
messageMap = /* @__PURE__ */ new Map();
|
|
@@ -1807,6 +2432,29 @@ var SyncProtocol = class {
|
|
|
1807
2432
|
// Crypto support
|
|
1808
2433
|
cryptoProvider = null;
|
|
1809
2434
|
cryptoConfig = null;
|
|
2435
|
+
persistence = null;
|
|
2436
|
+
persistTimer = null;
|
|
2437
|
+
persistInFlight = false;
|
|
2438
|
+
persistPending = false;
|
|
2439
|
+
constructor(options) {
|
|
2440
|
+
if (options?.persistence) {
|
|
2441
|
+
this.persistence = {
|
|
2442
|
+
...options.persistence,
|
|
2443
|
+
key: options.persistence.key ?? _SyncProtocol.DEFAULT_PERSIST_KEY,
|
|
2444
|
+
autoPersist: options.persistence.autoPersist ?? true,
|
|
2445
|
+
autoLoad: options.persistence.autoLoad ?? false,
|
|
2446
|
+
persistDebounceMs: options.persistence.persistDebounceMs ?? 25
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
if (this.persistence?.autoLoad) {
|
|
2450
|
+
void this.loadFromPersistence().catch((error) => {
|
|
2451
|
+
logger.error("[SyncProtocol] Failed to load persistence", {
|
|
2452
|
+
key: this.persistence?.key,
|
|
2453
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2454
|
+
});
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
1810
2458
|
/**
|
|
1811
2459
|
* Configure cryptographic provider for authenticated/encrypted messages
|
|
1812
2460
|
*/
|
|
@@ -1894,6 +2542,7 @@ var SyncProtocol = class {
|
|
|
1894
2542
|
}
|
|
1895
2543
|
this.messageMap.set(message.messageId, message);
|
|
1896
2544
|
this.messageQueue.push(message);
|
|
2545
|
+
this.schedulePersist();
|
|
1897
2546
|
logger.debug("[SyncProtocol] Authenticated handshake created", {
|
|
1898
2547
|
messageId: message.messageId,
|
|
1899
2548
|
did: localDID,
|
|
@@ -1912,6 +2561,7 @@ var SyncProtocol = class {
|
|
|
1912
2561
|
const handshake = message.payload;
|
|
1913
2562
|
if (!this.cryptoProvider || !this.cryptoConfig) {
|
|
1914
2563
|
this.handshakes.set(message.sender, handshake);
|
|
2564
|
+
this.schedulePersist();
|
|
1915
2565
|
return { valid: true, handshake };
|
|
1916
2566
|
}
|
|
1917
2567
|
if (handshake.did && handshake.publicSigningKey) {
|
|
@@ -1954,6 +2604,7 @@ var SyncProtocol = class {
|
|
|
1954
2604
|
}
|
|
1955
2605
|
}
|
|
1956
2606
|
this.handshakes.set(message.sender, handshake);
|
|
2607
|
+
this.schedulePersist();
|
|
1957
2608
|
logger.debug("[SyncProtocol] Authenticated handshake verified", {
|
|
1958
2609
|
messageId: message.messageId,
|
|
1959
2610
|
did: handshake.did
|
|
@@ -1977,7 +2628,10 @@ var SyncProtocol = class {
|
|
|
1977
2628
|
};
|
|
1978
2629
|
if (encrypt && message.receiver && this.cryptoConfig?.encryptionMode !== "none") {
|
|
1979
2630
|
const payloadBytes = new TextEncoder().encode(JSON.stringify(payload));
|
|
1980
|
-
const encrypted = await this.cryptoProvider.encrypt(
|
|
2631
|
+
const encrypted = await this.cryptoProvider.encrypt(
|
|
2632
|
+
payloadBytes,
|
|
2633
|
+
message.receiver
|
|
2634
|
+
);
|
|
1981
2635
|
message.payload = encrypted;
|
|
1982
2636
|
message.auth.encrypted = true;
|
|
1983
2637
|
logger.debug("[SyncProtocol] Message encrypted", {
|
|
@@ -2050,6 +2704,7 @@ var SyncProtocol = class {
|
|
|
2050
2704
|
};
|
|
2051
2705
|
this.messageMap.set(message.messageId, message);
|
|
2052
2706
|
this.messageQueue.push(message);
|
|
2707
|
+
this.schedulePersist();
|
|
2053
2708
|
logger.debug("[SyncProtocol] Handshake message created", {
|
|
2054
2709
|
messageId: message.messageId,
|
|
2055
2710
|
nodeId,
|
|
@@ -2077,6 +2732,7 @@ var SyncProtocol = class {
|
|
|
2077
2732
|
};
|
|
2078
2733
|
this.messageMap.set(message.messageId, message);
|
|
2079
2734
|
this.messageQueue.push(message);
|
|
2735
|
+
this.schedulePersist();
|
|
2080
2736
|
logger.debug("[SyncProtocol] Sync request created", {
|
|
2081
2737
|
messageId: message.messageId,
|
|
2082
2738
|
sessionId,
|
|
@@ -2107,6 +2763,7 @@ var SyncProtocol = class {
|
|
|
2107
2763
|
};
|
|
2108
2764
|
this.messageMap.set(message.messageId, message);
|
|
2109
2765
|
this.messageQueue.push(message);
|
|
2766
|
+
this.schedulePersist();
|
|
2110
2767
|
logger.debug("[SyncProtocol] Sync response created", {
|
|
2111
2768
|
messageId: message.messageId,
|
|
2112
2769
|
sessionId,
|
|
@@ -2130,6 +2787,7 @@ var SyncProtocol = class {
|
|
|
2130
2787
|
};
|
|
2131
2788
|
this.messageMap.set(message.messageId, message);
|
|
2132
2789
|
this.messageQueue.push(message);
|
|
2790
|
+
this.schedulePersist();
|
|
2133
2791
|
return message;
|
|
2134
2792
|
}
|
|
2135
2793
|
/**
|
|
@@ -2154,6 +2812,7 @@ var SyncProtocol = class {
|
|
|
2154
2812
|
error,
|
|
2155
2813
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2156
2814
|
});
|
|
2815
|
+
this.schedulePersist();
|
|
2157
2816
|
logger.error("[SyncProtocol] Error message created", {
|
|
2158
2817
|
messageId: message.messageId,
|
|
2159
2818
|
errorCode: error.code,
|
|
@@ -2178,9 +2837,8 @@ var SyncProtocol = class {
|
|
|
2178
2837
|
if (!message.timestamp) {
|
|
2179
2838
|
errors.push("Timestamp is required");
|
|
2180
2839
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
} catch {
|
|
2840
|
+
const timestampValue = new Date(message.timestamp);
|
|
2841
|
+
if (Number.isNaN(timestampValue.getTime())) {
|
|
2184
2842
|
errors.push("Invalid timestamp format");
|
|
2185
2843
|
}
|
|
2186
2844
|
return {
|
|
@@ -2199,7 +2857,9 @@ var SyncProtocol = class {
|
|
|
2199
2857
|
messageId: message.messageId,
|
|
2200
2858
|
error: error instanceof Error ? error.message : String(error)
|
|
2201
2859
|
});
|
|
2202
|
-
throw new Error(
|
|
2860
|
+
throw new Error(
|
|
2861
|
+
`Failed to serialize message: ${error instanceof Error ? error.message : String(error)}`
|
|
2862
|
+
);
|
|
2203
2863
|
}
|
|
2204
2864
|
}
|
|
2205
2865
|
/**
|
|
@@ -2217,7 +2877,9 @@ var SyncProtocol = class {
|
|
|
2217
2877
|
logger.error("[SyncProtocol] Message deserialization failed", {
|
|
2218
2878
|
error: error instanceof Error ? error.message : String(error)
|
|
2219
2879
|
});
|
|
2220
|
-
throw new Error(
|
|
2880
|
+
throw new Error(
|
|
2881
|
+
`Failed to deserialize message: ${error instanceof Error ? error.message : String(error)}`
|
|
2882
|
+
);
|
|
2221
2883
|
}
|
|
2222
2884
|
}
|
|
2223
2885
|
/**
|
|
@@ -2230,6 +2892,7 @@ var SyncProtocol = class {
|
|
|
2230
2892
|
const handshake = message.payload;
|
|
2231
2893
|
const nodeId = message.sender;
|
|
2232
2894
|
this.handshakes.set(nodeId, handshake);
|
|
2895
|
+
this.schedulePersist();
|
|
2233
2896
|
logger.debug("[SyncProtocol] Handshake processed", {
|
|
2234
2897
|
nodeId,
|
|
2235
2898
|
protocolVersion: handshake.protocolVersion,
|
|
@@ -2282,7 +2945,9 @@ var SyncProtocol = class {
|
|
|
2282
2945
|
messagesByType[message.type] = (messagesByType[message.type] || 0) + 1;
|
|
2283
2946
|
}
|
|
2284
2947
|
const errorCount = this.protocolErrors.length;
|
|
2285
|
-
const recoverableErrors = this.protocolErrors.filter(
|
|
2948
|
+
const recoverableErrors = this.protocolErrors.filter(
|
|
2949
|
+
(e) => e.error.recoverable
|
|
2950
|
+
).length;
|
|
2286
2951
|
return {
|
|
2287
2952
|
totalMessages: this.messageQueue.length,
|
|
2288
2953
|
messagesByType,
|
|
@@ -2298,6 +2963,156 @@ var SyncProtocol = class {
|
|
|
2298
2963
|
getErrors() {
|
|
2299
2964
|
return [...this.protocolErrors];
|
|
2300
2965
|
}
|
|
2966
|
+
/**
|
|
2967
|
+
* Persist protocol state for reconnect/replay.
|
|
2968
|
+
*/
|
|
2969
|
+
async saveToPersistence() {
|
|
2970
|
+
if (!this.persistence) {
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
const data = {
|
|
2974
|
+
protocolVersion: this.version,
|
|
2975
|
+
messageCounter: this.messageCounter,
|
|
2976
|
+
messageQueue: this.getAllMessages(),
|
|
2977
|
+
handshakes: Array.from(this.handshakes.entries()).map(
|
|
2978
|
+
([nodeId, handshake]) => ({
|
|
2979
|
+
nodeId,
|
|
2980
|
+
handshake
|
|
2981
|
+
})
|
|
2982
|
+
),
|
|
2983
|
+
protocolErrors: this.getErrors()
|
|
2984
|
+
};
|
|
2985
|
+
const envelope = {
|
|
2986
|
+
version: 1,
|
|
2987
|
+
updatedAt: Date.now(),
|
|
2988
|
+
data
|
|
2989
|
+
};
|
|
2990
|
+
const serialize = this.persistence.serializer ?? ((value) => JSON.stringify(value));
|
|
2991
|
+
await this.persistence.adapter.setItem(this.persistence.key, serialize(envelope));
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Load protocol state from persistence.
|
|
2995
|
+
*/
|
|
2996
|
+
async loadFromPersistence() {
|
|
2997
|
+
if (!this.persistence) {
|
|
2998
|
+
return { messages: 0, handshakes: 0, errors: 0 };
|
|
2999
|
+
}
|
|
3000
|
+
const raw = await this.persistence.adapter.getItem(this.persistence.key);
|
|
3001
|
+
if (!raw) {
|
|
3002
|
+
return { messages: 0, handshakes: 0, errors: 0 };
|
|
3003
|
+
}
|
|
3004
|
+
const deserialize = this.persistence.deserializer ?? ((value) => JSON.parse(value));
|
|
3005
|
+
const envelope = deserialize(raw);
|
|
3006
|
+
if (envelope.version !== 1 || !envelope.data) {
|
|
3007
|
+
throw new Error("Invalid sync protocol persistence payload");
|
|
3008
|
+
}
|
|
3009
|
+
if (!Array.isArray(envelope.data.messageQueue) || !Array.isArray(envelope.data.handshakes) || !Array.isArray(envelope.data.protocolErrors)) {
|
|
3010
|
+
throw new Error("Invalid sync protocol persistence structure");
|
|
3011
|
+
}
|
|
3012
|
+
const nextMessages = [];
|
|
3013
|
+
for (const message of envelope.data.messageQueue) {
|
|
3014
|
+
const validation = this.validateMessage(message);
|
|
3015
|
+
if (!validation.valid) {
|
|
3016
|
+
throw new Error(
|
|
3017
|
+
`Invalid persisted message ${message?.messageId ?? "unknown"}: ${validation.errors.join(", ")}`
|
|
3018
|
+
);
|
|
3019
|
+
}
|
|
3020
|
+
nextMessages.push(message);
|
|
3021
|
+
}
|
|
3022
|
+
const nextHandshakes = /* @__PURE__ */ new Map();
|
|
3023
|
+
for (const entry of envelope.data.handshakes) {
|
|
3024
|
+
if (typeof entry.nodeId !== "string" || !this.isValidHandshake(entry.handshake)) {
|
|
3025
|
+
throw new Error("Invalid persisted handshake payload");
|
|
3026
|
+
}
|
|
3027
|
+
nextHandshakes.set(entry.nodeId, entry.handshake);
|
|
3028
|
+
}
|
|
3029
|
+
const nextErrors = [];
|
|
3030
|
+
for (const entry of envelope.data.protocolErrors) {
|
|
3031
|
+
if (!this.isValidProtocolErrorEntry(entry)) {
|
|
3032
|
+
throw new Error("Invalid persisted protocol error payload");
|
|
3033
|
+
}
|
|
3034
|
+
nextErrors.push(entry);
|
|
3035
|
+
}
|
|
3036
|
+
this.messageQueue = nextMessages;
|
|
3037
|
+
this.messageMap = new Map(nextMessages.map((m) => [m.messageId, m]));
|
|
3038
|
+
this.handshakes = nextHandshakes;
|
|
3039
|
+
this.protocolErrors = nextErrors;
|
|
3040
|
+
this.messageCounter = Math.max(
|
|
3041
|
+
envelope.data.messageCounter || 0,
|
|
3042
|
+
this.messageQueue.length
|
|
3043
|
+
);
|
|
3044
|
+
logger.debug("[SyncProtocol] Loaded from persistence", {
|
|
3045
|
+
key: this.persistence.key,
|
|
3046
|
+
messages: this.messageQueue.length,
|
|
3047
|
+
handshakes: this.handshakes.size,
|
|
3048
|
+
errors: this.protocolErrors.length
|
|
3049
|
+
});
|
|
3050
|
+
return {
|
|
3051
|
+
messages: this.messageQueue.length,
|
|
3052
|
+
handshakes: this.handshakes.size,
|
|
3053
|
+
errors: this.protocolErrors.length
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
/**
|
|
3057
|
+
* Clear persisted protocol checkpoint.
|
|
3058
|
+
*/
|
|
3059
|
+
async clearPersistence() {
|
|
3060
|
+
if (!this.persistence) {
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
3063
|
+
await this.persistence.adapter.removeItem(this.persistence.key);
|
|
3064
|
+
}
|
|
3065
|
+
schedulePersist() {
|
|
3066
|
+
if (!this.persistence || this.persistence.autoPersist === false) {
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
if (this.persistTimer) {
|
|
3070
|
+
clearTimeout(this.persistTimer);
|
|
3071
|
+
}
|
|
3072
|
+
this.persistTimer = setTimeout(() => {
|
|
3073
|
+
void this.persistSafely();
|
|
3074
|
+
}, this.persistence.persistDebounceMs ?? 25);
|
|
3075
|
+
}
|
|
3076
|
+
async persistSafely() {
|
|
3077
|
+
if (!this.persistence) {
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3080
|
+
if (this.persistInFlight) {
|
|
3081
|
+
this.persistPending = true;
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
this.persistInFlight = true;
|
|
3085
|
+
try {
|
|
3086
|
+
await this.saveToPersistence();
|
|
3087
|
+
} catch (error) {
|
|
3088
|
+
logger.error("[SyncProtocol] Persistence write failed", {
|
|
3089
|
+
key: this.persistence.key,
|
|
3090
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3091
|
+
});
|
|
3092
|
+
} finally {
|
|
3093
|
+
this.persistInFlight = false;
|
|
3094
|
+
const shouldRunAgain = this.persistPending;
|
|
3095
|
+
this.persistPending = false;
|
|
3096
|
+
if (shouldRunAgain) {
|
|
3097
|
+
void this.persistSafely();
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
isValidHandshake(value) {
|
|
3102
|
+
if (typeof value !== "object" || value === null) {
|
|
3103
|
+
return false;
|
|
3104
|
+
}
|
|
3105
|
+
const handshake = value;
|
|
3106
|
+
const validState = handshake.state === "initiating" || handshake.state === "responding" || handshake.state === "completed";
|
|
3107
|
+
return typeof handshake.protocolVersion === "string" && typeof handshake.nodeId === "string" && Array.isArray(handshake.capabilities) && handshake.capabilities.every((cap) => typeof cap === "string") && validState;
|
|
3108
|
+
}
|
|
3109
|
+
isValidProtocolErrorEntry(entry) {
|
|
3110
|
+
if (typeof entry !== "object" || entry === null) {
|
|
3111
|
+
return false;
|
|
3112
|
+
}
|
|
3113
|
+
const candidate = entry;
|
|
3114
|
+
return typeof candidate.timestamp === "string" && typeof candidate.error?.code === "string" && typeof candidate.error.message === "string" && typeof candidate.error.recoverable === "boolean";
|
|
3115
|
+
}
|
|
2301
3116
|
/**
|
|
2302
3117
|
* Generate message ID
|
|
2303
3118
|
*/
|
|
@@ -2316,6 +3131,7 @@ var SyncProtocol = class {
|
|
|
2316
3131
|
this.messageCounter = 0;
|
|
2317
3132
|
this.cryptoProvider = null;
|
|
2318
3133
|
this.cryptoConfig = null;
|
|
3134
|
+
this.schedulePersist();
|
|
2319
3135
|
}
|
|
2320
3136
|
/**
|
|
2321
3137
|
* Get the crypto provider (for advanced usage)
|
|
@@ -2701,7 +3517,9 @@ var StateReconciler = class {
|
|
|
2701
3517
|
}
|
|
2702
3518
|
return {
|
|
2703
3519
|
totalReconciliations: this.reconciliationHistory.length,
|
|
2704
|
-
successfulReconciliations: this.reconciliationHistory.filter(
|
|
3520
|
+
successfulReconciliations: this.reconciliationHistory.filter(
|
|
3521
|
+
(r) => r.success
|
|
3522
|
+
).length,
|
|
2705
3523
|
totalConflictsResolved: resolvedConflicts,
|
|
2706
3524
|
averageConflictsPerReconciliation: this.reconciliationHistory.length > 0 ? resolvedConflicts / this.reconciliationHistory.length : 0,
|
|
2707
3525
|
strategyUsage,
|
|
@@ -2725,18 +3543,46 @@ var StateReconciler = class {
|
|
|
2725
3543
|
}
|
|
2726
3544
|
};
|
|
2727
3545
|
var logger2 = getLogger();
|
|
2728
|
-
var OfflineOperationQueue = class extends EventEmitter {
|
|
3546
|
+
var OfflineOperationQueue = class _OfflineOperationQueue extends EventEmitter {
|
|
3547
|
+
static DEFAULT_PERSIST_KEY = "aeon:offline-queue:v1";
|
|
2729
3548
|
queue = /* @__PURE__ */ new Map();
|
|
2730
3549
|
syncingIds = /* @__PURE__ */ new Set();
|
|
2731
3550
|
maxQueueSize = 1e3;
|
|
2732
3551
|
defaultMaxRetries = 3;
|
|
2733
|
-
|
|
3552
|
+
persistence = null;
|
|
3553
|
+
persistTimer = null;
|
|
3554
|
+
persistInFlight = false;
|
|
3555
|
+
persistPending = false;
|
|
3556
|
+
constructor(maxQueueSizeOrOptions = 1e3, defaultMaxRetries = 3) {
|
|
2734
3557
|
super();
|
|
2735
|
-
|
|
2736
|
-
|
|
3558
|
+
if (typeof maxQueueSizeOrOptions === "number") {
|
|
3559
|
+
this.maxQueueSize = maxQueueSizeOrOptions;
|
|
3560
|
+
this.defaultMaxRetries = defaultMaxRetries;
|
|
3561
|
+
} else {
|
|
3562
|
+
this.maxQueueSize = maxQueueSizeOrOptions.maxQueueSize ?? 1e3;
|
|
3563
|
+
this.defaultMaxRetries = maxQueueSizeOrOptions.defaultMaxRetries ?? 3;
|
|
3564
|
+
if (maxQueueSizeOrOptions.persistence) {
|
|
3565
|
+
this.persistence = {
|
|
3566
|
+
...maxQueueSizeOrOptions.persistence,
|
|
3567
|
+
key: maxQueueSizeOrOptions.persistence.key ?? _OfflineOperationQueue.DEFAULT_PERSIST_KEY,
|
|
3568
|
+
autoPersist: maxQueueSizeOrOptions.persistence.autoPersist ?? true,
|
|
3569
|
+
autoLoad: maxQueueSizeOrOptions.persistence.autoLoad ?? false,
|
|
3570
|
+
persistDebounceMs: maxQueueSizeOrOptions.persistence.persistDebounceMs ?? 25
|
|
3571
|
+
};
|
|
3572
|
+
if (this.persistence.autoLoad) {
|
|
3573
|
+
void this.loadFromPersistence().catch((error) => {
|
|
3574
|
+
logger2.error("[OfflineOperationQueue] Failed to load persistence", {
|
|
3575
|
+
key: this.persistence?.key,
|
|
3576
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3577
|
+
});
|
|
3578
|
+
});
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
2737
3582
|
logger2.debug("[OfflineOperationQueue] Initialized", {
|
|
2738
|
-
maxQueueSize,
|
|
2739
|
-
defaultMaxRetries
|
|
3583
|
+
maxQueueSize: this.maxQueueSize,
|
|
3584
|
+
defaultMaxRetries: this.defaultMaxRetries,
|
|
3585
|
+
persistenceEnabled: this.persistence !== null
|
|
2740
3586
|
});
|
|
2741
3587
|
}
|
|
2742
3588
|
/**
|
|
@@ -2765,6 +3611,7 @@ var OfflineOperationQueue = class extends EventEmitter {
|
|
|
2765
3611
|
};
|
|
2766
3612
|
this.queue.set(operation.id, operation);
|
|
2767
3613
|
this.emit("operation-added", operation);
|
|
3614
|
+
this.schedulePersist();
|
|
2768
3615
|
logger2.debug("[OfflineOperationQueue] Operation enqueued", {
|
|
2769
3616
|
id: operation.id,
|
|
2770
3617
|
type,
|
|
@@ -2789,13 +3636,18 @@ var OfflineOperationQueue = class extends EventEmitter {
|
|
|
2789
3636
|
* Mark operations as syncing
|
|
2790
3637
|
*/
|
|
2791
3638
|
markSyncing(operationIds) {
|
|
3639
|
+
let changed = false;
|
|
2792
3640
|
for (const id of operationIds) {
|
|
2793
3641
|
const op = this.queue.get(id);
|
|
2794
3642
|
if (op) {
|
|
2795
3643
|
op.status = "syncing";
|
|
2796
3644
|
this.syncingIds.add(id);
|
|
3645
|
+
changed = true;
|
|
2797
3646
|
}
|
|
2798
3647
|
}
|
|
3648
|
+
if (changed) {
|
|
3649
|
+
this.schedulePersist();
|
|
3650
|
+
}
|
|
2799
3651
|
}
|
|
2800
3652
|
/**
|
|
2801
3653
|
* Mark operation as synced
|
|
@@ -2806,8 +3658,10 @@ var OfflineOperationQueue = class extends EventEmitter {
|
|
|
2806
3658
|
op.status = "synced";
|
|
2807
3659
|
this.syncingIds.delete(operationId);
|
|
2808
3660
|
this.emit("operation-synced", op);
|
|
3661
|
+
this.schedulePersist();
|
|
2809
3662
|
setTimeout(() => {
|
|
2810
3663
|
this.queue.delete(operationId);
|
|
3664
|
+
this.schedulePersist();
|
|
2811
3665
|
if (this.getPendingCount() === 0) {
|
|
2812
3666
|
this.emit("queue-empty");
|
|
2813
3667
|
}
|
|
@@ -2839,6 +3693,7 @@ var OfflineOperationQueue = class extends EventEmitter {
|
|
|
2839
3693
|
maxRetries: op.maxRetries
|
|
2840
3694
|
});
|
|
2841
3695
|
}
|
|
3696
|
+
this.schedulePersist();
|
|
2842
3697
|
}
|
|
2843
3698
|
}
|
|
2844
3699
|
/**
|
|
@@ -2891,28 +3746,39 @@ var OfflineOperationQueue = class extends EventEmitter {
|
|
|
2891
3746
|
clear() {
|
|
2892
3747
|
this.queue.clear();
|
|
2893
3748
|
this.syncingIds.clear();
|
|
3749
|
+
this.schedulePersist();
|
|
2894
3750
|
logger2.debug("[OfflineOperationQueue] Queue cleared");
|
|
2895
3751
|
}
|
|
2896
3752
|
/**
|
|
2897
3753
|
* Clear failed operations
|
|
2898
3754
|
*/
|
|
2899
3755
|
clearFailed() {
|
|
3756
|
+
let changed = false;
|
|
2900
3757
|
for (const [id, op] of this.queue.entries()) {
|
|
2901
3758
|
if (op.status === "failed") {
|
|
2902
3759
|
this.queue.delete(id);
|
|
3760
|
+
changed = true;
|
|
2903
3761
|
}
|
|
2904
3762
|
}
|
|
3763
|
+
if (changed) {
|
|
3764
|
+
this.schedulePersist();
|
|
3765
|
+
}
|
|
2905
3766
|
}
|
|
2906
3767
|
/**
|
|
2907
3768
|
* Retry failed operations
|
|
2908
3769
|
*/
|
|
2909
3770
|
retryFailed() {
|
|
3771
|
+
let changed = false;
|
|
2910
3772
|
for (const op of this.queue.values()) {
|
|
2911
3773
|
if (op.status === "failed") {
|
|
2912
3774
|
op.status = "pending";
|
|
2913
3775
|
op.retryCount = 0;
|
|
3776
|
+
changed = true;
|
|
2914
3777
|
}
|
|
2915
3778
|
}
|
|
3779
|
+
if (changed) {
|
|
3780
|
+
this.schedulePersist();
|
|
3781
|
+
}
|
|
2916
3782
|
}
|
|
2917
3783
|
/**
|
|
2918
3784
|
* Find oldest low-priority operation
|
|
@@ -2931,12 +3797,125 @@ var OfflineOperationQueue = class extends EventEmitter {
|
|
|
2931
3797
|
* Import queue from persistence
|
|
2932
3798
|
*/
|
|
2933
3799
|
import(operations) {
|
|
3800
|
+
this.queue.clear();
|
|
3801
|
+
this.syncingIds.clear();
|
|
2934
3802
|
for (const op of operations) {
|
|
2935
|
-
this.
|
|
3803
|
+
if (this.isValidOfflineOperation(op)) {
|
|
3804
|
+
this.queue.set(op.id, {
|
|
3805
|
+
...op,
|
|
3806
|
+
status: op.status === "syncing" ? "pending" : op.status
|
|
3807
|
+
});
|
|
3808
|
+
}
|
|
2936
3809
|
}
|
|
3810
|
+
this.schedulePersist();
|
|
2937
3811
|
logger2.debug("[OfflineOperationQueue] Imported operations", {
|
|
2938
|
-
count:
|
|
3812
|
+
count: this.queue.size
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3815
|
+
/**
|
|
3816
|
+
* Persist current queue snapshot.
|
|
3817
|
+
*/
|
|
3818
|
+
async saveToPersistence() {
|
|
3819
|
+
if (!this.persistence) {
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3822
|
+
const envelope = {
|
|
3823
|
+
version: 1,
|
|
3824
|
+
updatedAt: Date.now(),
|
|
3825
|
+
data: this.export()
|
|
3826
|
+
};
|
|
3827
|
+
const serialize = this.persistence.serializer ?? ((value) => JSON.stringify(value));
|
|
3828
|
+
const raw = serialize(envelope);
|
|
3829
|
+
await this.persistence.adapter.setItem(this.persistence.key, raw);
|
|
3830
|
+
}
|
|
3831
|
+
/**
|
|
3832
|
+
* Load queue snapshot from persistence.
|
|
3833
|
+
*/
|
|
3834
|
+
async loadFromPersistence() {
|
|
3835
|
+
if (!this.persistence) {
|
|
3836
|
+
return 0;
|
|
3837
|
+
}
|
|
3838
|
+
const raw = await this.persistence.adapter.getItem(this.persistence.key);
|
|
3839
|
+
if (!raw) {
|
|
3840
|
+
return 0;
|
|
3841
|
+
}
|
|
3842
|
+
const deserialize = this.persistence.deserializer ?? ((value) => JSON.parse(value));
|
|
3843
|
+
const envelope = deserialize(raw);
|
|
3844
|
+
if (envelope.version !== 1 || !Array.isArray(envelope.data)) {
|
|
3845
|
+
throw new Error("Invalid offline queue persistence payload");
|
|
3846
|
+
}
|
|
3847
|
+
this.queue.clear();
|
|
3848
|
+
this.syncingIds.clear();
|
|
3849
|
+
let imported = 0;
|
|
3850
|
+
for (const operation of envelope.data) {
|
|
3851
|
+
if (this.isValidOfflineOperation(operation)) {
|
|
3852
|
+
this.queue.set(operation.id, {
|
|
3853
|
+
...operation,
|
|
3854
|
+
status: operation.status === "syncing" ? "pending" : operation.status
|
|
3855
|
+
});
|
|
3856
|
+
imported++;
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
logger2.debug("[OfflineOperationQueue] Loaded from persistence", {
|
|
3860
|
+
key: this.persistence.key,
|
|
3861
|
+
imported
|
|
2939
3862
|
});
|
|
3863
|
+
return imported;
|
|
3864
|
+
}
|
|
3865
|
+
/**
|
|
3866
|
+
* Remove persisted queue snapshot.
|
|
3867
|
+
*/
|
|
3868
|
+
async clearPersistence() {
|
|
3869
|
+
if (!this.persistence) {
|
|
3870
|
+
return;
|
|
3871
|
+
}
|
|
3872
|
+
await this.persistence.adapter.removeItem(this.persistence.key);
|
|
3873
|
+
}
|
|
3874
|
+
schedulePersist() {
|
|
3875
|
+
if (!this.persistence || this.persistence.autoPersist === false) {
|
|
3876
|
+
return;
|
|
3877
|
+
}
|
|
3878
|
+
if (this.persistTimer) {
|
|
3879
|
+
clearTimeout(this.persistTimer);
|
|
3880
|
+
}
|
|
3881
|
+
this.persistTimer = setTimeout(() => {
|
|
3882
|
+
void this.persistSafely();
|
|
3883
|
+
}, this.persistence.persistDebounceMs ?? 25);
|
|
3884
|
+
}
|
|
3885
|
+
async persistSafely() {
|
|
3886
|
+
if (!this.persistence) {
|
|
3887
|
+
return;
|
|
3888
|
+
}
|
|
3889
|
+
if (this.persistInFlight) {
|
|
3890
|
+
this.persistPending = true;
|
|
3891
|
+
return;
|
|
3892
|
+
}
|
|
3893
|
+
this.persistInFlight = true;
|
|
3894
|
+
try {
|
|
3895
|
+
await this.saveToPersistence();
|
|
3896
|
+
} catch (error) {
|
|
3897
|
+
logger2.error("[OfflineOperationQueue] Persistence write failed", {
|
|
3898
|
+
key: this.persistence.key,
|
|
3899
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3900
|
+
});
|
|
3901
|
+
} finally {
|
|
3902
|
+
this.persistInFlight = false;
|
|
3903
|
+
const shouldRunAgain = this.persistPending;
|
|
3904
|
+
this.persistPending = false;
|
|
3905
|
+
if (shouldRunAgain) {
|
|
3906
|
+
void this.persistSafely();
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
isValidOfflineOperation(value) {
|
|
3911
|
+
if (typeof value !== "object" || value === null) {
|
|
3912
|
+
return false;
|
|
3913
|
+
}
|
|
3914
|
+
const candidate = value;
|
|
3915
|
+
const validType = candidate.type === "create" || candidate.type === "update" || candidate.type === "delete" || candidate.type === "sync" || candidate.type === "batch";
|
|
3916
|
+
const validPriority = candidate.priority === "high" || candidate.priority === "normal" || candidate.priority === "low";
|
|
3917
|
+
const validStatus = candidate.status === "pending" || candidate.status === "syncing" || candidate.status === "failed" || candidate.status === "synced";
|
|
3918
|
+
return typeof candidate.id === "string" && validType && typeof candidate.data === "object" && candidate.data !== null && !Array.isArray(candidate.data) && typeof candidate.sessionId === "string" && validPriority && typeof candidate.createdAt === "number" && typeof candidate.retryCount === "number" && typeof candidate.maxRetries === "number" && validStatus;
|
|
2940
3919
|
}
|
|
2941
3920
|
};
|
|
2942
3921
|
var offlineQueueInstance = null;
|
|
@@ -2987,9 +3966,15 @@ var CompressionEngine = class {
|
|
|
2987
3966
|
let algorithm = this.preferredAlgorithm;
|
|
2988
3967
|
if (this.supportsNativeCompression()) {
|
|
2989
3968
|
try {
|
|
2990
|
-
compressed = await this.compressNative(
|
|
3969
|
+
compressed = await this.compressNative(
|
|
3970
|
+
inputData,
|
|
3971
|
+
this.preferredAlgorithm
|
|
3972
|
+
);
|
|
2991
3973
|
} catch (error) {
|
|
2992
|
-
logger3.warn(
|
|
3974
|
+
logger3.warn(
|
|
3975
|
+
"[CompressionEngine] Native compression failed, using fallback",
|
|
3976
|
+
error
|
|
3977
|
+
);
|
|
2993
3978
|
compressed = inputData;
|
|
2994
3979
|
algorithm = "none";
|
|
2995
3980
|
}
|
|
@@ -3061,7 +4046,13 @@ var CompressionEngine = class {
|
|
|
3061
4046
|
const stream = new CompressionStream(algorithm);
|
|
3062
4047
|
const writer = stream.writable.getWriter();
|
|
3063
4048
|
const reader = stream.readable.getReader();
|
|
3064
|
-
writer.write(
|
|
4049
|
+
writer.write(
|
|
4050
|
+
new Uint8Array(
|
|
4051
|
+
data.buffer,
|
|
4052
|
+
data.byteOffset,
|
|
4053
|
+
data.byteLength
|
|
4054
|
+
)
|
|
4055
|
+
);
|
|
3065
4056
|
writer.close();
|
|
3066
4057
|
const chunks = [];
|
|
3067
4058
|
let done = false;
|
|
@@ -3088,7 +4079,13 @@ var CompressionEngine = class {
|
|
|
3088
4079
|
const stream = new DecompressionStream(algorithm);
|
|
3089
4080
|
const writer = stream.writable.getWriter();
|
|
3090
4081
|
const reader = stream.readable.getReader();
|
|
3091
|
-
writer.write(
|
|
4082
|
+
writer.write(
|
|
4083
|
+
new Uint8Array(
|
|
4084
|
+
data.buffer,
|
|
4085
|
+
data.byteOffset,
|
|
4086
|
+
data.byteLength
|
|
4087
|
+
)
|
|
4088
|
+
);
|
|
3092
4089
|
writer.close();
|
|
3093
4090
|
const chunks = [];
|
|
3094
4091
|
let done = false;
|
|
@@ -3137,9 +4134,14 @@ var CompressionEngine = class {
|
|
|
3137
4134
|
const sorted = [...chunks].sort((a, b) => a.index - b.index);
|
|
3138
4135
|
const total = sorted[0]?.total ?? 0;
|
|
3139
4136
|
if (sorted.length !== total) {
|
|
3140
|
-
throw new Error(
|
|
4137
|
+
throw new Error(
|
|
4138
|
+
`Missing chunks: got ${sorted.length}, expected ${total}`
|
|
4139
|
+
);
|
|
3141
4140
|
}
|
|
3142
|
-
const totalLength = sorted.reduce(
|
|
4141
|
+
const totalLength = sorted.reduce(
|
|
4142
|
+
(sum, chunk) => sum + chunk.data.length,
|
|
4143
|
+
0
|
|
4144
|
+
);
|
|
3143
4145
|
const combined = new Uint8Array(totalLength);
|
|
3144
4146
|
let offset = 0;
|
|
3145
4147
|
for (const chunk of sorted) {
|
|
@@ -4070,7 +5072,10 @@ var AdaptiveCompressionOptimizer = class {
|
|
|
4070
5072
|
* Update device resource usage
|
|
4071
5073
|
*/
|
|
4072
5074
|
updateDeviceResources(cpuUtilization, memoryAvailableMB) {
|
|
4073
|
-
this.deviceProfile.cpuUtilization = Math.max(
|
|
5075
|
+
this.deviceProfile.cpuUtilization = Math.max(
|
|
5076
|
+
0,
|
|
5077
|
+
Math.min(1, cpuUtilization)
|
|
5078
|
+
);
|
|
4074
5079
|
this.deviceProfile.memoryAvailableMB = memoryAvailableMB;
|
|
4075
5080
|
this.deviceProfile.isConstrained = memoryAvailableMB < 512;
|
|
4076
5081
|
this.deviceProfile.isPremium = memoryAvailableMB > 2048;
|
|
@@ -4130,7 +5135,10 @@ var AdaptiveCompressionOptimizer = class {
|
|
|
4130
5135
|
networkFactor,
|
|
4131
5136
|
deviceFactor
|
|
4132
5137
|
};
|
|
4133
|
-
logger7.debug(
|
|
5138
|
+
logger7.debug(
|
|
5139
|
+
"[AdaptiveCompressionOptimizer] Recommendation",
|
|
5140
|
+
recommendation
|
|
5141
|
+
);
|
|
4134
5142
|
return recommendation;
|
|
4135
5143
|
}
|
|
4136
5144
|
/**
|
|
@@ -4282,7 +5290,11 @@ var AgentPresenceManager = class extends EventEmitter {
|
|
|
4282
5290
|
};
|
|
4283
5291
|
this.presences.set(agentId, presence);
|
|
4284
5292
|
this.emit("agent_joined", { agentId, presence });
|
|
4285
|
-
logger8.debug("[AgentPresenceManager] Agent joined", {
|
|
5293
|
+
logger8.debug("[AgentPresenceManager] Agent joined", {
|
|
5294
|
+
agentId,
|
|
5295
|
+
name,
|
|
5296
|
+
role
|
|
5297
|
+
});
|
|
4286
5298
|
}
|
|
4287
5299
|
/**
|
|
4288
5300
|
* Agent left
|
|
@@ -4327,6 +5339,157 @@ var AgentPresenceManager = class extends EventEmitter {
|
|
|
4327
5339
|
});
|
|
4328
5340
|
}
|
|
4329
5341
|
}
|
|
5342
|
+
/**
|
|
5343
|
+
* Update focused node path
|
|
5344
|
+
*/
|
|
5345
|
+
updateFocusNode(agentId, nodePath) {
|
|
5346
|
+
const presence = this.presences.get(agentId);
|
|
5347
|
+
if (presence) {
|
|
5348
|
+
presence.focusNode = nodePath;
|
|
5349
|
+
presence.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5350
|
+
this.presences.set(agentId, presence);
|
|
5351
|
+
this.emit("focus_updated", {
|
|
5352
|
+
agentId,
|
|
5353
|
+
focusNode: nodePath
|
|
5354
|
+
});
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5357
|
+
/**
|
|
5358
|
+
* Update text selection range
|
|
5359
|
+
*/
|
|
5360
|
+
updateSelection(agentId, selectionRange) {
|
|
5361
|
+
const presence = this.presences.get(agentId);
|
|
5362
|
+
if (presence) {
|
|
5363
|
+
presence.selectionRange = selectionRange;
|
|
5364
|
+
presence.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5365
|
+
this.presences.set(agentId, presence);
|
|
5366
|
+
this.emit("selection_updated", {
|
|
5367
|
+
agentId,
|
|
5368
|
+
selectionRange
|
|
5369
|
+
});
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5372
|
+
/**
|
|
5373
|
+
* Update typing state
|
|
5374
|
+
*/
|
|
5375
|
+
updateTyping(agentId, isTyping, field, isComposing = false) {
|
|
5376
|
+
const presence = this.presences.get(agentId);
|
|
5377
|
+
if (presence) {
|
|
5378
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5379
|
+
const previous = presence.typingState;
|
|
5380
|
+
const typingState = {
|
|
5381
|
+
isTyping,
|
|
5382
|
+
field,
|
|
5383
|
+
isComposing,
|
|
5384
|
+
startedAt: isTyping && !previous?.isTyping ? now : isTyping ? previous?.startedAt : void 0,
|
|
5385
|
+
stoppedAt: isTyping ? void 0 : now
|
|
5386
|
+
};
|
|
5387
|
+
presence.typingState = typingState;
|
|
5388
|
+
presence.lastSeen = now;
|
|
5389
|
+
this.presences.set(agentId, presence);
|
|
5390
|
+
this.emit("typing_updated", {
|
|
5391
|
+
agentId,
|
|
5392
|
+
typingState
|
|
5393
|
+
});
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
/**
|
|
5397
|
+
* Update scroll state
|
|
5398
|
+
*/
|
|
5399
|
+
updateScroll(agentId, scrollState) {
|
|
5400
|
+
const presence = this.presences.get(agentId);
|
|
5401
|
+
if (presence) {
|
|
5402
|
+
presence.scrollState = {
|
|
5403
|
+
...scrollState,
|
|
5404
|
+
depth: Math.max(0, Math.min(1, scrollState.depth))
|
|
5405
|
+
};
|
|
5406
|
+
presence.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5407
|
+
this.presences.set(agentId, presence);
|
|
5408
|
+
this.emit("scroll_updated", {
|
|
5409
|
+
agentId,
|
|
5410
|
+
scrollState: presence.scrollState
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
/**
|
|
5415
|
+
* Update viewport size
|
|
5416
|
+
*/
|
|
5417
|
+
updateViewport(agentId, width, height) {
|
|
5418
|
+
const presence = this.presences.get(agentId);
|
|
5419
|
+
if (presence) {
|
|
5420
|
+
presence.viewport = { width, height };
|
|
5421
|
+
presence.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5422
|
+
this.presences.set(agentId, presence);
|
|
5423
|
+
this.emit("viewport_updated", {
|
|
5424
|
+
agentId,
|
|
5425
|
+
viewport: presence.viewport
|
|
5426
|
+
});
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
5429
|
+
/**
|
|
5430
|
+
* Update input state
|
|
5431
|
+
*/
|
|
5432
|
+
updateInputState(agentId, inputState) {
|
|
5433
|
+
const presence = this.presences.get(agentId);
|
|
5434
|
+
if (presence) {
|
|
5435
|
+
presence.inputState = inputState;
|
|
5436
|
+
presence.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5437
|
+
this.presences.set(agentId, presence);
|
|
5438
|
+
this.emit("input_state_updated", {
|
|
5439
|
+
agentId,
|
|
5440
|
+
inputState
|
|
5441
|
+
});
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5444
|
+
/**
|
|
5445
|
+
* Clear input state
|
|
5446
|
+
*/
|
|
5447
|
+
clearInputState(agentId) {
|
|
5448
|
+
const presence = this.presences.get(agentId);
|
|
5449
|
+
if (presence) {
|
|
5450
|
+
presence.inputState = void 0;
|
|
5451
|
+
presence.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5452
|
+
this.presences.set(agentId, presence);
|
|
5453
|
+
this.emit("input_state_updated", {
|
|
5454
|
+
agentId,
|
|
5455
|
+
inputState: void 0
|
|
5456
|
+
});
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
/**
|
|
5460
|
+
* Update emotional state
|
|
5461
|
+
*/
|
|
5462
|
+
updateEmotionState(agentId, emotionState) {
|
|
5463
|
+
const presence = this.presences.get(agentId);
|
|
5464
|
+
if (presence) {
|
|
5465
|
+
const enrichedState = {
|
|
5466
|
+
...emotionState,
|
|
5467
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5468
|
+
};
|
|
5469
|
+
presence.emotionState = enrichedState;
|
|
5470
|
+
presence.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5471
|
+
this.presences.set(agentId, presence);
|
|
5472
|
+
this.emit("emotion_updated", {
|
|
5473
|
+
agentId,
|
|
5474
|
+
emotionState: enrichedState
|
|
5475
|
+
});
|
|
5476
|
+
}
|
|
5477
|
+
}
|
|
5478
|
+
/**
|
|
5479
|
+
* Clear emotional state
|
|
5480
|
+
*/
|
|
5481
|
+
clearEmotionState(agentId) {
|
|
5482
|
+
const presence = this.presences.get(agentId);
|
|
5483
|
+
if (presence) {
|
|
5484
|
+
presence.emotionState = void 0;
|
|
5485
|
+
presence.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5486
|
+
this.presences.set(agentId, presence);
|
|
5487
|
+
this.emit("emotion_updated", {
|
|
5488
|
+
agentId,
|
|
5489
|
+
emotionState: void 0
|
|
5490
|
+
});
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
4330
5493
|
/**
|
|
4331
5494
|
* Update status
|
|
4332
5495
|
*/
|
|
@@ -4516,7 +5679,9 @@ var AgentPresenceManager = class extends EventEmitter {
|
|
|
4516
5679
|
this.stopHeartbeatMonitoring();
|
|
4517
5680
|
this.presences.clear();
|
|
4518
5681
|
this.removeAllListeners();
|
|
4519
|
-
logger8.debug("[AgentPresenceManager] Destroyed", {
|
|
5682
|
+
logger8.debug("[AgentPresenceManager] Destroyed", {
|
|
5683
|
+
sessionId: this.sessionId
|
|
5684
|
+
});
|
|
4520
5685
|
}
|
|
4521
5686
|
};
|
|
4522
5687
|
var instances = /* @__PURE__ */ new Map();
|
|
@@ -4627,6 +5792,6 @@ var NullCryptoProvider = class {
|
|
|
4627
5792
|
}
|
|
4628
5793
|
};
|
|
4629
5794
|
|
|
4630
|
-
export { AEON_CAPABILITIES, AdaptiveCompressionOptimizer, AgentPresenceManager, BatchTimingOptimizer, CompressionEngine, DEFAULT_CRYPTO_CONFIG, DataTransformer, DeltaSyncOptimizer, MigrationEngine, MigrationTracker, NullCryptoProvider, OfflineOperationQueue, PrefetchingEngine, ReplicationManager, SchemaVersionManager, StateReconciler, SyncCoordinator, SyncProtocol, clearAgentPresenceManager, createNamespacedLogger, disableLogging, getAdaptiveCompressionOptimizer, getAgentPresenceManager, getBatchTimingOptimizer, getCompressionEngine, getDeltaSyncOptimizer, getLogger, getOfflineOperationQueue, getPrefetchingEngine, logger, resetAdaptiveCompressionOptimizer, resetBatchTimingOptimizer, resetCompressionEngine, resetDeltaSyncOptimizer, resetLogger, resetOfflineOperationQueue, resetPrefetchingEngine, setLogger };
|
|
5795
|
+
export { AEON_CAPABILITIES, AdaptiveCompressionOptimizer, AgentPresenceManager, BatchTimingOptimizer, CompressionEngine, DEFAULT_CRYPTO_CONFIG, DashStorageAdapter, DataTransformer, DeltaSyncOptimizer, InMemoryStorageAdapter, MigrationEngine, MigrationTracker, NullCryptoProvider, OfflineOperationQueue, PrefetchingEngine, ReplicationManager, SchemaVersionManager, StateReconciler, SyncCoordinator, SyncProtocol, clearAgentPresenceManager, createNamespacedLogger, disableLogging, getAdaptiveCompressionOptimizer, getAgentPresenceManager, getBatchTimingOptimizer, getCompressionEngine, getDeltaSyncOptimizer, getLogger, getOfflineOperationQueue, getPrefetchingEngine, logger, resetAdaptiveCompressionOptimizer, resetBatchTimingOptimizer, resetCompressionEngine, resetDeltaSyncOptimizer, resetLogger, resetOfflineOperationQueue, resetPrefetchingEngine, setLogger };
|
|
4631
5796
|
//# sourceMappingURL=index.js.map
|
|
4632
5797
|
//# sourceMappingURL=index.js.map
|