@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.
Files changed (62) hide show
  1. package/README.md +10 -0
  2. package/dist/compression/index.cjs +580 -0
  3. package/dist/compression/index.cjs.map +1 -0
  4. package/dist/compression/index.d.cts +189 -0
  5. package/dist/compression/index.d.ts +189 -0
  6. package/dist/compression/index.js +573 -0
  7. package/dist/compression/index.js.map +1 -0
  8. package/dist/core/index.d.cts +70 -5
  9. package/dist/core/index.d.ts +70 -5
  10. package/dist/crypto/index.cjs +100 -0
  11. package/dist/crypto/index.cjs.map +1 -0
  12. package/dist/crypto/index.d.cts +407 -0
  13. package/dist/crypto/index.d.ts +407 -0
  14. package/dist/crypto/index.js +96 -0
  15. package/dist/crypto/index.js.map +1 -0
  16. package/dist/distributed/index.cjs +420 -23
  17. package/dist/distributed/index.cjs.map +1 -1
  18. package/dist/distributed/index.d.cts +901 -2
  19. package/dist/distributed/index.d.ts +901 -2
  20. package/dist/distributed/index.js +420 -23
  21. package/dist/distributed/index.js.map +1 -1
  22. package/dist/index.cjs +1222 -55
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +11 -811
  25. package/dist/index.d.ts +11 -811
  26. package/dist/index.js +1221 -56
  27. package/dist/index.js.map +1 -1
  28. package/dist/offline/index.cjs +419 -0
  29. package/dist/offline/index.cjs.map +1 -0
  30. package/dist/offline/index.d.cts +148 -0
  31. package/dist/offline/index.d.ts +148 -0
  32. package/dist/offline/index.js +415 -0
  33. package/dist/offline/index.js.map +1 -0
  34. package/dist/optimization/index.cjs +797 -0
  35. package/dist/optimization/index.cjs.map +1 -0
  36. package/dist/optimization/index.d.cts +347 -0
  37. package/dist/optimization/index.d.ts +347 -0
  38. package/dist/optimization/index.js +787 -0
  39. package/dist/optimization/index.js.map +1 -0
  40. package/dist/persistence/index.cjs +145 -0
  41. package/dist/persistence/index.cjs.map +1 -0
  42. package/dist/persistence/index.d.cts +63 -0
  43. package/dist/persistence/index.d.ts +63 -0
  44. package/dist/persistence/index.js +142 -0
  45. package/dist/persistence/index.js.map +1 -0
  46. package/dist/presence/index.cjs +489 -0
  47. package/dist/presence/index.cjs.map +1 -0
  48. package/dist/presence/index.d.cts +283 -0
  49. package/dist/presence/index.d.ts +283 -0
  50. package/dist/presence/index.js +485 -0
  51. package/dist/presence/index.js.map +1 -0
  52. package/dist/types-CMxO7QF0.d.cts +33 -0
  53. package/dist/types-CMxO7QF0.d.ts +33 -0
  54. package/dist/versioning/index.cjs +296 -14
  55. package/dist/versioning/index.cjs.map +1 -1
  56. package/dist/versioning/index.d.cts +66 -1
  57. package/dist/versioning/index.d.ts +66 -1
  58. package/dist/versioning/index.js +296 -14
  59. package/dist/versioning/index.js.map +1 -1
  60. package/package.json +51 -1
  61. package/dist/index-C_4CMV5c.d.cts +0 -1207
  62. package/dist/index-C_4CMV5c.d.ts +0 -1207
@@ -75,7 +75,9 @@ var SchemaVersionManager = class {
75
75
  */
76
76
  setCurrentVersion(version) {
77
77
  if (!this.versions.has(this.versionToString(version))) {
78
- throw new Error(`Version ${this.versionToString(version)} not registered`);
78
+ throw new Error(
79
+ `Version ${this.versionToString(version)} not registered`
80
+ );
79
81
  }
80
82
  this.currentVersion = version;
81
83
  logger.debug("[SchemaVersionManager] Current version set", {
@@ -361,7 +363,9 @@ var MigrationEngine = class {
361
363
  id: migrationId,
362
364
  error: result.errors[0]
363
365
  });
364
- throw new Error(`Rollback for ${migrationId} failed: ${result.errors[0]}`);
366
+ throw new Error(
367
+ `Rollback for ${migrationId} failed: ${result.errors[0]}`
368
+ );
365
369
  }
366
370
  }
367
371
  /**
@@ -414,8 +418,14 @@ var MigrationEngine = class {
414
418
  getStatistics() {
415
419
  const successful = this.executedMigrations.filter((m) => m.success).length;
416
420
  const failed = this.executedMigrations.filter((m) => !m.success).length;
417
- const totalDuration = this.executedMigrations.reduce((sum, m) => sum + m.duration, 0);
418
- const totalAffected = this.executedMigrations.reduce((sum, m) => sum + m.itemsAffected, 0);
421
+ const totalDuration = this.executedMigrations.reduce(
422
+ (sum, m) => sum + m.duration,
423
+ 0
424
+ );
425
+ const totalAffected = this.executedMigrations.reduce(
426
+ (sum, m) => sum + m.itemsAffected,
427
+ 0
428
+ );
419
429
  return {
420
430
  totalExecuted: this.executedMigrations.length,
421
431
  successful,
@@ -469,7 +479,9 @@ var DataTransformer = class {
469
479
  return rule.transformer(value);
470
480
  } catch (error) {
471
481
  if (rule.required) {
472
- throw new Error(`Failed to transform required field ${field}: ${error instanceof Error ? error.message : String(error)}`);
482
+ throw new Error(
483
+ `Failed to transform required field ${field}: ${error instanceof Error ? error.message : String(error)}`
484
+ );
473
485
  }
474
486
  return rule.defaultValue !== void 0 ? rule.defaultValue : value;
475
487
  }
@@ -542,7 +554,9 @@ var DataTransformer = class {
542
554
  validateTransformation(original, transformed) {
543
555
  const issues = [];
544
556
  if (original.length !== transformed.length) {
545
- issues.push(`Item count mismatch: ${original.length} -> ${transformed.length}`);
557
+ issues.push(
558
+ `Item count mismatch: ${original.length} -> ${transformed.length}`
559
+ );
546
560
  }
547
561
  for (let i = 0; i < Math.min(original.length, transformed.length); i++) {
548
562
  const orig = original[i];
@@ -633,14 +647,40 @@ var DataTransformer = class {
633
647
  };
634
648
 
635
649
  // src/versioning/MigrationTracker.ts
636
- var MigrationTracker = class {
650
+ var MigrationTracker = class _MigrationTracker {
651
+ static DEFAULT_PERSIST_KEY = "aeon:migration-tracker:v1";
652
+ static INTEGRITY_ROOT = "aeon:migration-integrity-root:v1";
637
653
  migrations = [];
638
654
  snapshots = /* @__PURE__ */ new Map();
655
+ persistence = null;
656
+ persistTimer = null;
657
+ persistInFlight = false;
658
+ persistPending = false;
659
+ constructor(options) {
660
+ if (options?.persistence) {
661
+ this.persistence = {
662
+ ...options.persistence,
663
+ key: options.persistence.key ?? _MigrationTracker.DEFAULT_PERSIST_KEY,
664
+ autoPersist: options.persistence.autoPersist ?? true,
665
+ autoLoad: options.persistence.autoLoad ?? false,
666
+ persistDebounceMs: options.persistence.persistDebounceMs ?? 25
667
+ };
668
+ }
669
+ if (this.persistence?.autoLoad) {
670
+ void this.loadFromPersistence().catch((error) => {
671
+ logger.error("[MigrationTracker] Failed to load persistence", {
672
+ key: this.persistence?.key,
673
+ error: error instanceof Error ? error.message : String(error)
674
+ });
675
+ });
676
+ }
677
+ }
639
678
  /**
640
679
  * Track a new migration
641
680
  */
642
681
  recordMigration(record) {
643
682
  this.migrations.push({ ...record });
683
+ this.schedulePersist();
644
684
  logger.debug("[MigrationTracker] Migration recorded", {
645
685
  id: record.id,
646
686
  migrationId: record.migrationId,
@@ -697,7 +737,9 @@ var MigrationTracker = class {
697
737
  * Check if can rollback
698
738
  */
699
739
  canRollback(fromVersion, toVersion) {
700
- const fromIndex = this.migrations.findIndex((m) => m.version === fromVersion);
740
+ const fromIndex = this.migrations.findIndex(
741
+ (m) => m.version === fromVersion
742
+ );
701
743
  const toIndex = this.migrations.findIndex((m) => m.version === toVersion);
702
744
  if (fromIndex === -1 || toIndex === -1) {
703
745
  return false;
@@ -721,7 +763,9 @@ var MigrationTracker = class {
721
763
  const affectedVersions = [];
722
764
  let estimatedDuration = 0;
723
765
  if (canRollback) {
724
- const fromIndex = this.migrations.findIndex((m) => m.version === fromVersion);
766
+ const fromIndex = this.migrations.findIndex(
767
+ (m) => m.version === fromVersion
768
+ );
725
769
  const toIndex = this.migrations.findIndex((m) => m.version === toVersion);
726
770
  for (let i = fromIndex; i > toIndex; i--) {
727
771
  const migration = this.migrations[i];
@@ -777,12 +821,24 @@ var MigrationTracker = class {
777
821
  * Get migration statistics
778
822
  */
779
823
  getStatistics() {
780
- const applied = this.migrations.filter((m) => m.status === "applied").length;
824
+ const applied = this.migrations.filter(
825
+ (m) => m.status === "applied"
826
+ ).length;
781
827
  const failed = this.migrations.filter((m) => m.status === "failed").length;
782
- const pending = this.migrations.filter((m) => m.status === "pending").length;
783
- const rolledBack = this.migrations.filter((m) => m.status === "rolled-back").length;
784
- const totalDuration = this.migrations.reduce((sum, m) => sum + m.duration, 0);
785
- const totalAffected = this.migrations.reduce((sum, m) => sum + m.itemsAffected, 0);
828
+ const pending = this.migrations.filter(
829
+ (m) => m.status === "pending"
830
+ ).length;
831
+ const rolledBack = this.migrations.filter(
832
+ (m) => m.status === "rolled-back"
833
+ ).length;
834
+ const totalDuration = this.migrations.reduce(
835
+ (sum, m) => sum + m.duration,
836
+ 0
837
+ );
838
+ const totalAffected = this.migrations.reduce(
839
+ (sum, m) => sum + m.itemsAffected,
840
+ 0
841
+ );
786
842
  return {
787
843
  total: this.migrations.length,
788
844
  applied,
@@ -833,7 +889,147 @@ var MigrationTracker = class {
833
889
  status,
834
890
  hasError: !!error
835
891
  });
892
+ this.schedulePersist();
893
+ }
894
+ }
895
+ /**
896
+ * Persist tracker state with integrity chain verification metadata.
897
+ */
898
+ async saveToPersistence() {
899
+ if (!this.persistence) {
900
+ return;
901
+ }
902
+ const normalizedMigrations = this.migrations.map((migration) => ({
903
+ ...migration,
904
+ previousHash: void 0,
905
+ integrityHash: void 0
906
+ }));
907
+ const integrityEntries = [];
908
+ let previousHash = _MigrationTracker.INTEGRITY_ROOT;
909
+ for (const migration of normalizedMigrations) {
910
+ const hash = await this.computeDigestHex(
911
+ `${previousHash}|${this.stableStringify(migration)}`
912
+ );
913
+ integrityEntries.push({
914
+ recordId: migration.id,
915
+ previousHash,
916
+ hash
917
+ });
918
+ previousHash = hash;
919
+ }
920
+ const persistedMigrations = normalizedMigrations.map((migration, index) => ({
921
+ ...migration,
922
+ previousHash: integrityEntries[index]?.previousHash,
923
+ integrityHash: integrityEntries[index]?.hash
924
+ }));
925
+ const data = {
926
+ migrations: persistedMigrations,
927
+ snapshots: Array.from(this.snapshots.entries()).map(
928
+ ([recordId, snapshot]) => ({
929
+ recordId,
930
+ beforeHash: snapshot.beforeHash,
931
+ afterHash: snapshot.afterHash,
932
+ itemCount: snapshot.itemCount
933
+ })
934
+ ),
935
+ integrity: {
936
+ algorithm: "sha256-chain-v1",
937
+ entries: integrityEntries,
938
+ rootHash: previousHash
939
+ }
940
+ };
941
+ const envelope = {
942
+ version: 1,
943
+ updatedAt: Date.now(),
944
+ data
945
+ };
946
+ const serialize = this.persistence.serializer ?? ((value) => JSON.stringify(value));
947
+ await this.persistence.adapter.setItem(this.persistence.key, serialize(envelope));
948
+ }
949
+ /**
950
+ * Load tracker state and verify integrity chain.
951
+ */
952
+ async loadFromPersistence() {
953
+ if (!this.persistence) {
954
+ return { migrations: 0, snapshots: 0 };
955
+ }
956
+ const raw = await this.persistence.adapter.getItem(this.persistence.key);
957
+ if (!raw) {
958
+ return { migrations: 0, snapshots: 0 };
959
+ }
960
+ const deserialize = this.persistence.deserializer ?? ((value) => JSON.parse(value));
961
+ const envelope = deserialize(raw);
962
+ if (envelope.version !== 1 || !envelope.data) {
963
+ throw new Error("Invalid migration tracker persistence payload");
964
+ }
965
+ 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") {
966
+ throw new Error("Invalid migration tracker persistence structure");
967
+ }
968
+ if (envelope.data.integrity.algorithm !== "sha256-chain-v1") {
969
+ throw new Error("Unsupported migration integrity algorithm");
970
+ }
971
+ if (envelope.data.integrity.entries.length !== envelope.data.migrations.length) {
972
+ throw new Error("Migration integrity entry count mismatch");
973
+ }
974
+ const validatedMigrations = [];
975
+ let previousHash = _MigrationTracker.INTEGRITY_ROOT;
976
+ for (let i = 0; i < envelope.data.migrations.length; i++) {
977
+ const migration = envelope.data.migrations[i];
978
+ const integrity = envelope.data.integrity.entries[i];
979
+ if (!this.isValidMigrationRecord(migration)) {
980
+ throw new Error("Invalid persisted migration record");
981
+ }
982
+ if (!integrity || integrity.recordId !== migration.id || integrity.previousHash !== previousHash) {
983
+ throw new Error("Migration integrity chain mismatch");
984
+ }
985
+ const expectedHash = await this.computeDigestHex(
986
+ `${previousHash}|${this.stableStringify({
987
+ ...migration,
988
+ previousHash: void 0,
989
+ integrityHash: void 0
990
+ })}`
991
+ );
992
+ if (expectedHash !== integrity.hash) {
993
+ throw new Error("Migration integrity verification failed");
994
+ }
995
+ validatedMigrations.push({
996
+ ...migration,
997
+ previousHash: integrity.previousHash,
998
+ integrityHash: integrity.hash
999
+ });
1000
+ previousHash = expectedHash;
1001
+ }
1002
+ if (previousHash !== envelope.data.integrity.rootHash) {
1003
+ throw new Error("Migration integrity root hash mismatch");
1004
+ }
1005
+ const validatedSnapshots = /* @__PURE__ */ new Map();
1006
+ for (const snapshot of envelope.data.snapshots) {
1007
+ if (typeof snapshot.recordId !== "string" || typeof snapshot.beforeHash !== "string" || typeof snapshot.afterHash !== "string" || typeof snapshot.itemCount !== "number") {
1008
+ throw new Error("Invalid persisted migration snapshot");
1009
+ }
1010
+ validatedSnapshots.set(snapshot.recordId, {
1011
+ beforeHash: snapshot.beforeHash,
1012
+ afterHash: snapshot.afterHash,
1013
+ itemCount: snapshot.itemCount
1014
+ });
836
1015
  }
1016
+ this.migrations = validatedMigrations;
1017
+ this.snapshots = validatedSnapshots;
1018
+ logger.debug("[MigrationTracker] Loaded from persistence", {
1019
+ key: this.persistence.key,
1020
+ migrations: this.migrations.length,
1021
+ snapshots: this.snapshots.size
1022
+ });
1023
+ return { migrations: this.migrations.length, snapshots: this.snapshots.size };
1024
+ }
1025
+ /**
1026
+ * Remove persisted migration tracker state.
1027
+ */
1028
+ async clearPersistence() {
1029
+ if (!this.persistence) {
1030
+ return;
1031
+ }
1032
+ await this.persistence.adapter.removeItem(this.persistence.key);
837
1033
  }
838
1034
  /**
839
1035
  * Clear history (for testing)
@@ -841,6 +1037,7 @@ var MigrationTracker = class {
841
1037
  clear() {
842
1038
  this.migrations = [];
843
1039
  this.snapshots.clear();
1040
+ this.schedulePersist();
844
1041
  }
845
1042
  /**
846
1043
  * Get total migrations tracked
@@ -859,6 +1056,91 @@ var MigrationTracker = class {
859
1056
  return time >= start && time <= end;
860
1057
  });
861
1058
  }
1059
+ schedulePersist() {
1060
+ if (!this.persistence || this.persistence.autoPersist === false) {
1061
+ return;
1062
+ }
1063
+ if (this.persistTimer) {
1064
+ clearTimeout(this.persistTimer);
1065
+ }
1066
+ this.persistTimer = setTimeout(() => {
1067
+ void this.persistSafely();
1068
+ }, this.persistence.persistDebounceMs ?? 25);
1069
+ }
1070
+ async persistSafely() {
1071
+ if (!this.persistence) {
1072
+ return;
1073
+ }
1074
+ if (this.persistInFlight) {
1075
+ this.persistPending = true;
1076
+ return;
1077
+ }
1078
+ this.persistInFlight = true;
1079
+ try {
1080
+ await this.saveToPersistence();
1081
+ } catch (error) {
1082
+ logger.error("[MigrationTracker] Persistence write failed", {
1083
+ key: this.persistence.key,
1084
+ error: error instanceof Error ? error.message : String(error)
1085
+ });
1086
+ } finally {
1087
+ this.persistInFlight = false;
1088
+ const shouldRunAgain = this.persistPending;
1089
+ this.persistPending = false;
1090
+ if (shouldRunAgain) {
1091
+ void this.persistSafely();
1092
+ }
1093
+ }
1094
+ }
1095
+ isValidMigrationRecord(value) {
1096
+ if (typeof value !== "object" || value === null) {
1097
+ return false;
1098
+ }
1099
+ const record = value;
1100
+ const validDirection = record.direction === "up" || record.direction === "down";
1101
+ const validStatus = record.status === "pending" || record.status === "applied" || record.status === "failed" || record.status === "rolled-back";
1102
+ 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";
1103
+ }
1104
+ stableStringify(value) {
1105
+ if (value === null || typeof value !== "object") {
1106
+ return JSON.stringify(value);
1107
+ }
1108
+ if (Array.isArray(value)) {
1109
+ return `[${value.map((item) => this.stableStringify(item)).join(",")}]`;
1110
+ }
1111
+ const entries = Object.entries(value).sort(
1112
+ ([a], [b]) => a.localeCompare(b)
1113
+ );
1114
+ return `{${entries.map(
1115
+ ([key, entryValue]) => `${JSON.stringify(key)}:${this.stableStringify(entryValue)}`
1116
+ ).join(",")}}`;
1117
+ }
1118
+ async computeDigestHex(value) {
1119
+ if (globalThis.crypto?.subtle) {
1120
+ const bytes = new TextEncoder().encode(value);
1121
+ const normalized = bytes.buffer.slice(
1122
+ bytes.byteOffset,
1123
+ bytes.byteOffset + bytes.byteLength
1124
+ );
1125
+ const digest = await globalThis.crypto.subtle.digest(
1126
+ "SHA-256",
1127
+ normalized
1128
+ );
1129
+ return this.toHex(new Uint8Array(digest));
1130
+ }
1131
+ return this.fallbackDigestHex(value);
1132
+ }
1133
+ toHex(bytes) {
1134
+ return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1135
+ }
1136
+ fallbackDigestHex(value) {
1137
+ let hash = 2166136261;
1138
+ for (let i = 0; i < value.length; i++) {
1139
+ hash ^= value.charCodeAt(i);
1140
+ hash = Math.imul(hash, 16777619);
1141
+ }
1142
+ return (hash >>> 0).toString(16).padStart(8, "0");
1143
+ }
862
1144
  };
863
1145
 
864
1146
  export { DataTransformer, MigrationEngine, MigrationTracker, SchemaVersionManager };