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