@voidhash/mimic-effect 1.0.0-beta.1 → 1.0.0-beta.10

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 (142) hide show
  1. package/.turbo/turbo-build.log +116 -74
  2. package/dist/ColdStorage.cjs +9 -5
  3. package/dist/ColdStorage.d.cts.map +1 -1
  4. package/dist/ColdStorage.d.mts.map +1 -1
  5. package/dist/ColdStorage.mjs +9 -5
  6. package/dist/ColdStorage.mjs.map +1 -1
  7. package/dist/DocumentInstance.cjs +263 -0
  8. package/dist/DocumentInstance.d.cts +78 -0
  9. package/dist/DocumentInstance.d.cts.map +1 -0
  10. package/dist/DocumentInstance.d.mts +78 -0
  11. package/dist/DocumentInstance.d.mts.map +1 -0
  12. package/dist/DocumentInstance.mjs +264 -0
  13. package/dist/DocumentInstance.mjs.map +1 -0
  14. package/dist/Errors.cjs +10 -1
  15. package/dist/Errors.d.cts +18 -3
  16. package/dist/Errors.d.cts.map +1 -1
  17. package/dist/Errors.d.mts +18 -3
  18. package/dist/Errors.d.mts.map +1 -1
  19. package/dist/Errors.mjs +9 -1
  20. package/dist/Errors.mjs.map +1 -1
  21. package/dist/HotStorage.cjs +39 -12
  22. package/dist/HotStorage.d.cts +17 -1
  23. package/dist/HotStorage.d.cts.map +1 -1
  24. package/dist/HotStorage.d.mts +17 -1
  25. package/dist/HotStorage.d.mts.map +1 -1
  26. package/dist/HotStorage.mjs +39 -12
  27. package/dist/HotStorage.mjs.map +1 -1
  28. package/dist/Metrics.cjs +29 -1
  29. package/dist/Metrics.d.cts +5 -0
  30. package/dist/Metrics.d.cts.map +1 -1
  31. package/dist/Metrics.d.mts +5 -0
  32. package/dist/Metrics.d.mts.map +1 -1
  33. package/dist/Metrics.mjs +26 -1
  34. package/dist/Metrics.mjs.map +1 -1
  35. package/dist/MimicClusterServerEngine.cjs +44 -139
  36. package/dist/MimicClusterServerEngine.d.cts.map +1 -1
  37. package/dist/MimicClusterServerEngine.d.mts +1 -1
  38. package/dist/MimicClusterServerEngine.d.mts.map +1 -1
  39. package/dist/MimicClusterServerEngine.mjs +46 -141
  40. package/dist/MimicClusterServerEngine.mjs.map +1 -1
  41. package/dist/MimicServer.cjs +20 -20
  42. package/dist/MimicServer.d.cts.map +1 -1
  43. package/dist/MimicServer.d.mts.map +1 -1
  44. package/dist/MimicServer.mjs +20 -20
  45. package/dist/MimicServer.mjs.map +1 -1
  46. package/dist/MimicServerEngine.cjs +92 -11
  47. package/dist/MimicServerEngine.d.cts +12 -4
  48. package/dist/MimicServerEngine.d.cts.map +1 -1
  49. package/dist/MimicServerEngine.d.mts +12 -4
  50. package/dist/MimicServerEngine.d.mts.map +1 -1
  51. package/dist/MimicServerEngine.mjs +94 -13
  52. package/dist/MimicServerEngine.mjs.map +1 -1
  53. package/dist/PresenceManager.cjs +5 -5
  54. package/dist/PresenceManager.d.cts.map +1 -1
  55. package/dist/PresenceManager.d.mts.map +1 -1
  56. package/dist/PresenceManager.mjs +5 -5
  57. package/dist/PresenceManager.mjs.map +1 -1
  58. package/dist/Protocol.d.cts +1 -1
  59. package/dist/Protocol.d.mts +1 -1
  60. package/dist/Types.d.cts +9 -2
  61. package/dist/Types.d.cts.map +1 -1
  62. package/dist/Types.d.mts +9 -2
  63. package/dist/Types.d.mts.map +1 -1
  64. package/dist/index.cjs +5 -6
  65. package/dist/index.d.cts +3 -3
  66. package/dist/index.d.mts +3 -3
  67. package/dist/index.mjs +3 -3
  68. package/dist/testing/ColdStorageTestSuite.cjs +508 -0
  69. package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
  70. package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
  71. package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
  72. package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
  73. package/dist/testing/ColdStorageTestSuite.mjs +508 -0
  74. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
  75. package/dist/testing/FailingStorage.cjs +162 -0
  76. package/dist/testing/FailingStorage.d.cts +43 -0
  77. package/dist/testing/FailingStorage.d.cts.map +1 -0
  78. package/dist/testing/FailingStorage.d.mts +43 -0
  79. package/dist/testing/FailingStorage.d.mts.map +1 -0
  80. package/dist/testing/FailingStorage.mjs +163 -0
  81. package/dist/testing/FailingStorage.mjs.map +1 -0
  82. package/dist/testing/HotStorageTestSuite.cjs +820 -0
  83. package/dist/testing/HotStorageTestSuite.d.cts +42 -0
  84. package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
  85. package/dist/testing/HotStorageTestSuite.d.mts +42 -0
  86. package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
  87. package/dist/testing/HotStorageTestSuite.mjs +820 -0
  88. package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
  89. package/dist/testing/StorageIntegrationTestSuite.cjs +487 -0
  90. package/dist/testing/StorageIntegrationTestSuite.d.cts +37 -0
  91. package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
  92. package/dist/testing/StorageIntegrationTestSuite.d.mts +37 -0
  93. package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
  94. package/dist/testing/StorageIntegrationTestSuite.mjs +487 -0
  95. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
  96. package/dist/testing/assertions.cjs +117 -0
  97. package/dist/testing/assertions.mjs +112 -0
  98. package/dist/testing/assertions.mjs.map +1 -0
  99. package/dist/testing/index.cjs +14 -0
  100. package/dist/testing/index.d.cts +6 -0
  101. package/dist/testing/index.d.mts +6 -0
  102. package/dist/testing/index.mjs +7 -0
  103. package/dist/testing/types.cjs +15 -0
  104. package/dist/testing/types.d.cts +90 -0
  105. package/dist/testing/types.d.cts.map +1 -0
  106. package/dist/testing/types.d.mts +90 -0
  107. package/dist/testing/types.d.mts.map +1 -0
  108. package/dist/testing/types.mjs +16 -0
  109. package/dist/testing/types.mjs.map +1 -0
  110. package/package.json +8 -3
  111. package/src/ColdStorage.ts +21 -12
  112. package/src/DocumentInstance.ts +527 -0
  113. package/src/Errors.ts +15 -1
  114. package/src/HotStorage.ts +115 -24
  115. package/src/Metrics.ts +30 -0
  116. package/src/MimicClusterServerEngine.ts +120 -275
  117. package/src/MimicServer.ts +83 -75
  118. package/src/MimicServerEngine.ts +230 -30
  119. package/src/PresenceManager.ts +44 -34
  120. package/src/Types.ts +9 -2
  121. package/src/index.ts +5 -35
  122. package/src/testing/ColdStorageTestSuite.ts +589 -0
  123. package/src/testing/FailingStorage.ts +338 -0
  124. package/src/testing/HotStorageTestSuite.ts +1105 -0
  125. package/src/testing/StorageIntegrationTestSuite.ts +736 -0
  126. package/src/testing/assertions.ts +188 -0
  127. package/src/testing/index.ts +83 -0
  128. package/src/testing/types.ts +100 -0
  129. package/tests/ColdStorage.test.ts +8 -120
  130. package/tests/DocumentInstance.test.ts +669 -0
  131. package/tests/HotStorage.test.ts +7 -126
  132. package/tests/StorageIntegration.test.ts +259 -0
  133. package/tsdown.config.ts +1 -1
  134. package/dist/DocumentManager.cjs +0 -229
  135. package/dist/DocumentManager.d.cts +0 -59
  136. package/dist/DocumentManager.d.cts.map +0 -1
  137. package/dist/DocumentManager.d.mts +0 -59
  138. package/dist/DocumentManager.d.mts.map +0 -1
  139. package/dist/DocumentManager.mjs +0 -227
  140. package/dist/DocumentManager.mjs.map +0 -1
  141. package/src/DocumentManager.ts +0 -506
  142. package/tests/DocumentManager.test.ts +0 -335
package/dist/Metrics.mjs CHANGED
@@ -63,6 +63,10 @@ const transactionsLatency = Metric.histogram("mimic.transactions.latency_ms", Me
63
63
  */
64
64
  const storageSnapshots = Metric.counter("mimic.storage.snapshots");
65
65
  /**
66
+ * Snapshots triggered by idle document detection
67
+ */
68
+ const storageIdleSnapshots = Metric.counter("mimic.storage.idle_snapshots");
69
+ /**
66
70
  * Snapshot save duration histogram (milliseconds)
67
71
  */
68
72
  const storageSnapshotLatency = Metric.histogram("mimic.storage.snapshot_latency_ms", MetricBoundaries.exponential({
@@ -75,6 +79,22 @@ const storageSnapshotLatency = Metric.histogram("mimic.storage.snapshot_latency_
75
79
  */
76
80
  const storageWalAppends = Metric.counter("mimic.storage.wal_appends");
77
81
  /**
82
+ * Version gaps detected during WAL replay
83
+ */
84
+ const storageVersionGaps = Metric.counter("mimic.storage.version_gaps");
85
+ /**
86
+ * Failed WAL appends causing transaction rollback
87
+ */
88
+ const walAppendFailures = Metric.counter("mimic.storage.wal_append_failures");
89
+ /**
90
+ * ColdStorage load failures during document restore
91
+ */
92
+ const coldStorageLoadFailures = Metric.counter("mimic.storage.cold_load_failures");
93
+ /**
94
+ * HotStorage getEntries failures during document restore
95
+ */
96
+ const hotStorageLoadFailures = Metric.counter("mimic.storage.hot_load_failures");
97
+ /**
78
98
  * Presence set operations
79
99
  */
80
100
  const presenceUpdates = Metric.counter("mimic.presence.updates");
@@ -95,12 +115,17 @@ const MimicMetrics = {
95
115
  transactionsRejected,
96
116
  transactionsLatency,
97
117
  storageSnapshots,
118
+ storageIdleSnapshots,
98
119
  storageSnapshotLatency,
99
120
  storageWalAppends,
121
+ storageVersionGaps,
122
+ walAppendFailures,
123
+ coldStorageLoadFailures,
124
+ hotStorageLoadFailures,
100
125
  presenceUpdates,
101
126
  presenceActive
102
127
  };
103
128
 
104
129
  //#endregion
105
- export { MimicMetrics, connectionsActive, connectionsDuration, connectionsErrors, connectionsTotal, documentsActive, documentsCreated, documentsEvicted, documentsRestored, presenceActive, presenceUpdates, storageSnapshotLatency, storageSnapshots, storageWalAppends, transactionsLatency, transactionsProcessed, transactionsRejected };
130
+ export { MimicMetrics, connectionsActive, connectionsDuration, connectionsErrors, connectionsTotal, documentsActive, documentsCreated, documentsEvicted, documentsRestored, presenceActive, presenceUpdates, storageIdleSnapshots, storageSnapshotLatency, storageSnapshots, storageVersionGaps, storageWalAppends, transactionsLatency, transactionsProcessed, transactionsRejected, walAppendFailures };
106
131
  //# sourceMappingURL=Metrics.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Metrics.mjs","names":[],"sources":["../src/Metrics.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - Metrics\n *\n * Observability metrics using Effect's Metric API.\n */\nimport { Metric, MetricBoundaries } from \"effect\";\n\n// =============================================================================\n// Connection Metrics\n// =============================================================================\n\n/**\n * Current active WebSocket connections\n */\nexport const connectionsActive = Metric.gauge(\"mimic.connections.active\");\n\n/**\n * Total connections over lifetime\n */\nexport const connectionsTotal = Metric.counter(\"mimic.connections.total\");\n\n/**\n * Connection duration histogram (milliseconds)\n */\nexport const connectionsDuration = Metric.histogram(\n \"mimic.connections.duration_ms\",\n MetricBoundaries.exponential({\n start: 100,\n factor: 2,\n count: 15, // Up to ~3.2 million ms (~53 minutes)\n })\n);\n\n/**\n * Connection errors (auth failures, etc.)\n */\nexport const connectionsErrors = Metric.counter(\"mimic.connections.errors\");\n\n// =============================================================================\n// Document Metrics\n// =============================================================================\n\n/**\n * Documents currently in memory\n */\nexport const documentsActive = Metric.gauge(\"mimic.documents.active\");\n\n/**\n * New documents created\n */\nexport const documentsCreated = Metric.counter(\"mimic.documents.created\");\n\n/**\n * Documents restored from storage\n */\nexport const documentsRestored = Metric.counter(\"mimic.documents.restored\");\n\n/**\n * Documents garbage collected (evicted)\n */\nexport const documentsEvicted = Metric.counter(\"mimic.documents.evicted\");\n\n// =============================================================================\n// Transaction Metrics\n// =============================================================================\n\n/**\n * Successfully processed transactions\n */\nexport const transactionsProcessed = Metric.counter(\n \"mimic.transactions.processed\"\n);\n\n/**\n * Rejected transactions\n */\nexport const transactionsRejected = Metric.counter(\n \"mimic.transactions.rejected\"\n);\n\n/**\n * Transaction processing latency histogram (milliseconds)\n */\nexport const transactionsLatency = Metric.histogram(\n \"mimic.transactions.latency_ms\",\n MetricBoundaries.exponential({\n start: 0.1,\n factor: 2,\n count: 15, // Up to ~1638 ms\n })\n);\n\n// =============================================================================\n// Storage Metrics\n// =============================================================================\n\n/**\n * Snapshots saved to ColdStorage\n */\nexport const storageSnapshots = Metric.counter(\"mimic.storage.snapshots\");\n\n/**\n * Snapshot save duration histogram (milliseconds)\n */\nexport const storageSnapshotLatency = Metric.histogram(\n \"mimic.storage.snapshot_latency_ms\",\n MetricBoundaries.exponential({\n start: 1,\n factor: 2,\n count: 12, // Up to ~4 seconds\n })\n);\n\n/**\n * WAL entries written to HotStorage\n */\nexport const storageWalAppends = Metric.counter(\"mimic.storage.wal_appends\");\n\n// =============================================================================\n// Presence Metrics\n// =============================================================================\n\n/**\n * Presence set operations\n */\nexport const presenceUpdates = Metric.counter(\"mimic.presence.updates\");\n\n/**\n * Active presence entries\n */\nexport const presenceActive = Metric.gauge(\"mimic.presence.active\");\n\n// =============================================================================\n// Export namespace\n// =============================================================================\n\nexport const MimicMetrics = {\n // Connection\n connectionsActive,\n connectionsTotal,\n connectionsDuration,\n connectionsErrors,\n\n // Document\n documentsActive,\n documentsCreated,\n documentsRestored,\n documentsEvicted,\n\n // Transaction\n transactionsProcessed,\n transactionsRejected,\n transactionsLatency,\n\n // Storage\n storageSnapshots,\n storageSnapshotLatency,\n storageWalAppends,\n\n // Presence\n presenceUpdates,\n presenceActive,\n};\n"],"mappings":";;;;;;;;;;;AAcA,MAAa,oBAAoB,OAAO,MAAM,2BAA2B;;;;AAKzE,MAAa,mBAAmB,OAAO,QAAQ,0BAA0B;;;;AAKzE,MAAa,sBAAsB,OAAO,UACxC,iCACA,iBAAiB,YAAY;CAC3B,OAAO;CACP,QAAQ;CACR,OAAO;CACR,CAAC,CACH;;;;AAKD,MAAa,oBAAoB,OAAO,QAAQ,2BAA2B;;;;AAS3E,MAAa,kBAAkB,OAAO,MAAM,yBAAyB;;;;AAKrE,MAAa,mBAAmB,OAAO,QAAQ,0BAA0B;;;;AAKzE,MAAa,oBAAoB,OAAO,QAAQ,2BAA2B;;;;AAK3E,MAAa,mBAAmB,OAAO,QAAQ,0BAA0B;;;;AASzE,MAAa,wBAAwB,OAAO,QAC1C,+BACD;;;;AAKD,MAAa,uBAAuB,OAAO,QACzC,8BACD;;;;AAKD,MAAa,sBAAsB,OAAO,UACxC,iCACA,iBAAiB,YAAY;CAC3B,OAAO;CACP,QAAQ;CACR,OAAO;CACR,CAAC,CACH;;;;AASD,MAAa,mBAAmB,OAAO,QAAQ,0BAA0B;;;;AAKzE,MAAa,yBAAyB,OAAO,UAC3C,qCACA,iBAAiB,YAAY;CAC3B,OAAO;CACP,QAAQ;CACR,OAAO;CACR,CAAC,CACH;;;;AAKD,MAAa,oBAAoB,OAAO,QAAQ,4BAA4B;;;;AAS5E,MAAa,kBAAkB,OAAO,QAAQ,yBAAyB;;;;AAKvE,MAAa,iBAAiB,OAAO,MAAM,wBAAwB;AAMnE,MAAa,eAAe;CAE1B;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACD"}
1
+ {"version":3,"file":"Metrics.mjs","names":[],"sources":["../src/Metrics.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - Metrics\n *\n * Observability metrics using Effect's Metric API.\n */\nimport { Metric, MetricBoundaries } from \"effect\";\n\n// =============================================================================\n// Connection Metrics\n// =============================================================================\n\n/**\n * Current active WebSocket connections\n */\nexport const connectionsActive = Metric.gauge(\"mimic.connections.active\");\n\n/**\n * Total connections over lifetime\n */\nexport const connectionsTotal = Metric.counter(\"mimic.connections.total\");\n\n/**\n * Connection duration histogram (milliseconds)\n */\nexport const connectionsDuration = Metric.histogram(\n \"mimic.connections.duration_ms\",\n MetricBoundaries.exponential({\n start: 100,\n factor: 2,\n count: 15, // Up to ~3.2 million ms (~53 minutes)\n })\n);\n\n/**\n * Connection errors (auth failures, etc.)\n */\nexport const connectionsErrors = Metric.counter(\"mimic.connections.errors\");\n\n// =============================================================================\n// Document Metrics\n// =============================================================================\n\n/**\n * Documents currently in memory\n */\nexport const documentsActive = Metric.gauge(\"mimic.documents.active\");\n\n/**\n * New documents created\n */\nexport const documentsCreated = Metric.counter(\"mimic.documents.created\");\n\n/**\n * Documents restored from storage\n */\nexport const documentsRestored = Metric.counter(\"mimic.documents.restored\");\n\n/**\n * Documents garbage collected (evicted)\n */\nexport const documentsEvicted = Metric.counter(\"mimic.documents.evicted\");\n\n// =============================================================================\n// Transaction Metrics\n// =============================================================================\n\n/**\n * Successfully processed transactions\n */\nexport const transactionsProcessed = Metric.counter(\n \"mimic.transactions.processed\"\n);\n\n/**\n * Rejected transactions\n */\nexport const transactionsRejected = Metric.counter(\n \"mimic.transactions.rejected\"\n);\n\n/**\n * Transaction processing latency histogram (milliseconds)\n */\nexport const transactionsLatency = Metric.histogram(\n \"mimic.transactions.latency_ms\",\n MetricBoundaries.exponential({\n start: 0.1,\n factor: 2,\n count: 15, // Up to ~1638 ms\n })\n);\n\n// =============================================================================\n// Storage Metrics\n// =============================================================================\n\n/**\n * Snapshots saved to ColdStorage\n */\nexport const storageSnapshots = Metric.counter(\"mimic.storage.snapshots\");\n\n/**\n * Snapshots triggered by idle document detection\n */\nexport const storageIdleSnapshots = Metric.counter(\"mimic.storage.idle_snapshots\");\n\n/**\n * Snapshot save duration histogram (milliseconds)\n */\nexport const storageSnapshotLatency = Metric.histogram(\n \"mimic.storage.snapshot_latency_ms\",\n MetricBoundaries.exponential({\n start: 1,\n factor: 2,\n count: 12, // Up to ~4 seconds\n })\n);\n\n/**\n * WAL entries written to HotStorage\n */\nexport const storageWalAppends = Metric.counter(\"mimic.storage.wal_appends\");\n\n/**\n * Version gaps detected during WAL replay\n */\nexport const storageVersionGaps = Metric.counter(\"mimic.storage.version_gaps\");\n\n/**\n * Failed WAL appends causing transaction rollback\n */\nexport const walAppendFailures = Metric.counter(\"mimic.storage.wal_append_failures\");\n\n/**\n * ColdStorage load failures during document restore\n */\nexport const coldStorageLoadFailures = Metric.counter(\"mimic.storage.cold_load_failures\");\n\n/**\n * HotStorage getEntries failures during document restore\n */\nexport const hotStorageLoadFailures = Metric.counter(\"mimic.storage.hot_load_failures\");\n\n// =============================================================================\n// Presence Metrics\n// =============================================================================\n\n/**\n * Presence set operations\n */\nexport const presenceUpdates = Metric.counter(\"mimic.presence.updates\");\n\n/**\n * Active presence entries\n */\nexport const presenceActive = Metric.gauge(\"mimic.presence.active\");\n\n// =============================================================================\n// Export namespace\n// =============================================================================\n\nexport const MimicMetrics = {\n // Connection\n connectionsActive,\n connectionsTotal,\n connectionsDuration,\n connectionsErrors,\n\n // Document\n documentsActive,\n documentsCreated,\n documentsRestored,\n documentsEvicted,\n\n // Transaction\n transactionsProcessed,\n transactionsRejected,\n transactionsLatency,\n\n // Storage\n storageSnapshots,\n storageIdleSnapshots,\n storageSnapshotLatency,\n storageWalAppends,\n storageVersionGaps,\n walAppendFailures,\n coldStorageLoadFailures,\n hotStorageLoadFailures,\n\n // Presence\n presenceUpdates,\n presenceActive,\n};\n"],"mappings":";;;;;;;;;;;AAcA,MAAa,oBAAoB,OAAO,MAAM,2BAA2B;;;;AAKzE,MAAa,mBAAmB,OAAO,QAAQ,0BAA0B;;;;AAKzE,MAAa,sBAAsB,OAAO,UACxC,iCACA,iBAAiB,YAAY;CAC3B,OAAO;CACP,QAAQ;CACR,OAAO;CACR,CAAC,CACH;;;;AAKD,MAAa,oBAAoB,OAAO,QAAQ,2BAA2B;;;;AAS3E,MAAa,kBAAkB,OAAO,MAAM,yBAAyB;;;;AAKrE,MAAa,mBAAmB,OAAO,QAAQ,0BAA0B;;;;AAKzE,MAAa,oBAAoB,OAAO,QAAQ,2BAA2B;;;;AAK3E,MAAa,mBAAmB,OAAO,QAAQ,0BAA0B;;;;AASzE,MAAa,wBAAwB,OAAO,QAC1C,+BACD;;;;AAKD,MAAa,uBAAuB,OAAO,QACzC,8BACD;;;;AAKD,MAAa,sBAAsB,OAAO,UACxC,iCACA,iBAAiB,YAAY;CAC3B,OAAO;CACP,QAAQ;CACR,OAAO;CACR,CAAC,CACH;;;;AASD,MAAa,mBAAmB,OAAO,QAAQ,0BAA0B;;;;AAKzE,MAAa,uBAAuB,OAAO,QAAQ,+BAA+B;;;;AAKlF,MAAa,yBAAyB,OAAO,UAC3C,qCACA,iBAAiB,YAAY;CAC3B,OAAO;CACP,QAAQ;CACR,OAAO;CACR,CAAC,CACH;;;;AAKD,MAAa,oBAAoB,OAAO,QAAQ,4BAA4B;;;;AAK5E,MAAa,qBAAqB,OAAO,QAAQ,6BAA6B;;;;AAK9E,MAAa,oBAAoB,OAAO,QAAQ,oCAAoC;;;;AAKpF,MAAa,0BAA0B,OAAO,QAAQ,mCAAmC;;;;AAKzF,MAAa,yBAAyB,OAAO,QAAQ,kCAAkC;;;;AASvF,MAAa,kBAAkB,OAAO,QAAQ,yBAAyB;;;;AAKvE,MAAa,iBAAiB,OAAO,MAAM,wBAAwB;AAMnE,MAAa,eAAe;CAE1B;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACD"}
@@ -1,10 +1,10 @@
1
1
  const require_ColdStorage = require('./ColdStorage.cjs');
2
2
  const require_HotStorage = require('./HotStorage.cjs');
3
3
  const require_Metrics = require('./Metrics.cjs');
4
+ const require_DocumentInstance = require('./DocumentInstance.cjs');
4
5
  const require_objectSpread2 = require('./_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.cjs');
5
6
  const require_MimicServerEngine = require('./MimicServerEngine.cjs');
6
7
  let effect = require("effect");
7
- let _voidhash_mimic_server = require("@voidhash/mimic/server");
8
8
  let _effect_cluster = require("@effect/cluster");
9
9
  let _effect_rpc = require("@effect/rpc");
10
10
 
@@ -21,6 +21,7 @@ const DEFAULT_MAX_IDLE_TIME = effect.Duration.minutes(5);
21
21
  const DEFAULT_MAX_TRANSACTION_HISTORY = 1e3;
22
22
  const DEFAULT_SNAPSHOT_INTERVAL = effect.Duration.minutes(5);
23
23
  const DEFAULT_SNAPSHOT_THRESHOLD = 100;
24
+ const DEFAULT_SNAPSHOT_IDLE_TIMEOUT = effect.Duration.seconds(30);
24
25
  const DEFAULT_SHARD_GROUP = "mimic-documents";
25
26
  /**
26
27
  * Schema for encoded transaction (wire format)
@@ -99,7 +100,7 @@ const MimicDocumentEntity = _effect_cluster.Entity.make("MimicDocument", [
99
100
  */
100
101
  var MimicClusterConfigTag = class extends effect.Context.Tag("@voidhash/mimic-effect/MimicClusterConfig")() {};
101
102
  const resolveClusterConfig = (config) => {
102
- var _config$maxTransactio, _config$snapshot, _config$snapshot$tran, _config$snapshot2, _config$shardGroup;
103
+ var _config$maxTransactio, _config$snapshot, _config$snapshot$tran, _config$snapshot2, _config$snapshot3, _config$shardGroup;
103
104
  return {
104
105
  schema: config.schema,
105
106
  initial: config.initial,
@@ -108,7 +109,8 @@ const resolveClusterConfig = (config) => {
108
109
  maxTransactionHistory: (_config$maxTransactio = config.maxTransactionHistory) !== null && _config$maxTransactio !== void 0 ? _config$maxTransactio : DEFAULT_MAX_TRANSACTION_HISTORY,
109
110
  snapshot: {
110
111
  interval: ((_config$snapshot = config.snapshot) === null || _config$snapshot === void 0 ? void 0 : _config$snapshot.interval) ? effect.Duration.decode(config.snapshot.interval) : DEFAULT_SNAPSHOT_INTERVAL,
111
- transactionThreshold: (_config$snapshot$tran = (_config$snapshot2 = config.snapshot) === null || _config$snapshot2 === void 0 ? void 0 : _config$snapshot2.transactionThreshold) !== null && _config$snapshot$tran !== void 0 ? _config$snapshot$tran : DEFAULT_SNAPSHOT_THRESHOLD
112
+ transactionThreshold: (_config$snapshot$tran = (_config$snapshot2 = config.snapshot) === null || _config$snapshot2 === void 0 ? void 0 : _config$snapshot2.transactionThreshold) !== null && _config$snapshot$tran !== void 0 ? _config$snapshot$tran : DEFAULT_SNAPSHOT_THRESHOLD,
113
+ idleTimeout: ((_config$snapshot3 = config.snapshot) === null || _config$snapshot3 === void 0 ? void 0 : _config$snapshot3.idleTimeout) ? effect.Duration.decode(config.snapshot.idleTimeout) : DEFAULT_SNAPSHOT_IDLE_TIMEOUT
112
114
  },
113
115
  shardGroup: (_config$shardGroup = config.shardGroup) !== null && _config$shardGroup !== void 0 ? _config$shardGroup : DEFAULT_SHARD_GROUP
114
116
  };
@@ -130,150 +132,53 @@ const encodeTransaction = (tx) => {
130
132
  /**
131
133
  * Create the entity handler for MimicDocument
132
134
  */
133
- const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.gen(function* () {
135
+ const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.fn("cluster.entity.handler.create")(function* () {
134
136
  const documentId = (yield* _effect_cluster.Entity.CurrentAddress).entityId;
135
- const SCHEMA_VERSION = 1;
136
- const computeInitialState = () => {
137
- if (config.initial === void 0) return effect.Effect.succeed(void 0);
138
- if (typeof config.initial === "function") return config.initial({ documentId });
139
- return effect.Effect.succeed(config.initial);
140
- };
141
- const storedDoc = yield* effect.Effect.catchAll(coldStorage.load(documentId), () => effect.Effect.succeed(void 0));
142
- let initialState;
143
- let initialVersion = 0;
144
- if (storedDoc) {
145
- initialState = storedDoc.state;
146
- initialVersion = storedDoc.version;
147
- } else initialState = yield* computeInitialState();
148
- const broadcastPubSub = yield* effect.PubSub.unbounded();
137
+ const instance = yield* require_DocumentInstance.DocumentInstance.make(documentId, {
138
+ schema: config.schema,
139
+ initial: config.initial,
140
+ maxTransactionHistory: config.maxTransactionHistory,
141
+ snapshot: config.snapshot
142
+ }, coldStorage, hotStorage).pipe(effect.Effect.orDie);
149
143
  const presencePubSub = yield* effect.PubSub.unbounded();
150
144
  const stateRef = yield* effect.Ref.make({
151
- document: void 0,
152
- broadcastPubSub,
145
+ instance,
153
146
  presences: effect.HashMap.empty(),
154
- presencePubSub,
155
- lastSnapshotVersion: initialVersion,
156
- lastSnapshotTime: Date.now(),
157
- transactionsSinceSnapshot: 0
158
- });
159
- const document = _voidhash_mimic_server.ServerDocument.make({
160
- schema: config.schema,
161
- initialState,
162
- initialVersion,
163
- maxTransactionHistory: config.maxTransactionHistory,
164
- onBroadcast: (message) => {
165
- effect.Effect.runSync(effect.PubSub.publish(broadcastPubSub, {
166
- type: "transaction",
167
- transaction: message.transaction,
168
- version: message.version
169
- }));
170
- },
171
- onRejection: (transactionId, reason) => {
172
- effect.Effect.runSync(effect.PubSub.publish(broadcastPubSub, {
173
- type: "error",
174
- transactionId,
175
- reason
176
- }));
177
- }
147
+ presencePubSub
178
148
  });
179
- yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, { document }));
180
- const walEntries = yield* effect.Effect.catchAll(hotStorage.getEntries(documentId, initialVersion), () => effect.Effect.succeed([]));
181
- for (const entry of walEntries) {
182
- const result = document.submit(entry.transaction);
183
- if (!result.success) yield* effect.Effect.logWarning("Skipping corrupted WAL entry", {
184
- documentId,
185
- version: entry.version,
186
- reason: result.reason
187
- });
188
- }
189
- if (storedDoc) yield* effect.Metric.increment(require_Metrics.documentsRestored);
190
- else yield* effect.Metric.increment(require_Metrics.documentsCreated);
191
- yield* effect.Metric.incrementBy(require_Metrics.documentsActive, 1);
192
- /**
193
- * Save snapshot to ColdStorage
194
- */
195
- const saveSnapshot = effect.Effect.gen(function* () {
196
- const state = yield* effect.Ref.get(stateRef);
197
- const docState = state.document.get();
198
- const version = state.document.getVersion();
199
- if (docState === void 0) return;
200
- const storedDocument = {
201
- state: docState,
202
- version,
203
- schemaVersion: SCHEMA_VERSION,
204
- savedAt: Date.now()
205
- };
206
- const snapshotStartTime = Date.now();
207
- yield* effect.Effect.catchAll(coldStorage.save(documentId, storedDocument), (e) => effect.Effect.logError("Failed to save snapshot", {
208
- documentId,
209
- error: e
210
- }));
211
- const snapshotDuration = Date.now() - snapshotStartTime;
212
- yield* effect.Metric.increment(require_Metrics.storageSnapshots);
213
- yield* effect.Metric.update(require_Metrics.storageSnapshotLatency, snapshotDuration);
214
- yield* effect.Effect.catchAll(hotStorage.truncate(documentId, version), (e) => effect.Effect.logError("Failed to truncate WAL", {
149
+ yield* effect.Effect.addFinalizer(() => effect.Effect.fn("cluster.entity.finalize")(function* () {
150
+ yield* effect.Effect.catchAll(instance.saveSnapshot(), (e) => effect.Effect.logError("Failed to save snapshot during entity finalization", {
215
151
  documentId,
216
152
  error: e
217
153
  }));
218
- yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, {
219
- lastSnapshotVersion: version,
220
- lastSnapshotTime: Date.now(),
221
- transactionsSinceSnapshot: 0
222
- }));
223
- });
224
- /**
225
- * Check if snapshot should be triggered
226
- */
227
- const checkSnapshotTriggers = effect.Effect.gen(function* () {
228
- const state = yield* effect.Ref.get(stateRef);
229
- const now = Date.now();
230
- const intervalMs = effect.Duration.toMillis(config.snapshot.interval);
231
- const threshold = config.snapshot.transactionThreshold;
232
- if (state.transactionsSinceSnapshot >= threshold) {
233
- yield* saveSnapshot;
234
- return;
235
- }
236
- if (now - state.lastSnapshotTime >= intervalMs) {
237
- yield* saveSnapshot;
238
- return;
239
- }
240
- });
241
- yield* effect.Effect.addFinalizer(() => effect.Effect.gen(function* () {
242
- yield* saveSnapshot;
243
154
  yield* effect.Metric.incrementBy(require_Metrics.documentsActive, -1);
244
155
  yield* effect.Metric.increment(require_Metrics.documentsEvicted);
245
156
  yield* effect.Effect.logDebug("Entity finalized", { documentId });
246
- }));
157
+ })());
158
+ if (effect.Duration.toMillis(config.snapshot.idleTimeout) > 0) yield* effect.Effect.fn("cluster.entity.snapshot.loop")(function* () {
159
+ if (yield* instance.needsSnapshot()) {
160
+ yield* effect.Effect.catchAll(instance.saveSnapshot(), (e) => effect.Effect.logWarning("Periodic snapshot failed in cluster entity", {
161
+ documentId,
162
+ error: e
163
+ }));
164
+ yield* effect.Metric.increment(require_Metrics.storageIdleSnapshots);
165
+ }
166
+ })().pipe(effect.Effect.repeat(effect.Schedule.spaced(config.snapshot.idleTimeout)), effect.Effect.fork);
247
167
  return {
248
- Submit: effect.Effect.fnUntraced(function* ({ payload }) {
249
- const submitStartTime = Date.now();
250
- const state = yield* effect.Ref.get(stateRef);
168
+ Submit: effect.Effect.fn("cluster.document.transaction.submit")(function* ({ payload }) {
251
169
  const transaction = decodeTransaction(payload.transaction);
252
- const result = state.document.submit(transaction);
253
- const latency = Date.now() - submitStartTime;
254
- yield* effect.Metric.update(require_Metrics.transactionsLatency, latency);
255
- if (result.success) {
256
- yield* effect.Metric.increment(require_Metrics.transactionsProcessed);
257
- const walEntry = {
258
- transaction,
259
- version: result.version,
260
- timestamp: Date.now()
261
- };
262
- yield* effect.Effect.catchAll(hotStorage.append(documentId, walEntry), (e) => effect.Effect.logError("Failed to append to WAL", {
263
- documentId,
264
- error: e
265
- }));
266
- yield* effect.Metric.increment(require_Metrics.storageWalAppends);
267
- yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, { transactionsSinceSnapshot: s.transactionsSinceSnapshot + 1 }));
268
- yield* checkSnapshotTriggers;
269
- } else yield* effect.Metric.increment(require_Metrics.transactionsRejected);
270
- return result;
170
+ return yield* instance.submit(transaction).pipe(effect.Effect.catchAll((error) => effect.Effect.succeed({
171
+ success: false,
172
+ reason: `Storage error: ${String(error)}`
173
+ })));
271
174
  }),
272
- GetSnapshot: effect.Effect.fnUntraced(function* () {
273
- return (yield* effect.Ref.get(stateRef)).document.getSnapshot();
175
+ GetSnapshot: effect.Effect.fn("cluster.document.snapshot.get")(function* () {
176
+ return instance.getSnapshot();
274
177
  }),
275
- Touch: effect.Effect.fnUntraced(function* () {}),
276
- SetPresence: effect.Effect.fnUntraced(function* ({ payload }) {
178
+ Touch: effect.Effect.fn("cluster.document.touch")(function* () {
179
+ yield* instance.touch();
180
+ }),
181
+ SetPresence: effect.Effect.fn("cluster.presence.set")(function* ({ payload }) {
277
182
  const { connectionId, entry } = payload;
278
183
  yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, { presences: effect.HashMap.set(s.presences, connectionId, entry) }));
279
184
  yield* effect.Metric.increment(require_Metrics.presenceUpdates);
@@ -287,7 +192,7 @@ const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.g
287
192
  };
288
193
  yield* effect.PubSub.publish(state.presencePubSub, event);
289
194
  }),
290
- RemovePresence: effect.Effect.fnUntraced(function* ({ payload }) {
195
+ RemovePresence: effect.Effect.fn("cluster.presence.remove")(function* ({ payload }) {
291
196
  const { connectionId } = payload;
292
197
  const state = yield* effect.Ref.get(stateRef);
293
198
  if (!effect.HashMap.has(state.presences, connectionId)) return;
@@ -299,20 +204,20 @@ const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.g
299
204
  };
300
205
  yield* effect.PubSub.publish(state.presencePubSub, event);
301
206
  }),
302
- GetPresenceSnapshot: effect.Effect.fnUntraced(function* () {
207
+ GetPresenceSnapshot: effect.Effect.fn("cluster.presence.snapshot.get")(function* () {
303
208
  const state = yield* effect.Ref.get(stateRef);
304
209
  const presences = {};
305
210
  for (const [id, entry] of state.presences) presences[id] = entry;
306
211
  return { presences };
307
212
  })
308
213
  };
309
- });
214
+ })();
310
215
  var SubscriptionStoreTag = class extends effect.Context.Tag("@voidhash/mimic-effect/SubscriptionStore")() {};
311
- const subscriptionStoreLayer = effect.Layer.effect(SubscriptionStoreTag, effect.Effect.gen(function* () {
216
+ const subscriptionStoreLayer = effect.Layer.effect(SubscriptionStoreTag, effect.Effect.fn("cluster.subscriptions.layer.create")(function* () {
312
217
  const documentPubSubs = yield* effect.Ref.make(effect.HashMap.empty());
313
218
  const presencePubSubs = yield* effect.Ref.make(effect.HashMap.empty());
314
219
  return {
315
- getOrCreatePubSub: (documentId) => effect.Effect.gen(function* () {
220
+ getOrCreatePubSub: effect.Effect.fn("cluster.subscriptions.pubsub.get-or-create")(function* (documentId) {
316
221
  const current = yield* effect.Ref.get(documentPubSubs);
317
222
  const existing = effect.HashMap.get(current, documentId);
318
223
  if (existing._tag === "Some") return existing.value;
@@ -320,7 +225,7 @@ const subscriptionStoreLayer = effect.Layer.effect(SubscriptionStoreTag, effect.
320
225
  yield* effect.Ref.update(documentPubSubs, (map) => effect.HashMap.set(map, documentId, pubsub));
321
226
  return pubsub;
322
227
  }),
323
- getOrCreatePresencePubSub: (documentId) => effect.Effect.gen(function* () {
228
+ getOrCreatePresencePubSub: effect.Effect.fn("cluster.subscriptions.presence-pubsub.get-or-create")(function* (documentId) {
324
229
  const current = yield* effect.Ref.get(presencePubSubs);
325
230
  const existing = effect.HashMap.get(current, documentId);
326
231
  if (existing._tag === "Some") return existing.value;
@@ -329,7 +234,7 @@ const subscriptionStoreLayer = effect.Layer.effect(SubscriptionStoreTag, effect.
329
234
  return pubsub;
330
235
  })
331
236
  };
332
- }));
237
+ })());
333
238
  /**
334
239
  * Create a MimicClusterServerEngine layer.
335
240
  *
@@ -1 +1 @@
1
- {"version":3,"file":"MimicClusterServerEngine.d.cts","names":[],"sources":["../src/MimicClusterServerEngine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAqzBa;yBA3JwB,SAAA,CAAU,sBACrC,+BAA+B,aACtC,KAAA,CAAM,MACP,6BAEA,iBAAiB,gBAAgB,sBAAsB,QAAA,CAAS"}
1
+ {"version":3,"file":"MimicClusterServerEngine.d.cts","names":[],"sources":["../src/MimicClusterServerEngine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cA0pBa;yBA3JwB,SAAA,CAAU,sBACrC,+BAA+B,aACtC,KAAA,CAAM,MACP,6BAEA,iBAAiB,gBAAgB,sBAAsB,QAAA,CAAS"}
@@ -4,8 +4,8 @@ import { HotStorageTag } from "./HotStorage.mjs";
4
4
  import { MimicAuthServiceTag } from "./MimicAuthService.mjs";
5
5
  import { MimicServerEngineTag } from "./MimicServerEngine.mjs";
6
6
  import { Layer } from "effect";
7
- import { Sharding } from "@effect/cluster";
8
7
  import { Primitive } from "@voidhash/mimic";
8
+ import { Sharding } from "@effect/cluster";
9
9
 
10
10
  //#region src/MimicClusterServerEngine.d.ts
11
11
 
@@ -1 +1 @@
1
- {"version":3,"file":"MimicClusterServerEngine.d.mts","names":[],"sources":["../src/MimicClusterServerEngine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAqzBa;yBA3JwB,SAAA,CAAU,sBACrC,+BAA+B,aACtC,KAAA,CAAM,MACP,6BAEA,iBAAiB,gBAAgB,sBAAsB,QAAA,CAAS"}
1
+ {"version":3,"file":"MimicClusterServerEngine.d.mts","names":[],"sources":["../src/MimicClusterServerEngine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cA0pBa;yBA3JwB,SAAA,CAAU,sBACrC,+BAA+B,aACtC,KAAA,CAAM,MACP,6BAEA,iBAAiB,gBAAgB,sBAAsB,QAAA,CAAS"}
@@ -1,11 +1,11 @@
1
1
  import { __require } from "./_virtual/rolldown_runtime.mjs";
2
2
  import { ColdStorageTag } from "./ColdStorage.mjs";
3
3
  import { HotStorageTag } from "./HotStorage.mjs";
4
- import { documentsActive, documentsCreated, documentsEvicted, documentsRestored, presenceActive, presenceUpdates, storageSnapshotLatency, storageSnapshots, storageWalAppends, transactionsLatency, transactionsProcessed, transactionsRejected } from "./Metrics.mjs";
4
+ import { documentsActive, documentsEvicted, presenceActive, presenceUpdates, storageIdleSnapshots } from "./Metrics.mjs";
5
+ import { DocumentInstance } from "./DocumentInstance.mjs";
5
6
  import { _objectSpread2 } from "./_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs";
6
7
  import { MimicServerEngineTag } from "./MimicServerEngine.mjs";
7
- import { Context, Duration, Effect, HashMap, Layer, Metric, PubSub, Ref, Schema, Stream } from "effect";
8
- import { ServerDocument } from "@voidhash/mimic/server";
8
+ import { Context, Duration, Effect, HashMap, Layer, Metric, PubSub, Ref, Schedule, Schema, Stream } from "effect";
9
9
  import { Entity } from "@effect/cluster";
10
10
  import { Rpc } from "@effect/rpc";
11
11
 
@@ -22,6 +22,7 @@ const DEFAULT_MAX_IDLE_TIME = Duration.minutes(5);
22
22
  const DEFAULT_MAX_TRANSACTION_HISTORY = 1e3;
23
23
  const DEFAULT_SNAPSHOT_INTERVAL = Duration.minutes(5);
24
24
  const DEFAULT_SNAPSHOT_THRESHOLD = 100;
25
+ const DEFAULT_SNAPSHOT_IDLE_TIMEOUT = Duration.seconds(30);
25
26
  const DEFAULT_SHARD_GROUP = "mimic-documents";
26
27
  /**
27
28
  * Schema for encoded transaction (wire format)
@@ -100,7 +101,7 @@ const MimicDocumentEntity = Entity.make("MimicDocument", [
100
101
  */
101
102
  var MimicClusterConfigTag = class extends Context.Tag("@voidhash/mimic-effect/MimicClusterConfig")() {};
102
103
  const resolveClusterConfig = (config) => {
103
- var _config$maxTransactio, _config$snapshot, _config$snapshot$tran, _config$snapshot2, _config$shardGroup;
104
+ var _config$maxTransactio, _config$snapshot, _config$snapshot$tran, _config$snapshot2, _config$snapshot3, _config$shardGroup;
104
105
  return {
105
106
  schema: config.schema,
106
107
  initial: config.initial,
@@ -109,7 +110,8 @@ const resolveClusterConfig = (config) => {
109
110
  maxTransactionHistory: (_config$maxTransactio = config.maxTransactionHistory) !== null && _config$maxTransactio !== void 0 ? _config$maxTransactio : DEFAULT_MAX_TRANSACTION_HISTORY,
110
111
  snapshot: {
111
112
  interval: ((_config$snapshot = config.snapshot) === null || _config$snapshot === void 0 ? void 0 : _config$snapshot.interval) ? Duration.decode(config.snapshot.interval) : DEFAULT_SNAPSHOT_INTERVAL,
112
- transactionThreshold: (_config$snapshot$tran = (_config$snapshot2 = config.snapshot) === null || _config$snapshot2 === void 0 ? void 0 : _config$snapshot2.transactionThreshold) !== null && _config$snapshot$tran !== void 0 ? _config$snapshot$tran : DEFAULT_SNAPSHOT_THRESHOLD
113
+ transactionThreshold: (_config$snapshot$tran = (_config$snapshot2 = config.snapshot) === null || _config$snapshot2 === void 0 ? void 0 : _config$snapshot2.transactionThreshold) !== null && _config$snapshot$tran !== void 0 ? _config$snapshot$tran : DEFAULT_SNAPSHOT_THRESHOLD,
114
+ idleTimeout: ((_config$snapshot3 = config.snapshot) === null || _config$snapshot3 === void 0 ? void 0 : _config$snapshot3.idleTimeout) ? Duration.decode(config.snapshot.idleTimeout) : DEFAULT_SNAPSHOT_IDLE_TIMEOUT
113
115
  },
114
116
  shardGroup: (_config$shardGroup = config.shardGroup) !== null && _config$shardGroup !== void 0 ? _config$shardGroup : DEFAULT_SHARD_GROUP
115
117
  };
@@ -131,150 +133,53 @@ const encodeTransaction = (tx) => {
131
133
  /**
132
134
  * Create the entity handler for MimicDocument
133
135
  */
134
- const createEntityHandler = (config, coldStorage, hotStorage) => Effect.gen(function* () {
136
+ const createEntityHandler = (config, coldStorage, hotStorage) => Effect.fn("cluster.entity.handler.create")(function* () {
135
137
  const documentId = (yield* Entity.CurrentAddress).entityId;
136
- const SCHEMA_VERSION = 1;
137
- const computeInitialState = () => {
138
- if (config.initial === void 0) return Effect.succeed(void 0);
139
- if (typeof config.initial === "function") return config.initial({ documentId });
140
- return Effect.succeed(config.initial);
141
- };
142
- const storedDoc = yield* Effect.catchAll(coldStorage.load(documentId), () => Effect.succeed(void 0));
143
- let initialState;
144
- let initialVersion = 0;
145
- if (storedDoc) {
146
- initialState = storedDoc.state;
147
- initialVersion = storedDoc.version;
148
- } else initialState = yield* computeInitialState();
149
- const broadcastPubSub = yield* PubSub.unbounded();
138
+ const instance = yield* DocumentInstance.make(documentId, {
139
+ schema: config.schema,
140
+ initial: config.initial,
141
+ maxTransactionHistory: config.maxTransactionHistory,
142
+ snapshot: config.snapshot
143
+ }, coldStorage, hotStorage).pipe(Effect.orDie);
150
144
  const presencePubSub = yield* PubSub.unbounded();
151
145
  const stateRef = yield* Ref.make({
152
- document: void 0,
153
- broadcastPubSub,
146
+ instance,
154
147
  presences: HashMap.empty(),
155
- presencePubSub,
156
- lastSnapshotVersion: initialVersion,
157
- lastSnapshotTime: Date.now(),
158
- transactionsSinceSnapshot: 0
159
- });
160
- const document = ServerDocument.make({
161
- schema: config.schema,
162
- initialState,
163
- initialVersion,
164
- maxTransactionHistory: config.maxTransactionHistory,
165
- onBroadcast: (message) => {
166
- Effect.runSync(PubSub.publish(broadcastPubSub, {
167
- type: "transaction",
168
- transaction: message.transaction,
169
- version: message.version
170
- }));
171
- },
172
- onRejection: (transactionId, reason) => {
173
- Effect.runSync(PubSub.publish(broadcastPubSub, {
174
- type: "error",
175
- transactionId,
176
- reason
177
- }));
178
- }
148
+ presencePubSub
179
149
  });
180
- yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, { document }));
181
- const walEntries = yield* Effect.catchAll(hotStorage.getEntries(documentId, initialVersion), () => Effect.succeed([]));
182
- for (const entry of walEntries) {
183
- const result = document.submit(entry.transaction);
184
- if (!result.success) yield* Effect.logWarning("Skipping corrupted WAL entry", {
185
- documentId,
186
- version: entry.version,
187
- reason: result.reason
188
- });
189
- }
190
- if (storedDoc) yield* Metric.increment(documentsRestored);
191
- else yield* Metric.increment(documentsCreated);
192
- yield* Metric.incrementBy(documentsActive, 1);
193
- /**
194
- * Save snapshot to ColdStorage
195
- */
196
- const saveSnapshot = Effect.gen(function* () {
197
- const state = yield* Ref.get(stateRef);
198
- const docState = state.document.get();
199
- const version = state.document.getVersion();
200
- if (docState === void 0) return;
201
- const storedDocument = {
202
- state: docState,
203
- version,
204
- schemaVersion: SCHEMA_VERSION,
205
- savedAt: Date.now()
206
- };
207
- const snapshotStartTime = Date.now();
208
- yield* Effect.catchAll(coldStorage.save(documentId, storedDocument), (e) => Effect.logError("Failed to save snapshot", {
209
- documentId,
210
- error: e
211
- }));
212
- const snapshotDuration = Date.now() - snapshotStartTime;
213
- yield* Metric.increment(storageSnapshots);
214
- yield* Metric.update(storageSnapshotLatency, snapshotDuration);
215
- yield* Effect.catchAll(hotStorage.truncate(documentId, version), (e) => Effect.logError("Failed to truncate WAL", {
150
+ yield* Effect.addFinalizer(() => Effect.fn("cluster.entity.finalize")(function* () {
151
+ yield* Effect.catchAll(instance.saveSnapshot(), (e) => Effect.logError("Failed to save snapshot during entity finalization", {
216
152
  documentId,
217
153
  error: e
218
154
  }));
219
- yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, {
220
- lastSnapshotVersion: version,
221
- lastSnapshotTime: Date.now(),
222
- transactionsSinceSnapshot: 0
223
- }));
224
- });
225
- /**
226
- * Check if snapshot should be triggered
227
- */
228
- const checkSnapshotTriggers = Effect.gen(function* () {
229
- const state = yield* Ref.get(stateRef);
230
- const now = Date.now();
231
- const intervalMs = Duration.toMillis(config.snapshot.interval);
232
- const threshold = config.snapshot.transactionThreshold;
233
- if (state.transactionsSinceSnapshot >= threshold) {
234
- yield* saveSnapshot;
235
- return;
236
- }
237
- if (now - state.lastSnapshotTime >= intervalMs) {
238
- yield* saveSnapshot;
239
- return;
240
- }
241
- });
242
- yield* Effect.addFinalizer(() => Effect.gen(function* () {
243
- yield* saveSnapshot;
244
155
  yield* Metric.incrementBy(documentsActive, -1);
245
156
  yield* Metric.increment(documentsEvicted);
246
157
  yield* Effect.logDebug("Entity finalized", { documentId });
247
- }));
158
+ })());
159
+ if (Duration.toMillis(config.snapshot.idleTimeout) > 0) yield* Effect.fn("cluster.entity.snapshot.loop")(function* () {
160
+ if (yield* instance.needsSnapshot()) {
161
+ yield* Effect.catchAll(instance.saveSnapshot(), (e) => Effect.logWarning("Periodic snapshot failed in cluster entity", {
162
+ documentId,
163
+ error: e
164
+ }));
165
+ yield* Metric.increment(storageIdleSnapshots);
166
+ }
167
+ })().pipe(Effect.repeat(Schedule.spaced(config.snapshot.idleTimeout)), Effect.fork);
248
168
  return {
249
- Submit: Effect.fnUntraced(function* ({ payload }) {
250
- const submitStartTime = Date.now();
251
- const state = yield* Ref.get(stateRef);
169
+ Submit: Effect.fn("cluster.document.transaction.submit")(function* ({ payload }) {
252
170
  const transaction = decodeTransaction(payload.transaction);
253
- const result = state.document.submit(transaction);
254
- const latency = Date.now() - submitStartTime;
255
- yield* Metric.update(transactionsLatency, latency);
256
- if (result.success) {
257
- yield* Metric.increment(transactionsProcessed);
258
- const walEntry = {
259
- transaction,
260
- version: result.version,
261
- timestamp: Date.now()
262
- };
263
- yield* Effect.catchAll(hotStorage.append(documentId, walEntry), (e) => Effect.logError("Failed to append to WAL", {
264
- documentId,
265
- error: e
266
- }));
267
- yield* Metric.increment(storageWalAppends);
268
- yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, { transactionsSinceSnapshot: s.transactionsSinceSnapshot + 1 }));
269
- yield* checkSnapshotTriggers;
270
- } else yield* Metric.increment(transactionsRejected);
271
- return result;
171
+ return yield* instance.submit(transaction).pipe(Effect.catchAll((error) => Effect.succeed({
172
+ success: false,
173
+ reason: `Storage error: ${String(error)}`
174
+ })));
272
175
  }),
273
- GetSnapshot: Effect.fnUntraced(function* () {
274
- return (yield* Ref.get(stateRef)).document.getSnapshot();
176
+ GetSnapshot: Effect.fn("cluster.document.snapshot.get")(function* () {
177
+ return instance.getSnapshot();
275
178
  }),
276
- Touch: Effect.fnUntraced(function* () {}),
277
- SetPresence: Effect.fnUntraced(function* ({ payload }) {
179
+ Touch: Effect.fn("cluster.document.touch")(function* () {
180
+ yield* instance.touch();
181
+ }),
182
+ SetPresence: Effect.fn("cluster.presence.set")(function* ({ payload }) {
278
183
  const { connectionId, entry } = payload;
279
184
  yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, { presences: HashMap.set(s.presences, connectionId, entry) }));
280
185
  yield* Metric.increment(presenceUpdates);
@@ -288,7 +193,7 @@ const createEntityHandler = (config, coldStorage, hotStorage) => Effect.gen(func
288
193
  };
289
194
  yield* PubSub.publish(state.presencePubSub, event);
290
195
  }),
291
- RemovePresence: Effect.fnUntraced(function* ({ payload }) {
196
+ RemovePresence: Effect.fn("cluster.presence.remove")(function* ({ payload }) {
292
197
  const { connectionId } = payload;
293
198
  const state = yield* Ref.get(stateRef);
294
199
  if (!HashMap.has(state.presences, connectionId)) return;
@@ -300,20 +205,20 @@ const createEntityHandler = (config, coldStorage, hotStorage) => Effect.gen(func
300
205
  };
301
206
  yield* PubSub.publish(state.presencePubSub, event);
302
207
  }),
303
- GetPresenceSnapshot: Effect.fnUntraced(function* () {
208
+ GetPresenceSnapshot: Effect.fn("cluster.presence.snapshot.get")(function* () {
304
209
  const state = yield* Ref.get(stateRef);
305
210
  const presences = {};
306
211
  for (const [id, entry] of state.presences) presences[id] = entry;
307
212
  return { presences };
308
213
  })
309
214
  };
310
- });
215
+ })();
311
216
  var SubscriptionStoreTag = class extends Context.Tag("@voidhash/mimic-effect/SubscriptionStore")() {};
312
- const subscriptionStoreLayer = Layer.effect(SubscriptionStoreTag, Effect.gen(function* () {
217
+ const subscriptionStoreLayer = Layer.effect(SubscriptionStoreTag, Effect.fn("cluster.subscriptions.layer.create")(function* () {
313
218
  const documentPubSubs = yield* Ref.make(HashMap.empty());
314
219
  const presencePubSubs = yield* Ref.make(HashMap.empty());
315
220
  return {
316
- getOrCreatePubSub: (documentId) => Effect.gen(function* () {
221
+ getOrCreatePubSub: Effect.fn("cluster.subscriptions.pubsub.get-or-create")(function* (documentId) {
317
222
  const current = yield* Ref.get(documentPubSubs);
318
223
  const existing = HashMap.get(current, documentId);
319
224
  if (existing._tag === "Some") return existing.value;
@@ -321,7 +226,7 @@ const subscriptionStoreLayer = Layer.effect(SubscriptionStoreTag, Effect.gen(fun
321
226
  yield* Ref.update(documentPubSubs, (map) => HashMap.set(map, documentId, pubsub));
322
227
  return pubsub;
323
228
  }),
324
- getOrCreatePresencePubSub: (documentId) => Effect.gen(function* () {
229
+ getOrCreatePresencePubSub: Effect.fn("cluster.subscriptions.presence-pubsub.get-or-create")(function* (documentId) {
325
230
  const current = yield* Ref.get(presencePubSubs);
326
231
  const existing = HashMap.get(current, documentId);
327
232
  if (existing._tag === "Some") return existing.value;
@@ -330,7 +235,7 @@ const subscriptionStoreLayer = Layer.effect(SubscriptionStoreTag, Effect.gen(fun
330
235
  return pubsub;
331
236
  })
332
237
  };
333
- }));
238
+ })());
334
239
  /**
335
240
  * Create a MimicClusterServerEngine layer.
336
241
  *