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

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @voidhash/mimic-effect@1.0.0-beta.10 build /home/runner/work/mimic/mimic/packages/mimic-effect
2
+ > @voidhash/mimic-effect@1.0.0-beta.11 build /home/runner/work/mimic/mimic/packages/mimic-effect
3
3
  > tsdown
4
4
 
5
5
  ℹ tsdown v0.18.2 powered by rolldown v1.0.0-beta.55
@@ -10,16 +10,16 @@
10
10
  ℹ Build start
11
11
  ℹ [CJS] dist/index.cjs  2.28 kB │ gzip: 0.49 kB
12
12
  ℹ [CJS] dist/testing/index.cjs  0.95 kB │ gzip: 0.22 kB
13
- ℹ [CJS] dist/testing/HotStorageTestSuite.cjs 38.03 kB │ gzip: 5.13 kB
13
+ ℹ [CJS] dist/testing/HotStorageTestSuite.cjs 40.13 kB │ gzip: 5.31 kB
14
14
  ℹ [CJS] dist/testing/StorageIntegrationTestSuite.cjs 20.68 kB │ gzip: 3.67 kB
15
15
  ℹ [CJS] dist/testing/ColdStorageTestSuite.cjs 18.14 kB │ gzip: 3.22 kB
16
16
  ℹ [CJS] dist/MimicClusterServerEngine.cjs 15.02 kB │ gzip: 3.53 kB
17
- ℹ [CJS] dist/DocumentInstance.cjs 10.46 kB │ gzip: 2.74 kB
17
+ ℹ [CJS] dist/DocumentInstance.cjs 10.55 kB │ gzip: 2.75 kB
18
18
  ℹ [CJS] dist/MimicServer.cjs 10.37 kB │ gzip: 2.84 kB
19
19
  ℹ [CJS] dist/MimicServerEngine.cjs  8.30 kB │ gzip: 2.21 kB
20
20
  ℹ [CJS] dist/testing/FailingStorage.cjs  6.91 kB │ gzip: 1.53 kB
21
21
  ℹ [CJS] dist/Metrics.cjs  4.47 kB │ gzip: 1.08 kB
22
- ℹ [CJS] dist/HotStorage.cjs  3.86 kB │ gzip: 1.27 kB
22
+ ℹ [CJS] dist/HotStorage.cjs  4.06 kB │ gzip: 1.31 kB
23
23
  ℹ [CJS] dist/PresenceManager.cjs  3.81 kB │ gzip: 1.03 kB
24
24
  ℹ [CJS] dist/Protocol.cjs  3.78 kB │ gzip: 1.14 kB
25
25
  ℹ [CJS] dist/testing/assertions.cjs  3.74 kB │ gzip: 1.16 kB
@@ -33,15 +33,15 @@
33
33
  ℹ [CJS] dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.cjs  0.37 kB │ gzip: 0.23 kB
34
34
  ℹ [CJS] dist/_virtual/rolldown_runtime.cjs  0.36 kB │ gzip: 0.25 kB
35
35
  ℹ [CJS] dist/testing/types.cjs  0.34 kB │ gzip: 0.23 kB
36
- ℹ [CJS] 25 files, total: 160.61 kB
36
+ ℹ [CJS] 25 files, total: 162.99 kB
37
37
  ℹ [CJS] dist/Protocol.d.cts.map 1.57 kB │ gzip: 0.70 kB
38
38
  ℹ [CJS] dist/Types.d.cts.map 1.44 kB │ gzip: 0.66 kB
39
39
  ℹ [CJS] dist/DocumentInstance.d.cts.map 1.26 kB │ gzip: 0.57 kB
40
40
  ℹ [CJS] dist/MimicServerEngine.d.cts.map 0.97 kB │ gzip: 0.49 kB
41
41
  ℹ [CJS] dist/ColdStorage.d.cts.map 0.70 kB │ gzip: 0.37 kB
42
- ℹ [CJS] dist/HotStorage.d.cts.map 0.67 kB │ gzip: 0.35 kB
43
42
  ℹ [CJS] dist/MimicAuthService.d.cts.map 0.61 kB │ gzip: 0.35 kB
44
43
  ℹ [CJS] dist/Errors.d.cts.map 0.60 kB │ gzip: 0.32 kB
44
+ ℹ [CJS] dist/HotStorage.d.cts.map 0.55 kB │ gzip: 0.32 kB
45
45
  ℹ [CJS] dist/testing/HotStorageTestSuite.d.cts.map 0.48 kB │ gzip: 0.29 kB
46
46
  ℹ [CJS] dist/PresenceManager.d.cts.map 0.45 kB │ gzip: 0.27 kB
47
47
  ℹ [CJS] dist/testing/types.d.cts.map 0.38 kB │ gzip: 0.23 kB
@@ -57,9 +57,9 @@
57
57
  ℹ [CJS] dist/Types.d.cts 5.72 kB │ gzip: 1.61 kB
58
58
  ℹ [CJS] dist/Errors.d.cts 4.86 kB │ gzip: 1.02 kB
59
59
  ℹ [CJS] dist/MimicServerEngine.d.cts 3.60 kB │ gzip: 1.09 kB
60
+ ℹ [CJS] dist/HotStorage.d.cts 3.11 kB │ gzip: 1.23 kB
60
61
  ℹ [CJS] dist/DocumentInstance.d.cts 3.07 kB │ gzip: 1.00 kB
61
62
  ℹ [CJS] dist/testing/types.d.cts 2.76 kB │ gzip: 1.15 kB
62
- ℹ [CJS] dist/HotStorage.d.cts 2.72 kB │ gzip: 1.08 kB
63
63
  ℹ [CJS] dist/MimicAuthService.d.cts 2.70 kB │ gzip: 1.04 kB
64
64
  ℹ [CJS] dist/testing/HotStorageTestSuite.d.cts 1.93 kB │ gzip: 0.57 kB
65
65
  ℹ [CJS] dist/ColdStorage.d.cts 1.74 kB │ gzip: 0.70 kB
@@ -70,24 +70,24 @@
70
70
  ℹ [CJS] dist/testing/StorageIntegrationTestSuite.d.cts 1.43 kB │ gzip: 0.57 kB
71
71
  ℹ [CJS] dist/MimicClusterServerEngine.d.cts 0.82 kB │ gzip: 0.33 kB
72
72
  ℹ [CJS] dist/MimicServer.d.cts 0.74 kB │ gzip: 0.31 kB
73
- ℹ [CJS] 36 files, total: 57.85 kB
74
- ✔ Build complete in 5947ms
73
+ ℹ [CJS] 36 files, total: 58.13 kB
74
+ ✔ Build complete in 7202ms
75
75
  ℹ [ESM] dist/index.mjs  1.29 kB │ gzip: 0.34 kB
76
76
  ℹ [ESM] dist/testing/index.mjs  0.60 kB │ gzip: 0.19 kB
77
- ℹ [ESM] dist/testing/HotStorageTestSuite.mjs.map 63.42 kB │ gzip: 8.61 kB
77
+ ℹ [ESM] dist/testing/HotStorageTestSuite.mjs.map 67.12 kB │ gzip: 9.05 kB
78
+ ℹ [ESM] dist/testing/HotStorageTestSuite.mjs 36.09 kB │ gzip: 5.26 kB
78
79
  ℹ [ESM] dist/testing/StorageIntegrationTestSuite.mjs.map 35.60 kB │ gzip: 6.44 kB
79
- ℹ [ESM] dist/testing/HotStorageTestSuite.mjs 34.21 kB │ gzip: 5.06 kB
80
80
  ℹ [ESM] dist/testing/ColdStorageTestSuite.mjs.map 31.92 kB │ gzip: 5.21 kB
81
81
  ℹ [ESM] dist/MimicClusterServerEngine.mjs.map 28.95 kB │ gzip: 6.88 kB
82
- ℹ [ESM] dist/DocumentInstance.mjs.map 23.70 kB │ gzip: 6.13 kB
82
+ ℹ [ESM] dist/DocumentInstance.mjs.map 23.98 kB │ gzip: 6.20 kB
83
83
  ℹ [ESM] dist/MimicServer.mjs.map 21.35 kB │ gzip: 5.64 kB
84
84
  ℹ [ESM] dist/MimicServerEngine.mjs.map 19.25 kB │ gzip: 4.87 kB
85
85
  ℹ [ESM] dist/testing/StorageIntegrationTestSuite.mjs 18.49 kB │ gzip: 3.63 kB
86
86
  ℹ [ESM] dist/testing/ColdStorageTestSuite.mjs 16.50 kB │ gzip: 3.21 kB
87
87
  ℹ [ESM] dist/testing/FailingStorage.mjs.map 15.60 kB │ gzip: 3.07 kB
88
88
  ℹ [ESM] dist/MimicClusterServerEngine.mjs 13.93 kB │ gzip: 3.55 kB
89
- ℹ [ESM] dist/HotStorage.mjs.map 10.81 kB │ gzip: 3.04 kB
90
- ℹ [ESM] dist/DocumentInstance.mjs 10.06 kB │ gzip: 2.75 kB
89
+ ℹ [ESM] dist/HotStorage.mjs.map 11.89 kB │ gzip: 3.36 kB
90
+ ℹ [ESM] dist/DocumentInstance.mjs 10.15 kB │ gzip: 2.76 kB
91
91
  ℹ [ESM] dist/MimicServer.mjs  9.90 kB │ gzip: 2.88 kB
92
92
  ℹ [ESM] dist/PresenceManager.mjs.map  9.38 kB │ gzip: 2.38 kB
93
93
  ℹ [ESM] dist/Protocol.mjs.map  9.18 kB │ gzip: 2.15 kB
@@ -98,8 +98,8 @@
98
98
  ℹ [ESM] dist/MimicAuthService.mjs.map  6.09 kB │ gzip: 1.85 kB
99
99
  ℹ [ESM] dist/ColdStorage.mjs.map  5.35 kB │ gzip: 1.64 kB
100
100
  ℹ [ESM] dist/Errors.mjs.map  4.00 kB │ gzip: 1.05 kB
101
+ ℹ [ESM] dist/HotStorage.mjs  3.92 kB │ gzip: 1.32 kB
101
102
  ℹ [ESM] dist/Metrics.mjs  3.81 kB │ gzip: 1.05 kB
102
- ℹ [ESM] dist/HotStorage.mjs  3.73 kB │ gzip: 1.27 kB
103
103
  ℹ [ESM] dist/PresenceManager.mjs  3.48 kB │ gzip: 1.04 kB
104
104
  ℹ [ESM] dist/testing/assertions.mjs  3.46 kB │ gzip: 1.15 kB
105
105
  ℹ [ESM] dist/Protocol.mjs  3.35 kB │ gzip: 1.06 kB
@@ -113,9 +113,9 @@
113
113
  ℹ [ESM] dist/MimicServerEngine.d.mts.map  0.97 kB │ gzip: 0.49 kB
114
114
  ℹ [ESM] dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs  0.90 kB │ gzip: 0.42 kB
115
115
  ℹ [ESM] dist/ColdStorage.d.mts.map  0.70 kB │ gzip: 0.37 kB
116
- ℹ [ESM] dist/HotStorage.d.mts.map  0.67 kB │ gzip: 0.35 kB
117
116
  ℹ [ESM] dist/MimicAuthService.d.mts.map  0.61 kB │ gzip: 0.35 kB
118
117
  ℹ [ESM] dist/Errors.d.mts.map  0.60 kB │ gzip: 0.32 kB
118
+ ℹ [ESM] dist/HotStorage.d.mts.map  0.55 kB │ gzip: 0.32 kB
119
119
  ℹ [ESM] dist/testing/HotStorageTestSuite.d.mts.map  0.48 kB │ gzip: 0.29 kB
120
120
  ℹ [ESM] dist/_virtual/rolldown_runtime.mjs  0.47 kB │ gzip: 0.30 kB
121
121
  ℹ [ESM] dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.mjs  0.47 kB │ gzip: 0.30 kB
@@ -137,9 +137,9 @@
137
137
  ℹ [ESM] dist/Types.d.mts  5.72 kB │ gzip: 1.61 kB
138
138
  ℹ [ESM] dist/Errors.d.mts  4.86 kB │ gzip: 1.02 kB
139
139
  ℹ [ESM] dist/MimicServerEngine.d.mts  3.60 kB │ gzip: 1.09 kB
140
+ ℹ [ESM] dist/HotStorage.d.mts  3.11 kB │ gzip: 1.23 kB
140
141
  ℹ [ESM] dist/DocumentInstance.d.mts  3.07 kB │ gzip: 1.00 kB
141
142
  ℹ [ESM] dist/testing/types.d.mts  2.76 kB │ gzip: 1.15 kB
142
- ℹ [ESM] dist/HotStorage.d.mts  2.72 kB │ gzip: 1.08 kB
143
143
  ℹ [ESM] dist/MimicAuthService.d.mts  2.70 kB │ gzip: 1.04 kB
144
144
  ℹ [ESM] dist/testing/HotStorageTestSuite.d.mts  1.93 kB │ gzip: 0.57 kB
145
145
  ℹ [ESM] dist/ColdStorage.d.mts  1.74 kB │ gzip: 0.70 kB
@@ -150,5 +150,5 @@
150
150
  ℹ [ESM] dist/testing/StorageIntegrationTestSuite.d.mts  1.43 kB │ gzip: 0.57 kB
151
151
  ℹ [ESM] dist/MimicClusterServerEngine.d.mts  0.82 kB │ gzip: 0.33 kB
152
152
  ℹ [ESM] dist/MimicServer.d.mts  0.74 kB │ gzip: 0.31 kB
153
- ℹ [ESM] 78 files, total: 505.69 kB
154
- ✔ Build complete in 5966ms
153
+ ℹ [ESM] 78 files, total: 513.20 kB
154
+ ✔ Build complete in 7224ms
@@ -126,7 +126,8 @@ const make = (documentId, config, coldStorage, hotStorage) => effect.Effect.gen(
126
126
  version: validation.nextVersion,
127
127
  timestamp: Date.now()
128
128
  };
129
- const appendResult = yield* effect.Effect.either(hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion));
129
+ const snapshotVersion = yield* effect.Ref.get(lastSnapshotVersionRef);
130
+ const appendResult = yield* effect.Effect.either(hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion, snapshotVersion));
130
131
  if (appendResult._tag === "Left") {
131
132
  yield* effect.Effect.logError("WAL append failed", {
132
133
  documentId,
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentInstance.d.cts","names":[],"sources":["../src/DocumentInstance.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;AAkDA;AASiB,KA/BL,YAAA,GA+BqB;EAAiB,SAAU,OAAA,EAAA,IAAA;EAET,SAAA,OAAA,EAAA,MAAA;CAA9B,GAAA;EAEY,SAAA,OAAA,EAAA,KAAA;EAAd,SAAO,MAAA,EAAA,MAAA;CAEoB;;;;AAE2C,UAhCxE,sBAgCwE,CAAA,gBAhCjC,SAAA,CAAU,YAgCuB,CAAA,CAAA;EAAmB,SAAA,MAAA,EA/BzF,OA+ByF;EAA/C,SAAO,OAAA,CAAA,EA7B9D,SAAA,CAAU,aA6BoD,CA7BtC,OA6BsC,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA;IAEjB,UAAA,EAAA,MAAA;EAAmB,CAAA,EAAA,GA9B9B,MAAA,CAAO,MA8BuB,CA9BhB,SAAA,CAAU,aA8BM,CA9BQ,OA8BR,CAAA,CAAA,CAAA;EAAvC,SAAO,qBAAA,EAAA,MAAA;EAEsB,SAAA,QAAA,EAAA;IAAmB,SAAA,QAAA,EA7BxD,QAAA,CAAS,QA6B+C;IAAhC,SAAA,oBAAA,EAAA,MAAA;EAEvB,CAAA;;;;AAmcxB;AA1aqC,UAhDpB,gBAAA,CAgD8B;EAEd,SAAA,mBAAA,EAAA,MAAA;EAAvB,SAAA,gBAAA,EAAA,MAAA;EACK,SAAA,yBAAA,EAAA,MAAA;;;;;AAEgD,UA5C9C,gBA4C8C,CAAA,gBA5Cb,SAAA,CAAU,YA4CG,CAAA,CAAA;EAA5D;EAAa,SAAA,QAAA,EA1CK,cAAA,CAAe,cA0CpB,CA1CmC,OA0CnC,CAAA;;mBAxCG,MAAA,CAAO,OAAO;;gCAED,MAAA,CAAO,OAAO;;iCAEb,WAAA,CAAY,gBAAgB,MAAA,CAAO,OAAO,cAAc,mBAAmB;;+BAE7E,MAAA,CAAO,aAAa,mBAAmB;;wCAE9B,MAAA,CAAO,aAAa,mBAAmB;;wBAEvD,MAAA,CAAO;;;;;;;;;gCAMC,MAAA,CAAO;;sCAED,MAAA,CAAO;;cA2bhC;yBA1awB,SAAA,CAAU,0CAErC,uBAAuB,uBAClB,yBACD,eACX,MAAA,CAAO,OAAO,iBAAiB,UAAU,mBAAmB"}
1
+ {"version":3,"file":"DocumentInstance.d.cts","names":[],"sources":["../src/DocumentInstance.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;AAkDA;AASiB,KA/BL,YAAA,GA+BqB;EAAiB,SAAU,OAAA,EAAA,IAAA;EAET,SAAA,OAAA,EAAA,MAAA;CAA9B,GAAA;EAEY,SAAA,OAAA,EAAA,KAAA;EAAd,SAAO,MAAA,EAAA,MAAA;CAEoB;;;;AAE2C,UAhCxE,sBAgCwE,CAAA,gBAhCjC,SAAA,CAAU,YAgCuB,CAAA,CAAA;EAAmB,SAAA,MAAA,EA/BzF,OA+ByF;EAA/C,SAAO,OAAA,CAAA,EA7B9D,SAAA,CAAU,aA6BoD,CA7BtC,OA6BsC,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA;IAEjB,UAAA,EAAA,MAAA;EAAmB,CAAA,EAAA,GA9B9B,MAAA,CAAO,MA8BuB,CA9BhB,SAAA,CAAU,aA8BM,CA9BQ,OA8BR,CAAA,CAAA,CAAA;EAAvC,SAAO,qBAAA,EAAA,MAAA;EAEsB,SAAA,QAAA,EAAA;IAAmB,SAAA,QAAA,EA7BxD,QAAA,CAAS,QA6B+C;IAAhC,SAAA,oBAAA,EAAA,MAAA;EAEvB,CAAA;;;;AAucxB;AA9aqC,UAhDpB,gBAAA,CAgD8B;EAEd,SAAA,mBAAA,EAAA,MAAA;EAAvB,SAAA,gBAAA,EAAA,MAAA;EACK,SAAA,yBAAA,EAAA,MAAA;;;;;AAEgD,UA5C9C,gBA4C8C,CAAA,gBA5Cb,SAAA,CAAU,YA4CG,CAAA,CAAA;EAA5D;EAAa,SAAA,QAAA,EA1CK,cAAA,CAAe,cA0CpB,CA1CmC,OA0CnC,CAAA;;mBAxCG,MAAA,CAAO,OAAO;;gCAED,MAAA,CAAO,OAAO;;iCAEb,WAAA,CAAY,gBAAgB,MAAA,CAAO,OAAO,cAAc,mBAAmB;;+BAE7E,MAAA,CAAO,aAAa,mBAAmB;;wCAE9B,MAAA,CAAO,aAAa,mBAAmB;;wBAEvD,MAAA,CAAO;;;;;;;;;gCAMC,MAAA,CAAO;;sCAED,MAAA,CAAO;;cA+bhC;yBA9awB,SAAA,CAAU,0CAErC,uBAAuB,uBAClB,yBACD,eACX,MAAA,CAAO,OAAO,iBAAiB,UAAU,mBAAmB"}
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentInstance.d.mts","names":[],"sources":["../src/DocumentInstance.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;AAkDA;AASiB,KA/BL,YAAA,GA+BqB;EAAiB,SAAU,OAAA,EAAA,IAAA;EAET,SAAA,OAAA,EAAA,MAAA;CAA9B,GAAA;EAEY,SAAA,OAAA,EAAA,KAAA;EAAd,SAAO,MAAA,EAAA,MAAA;CAEoB;;;;AAE2C,UAhCxE,sBAgCwE,CAAA,gBAhCjC,SAAA,CAAU,YAgCuB,CAAA,CAAA;EAAmB,SAAA,MAAA,EA/BzF,OA+ByF;EAA/C,SAAO,OAAA,CAAA,EA7B9D,SAAA,CAAU,aA6BoD,CA7BtC,OA6BsC,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA;IAEjB,UAAA,EAAA,MAAA;EAAmB,CAAA,EAAA,GA9B9B,MAAA,CAAO,MA8BuB,CA9BhB,SAAA,CAAU,aA8BM,CA9BQ,OA8BR,CAAA,CAAA,CAAA;EAAvC,SAAO,qBAAA,EAAA,MAAA;EAEsB,SAAA,QAAA,EAAA;IAAmB,SAAA,QAAA,EA7BxD,QAAA,CAAS,QA6B+C;IAAhC,SAAA,oBAAA,EAAA,MAAA;EAEvB,CAAA;;;;AAmcxB;AA1aqC,UAhDpB,gBAAA,CAgD8B;EAEd,SAAA,mBAAA,EAAA,MAAA;EAAvB,SAAA,gBAAA,EAAA,MAAA;EACK,SAAA,yBAAA,EAAA,MAAA;;;;;AAEgD,UA5C9C,gBA4C8C,CAAA,gBA5Cb,SAAA,CAAU,YA4CG,CAAA,CAAA;EAA5D;EAAa,SAAA,QAAA,EA1CK,cAAA,CAAe,cA0CpB,CA1CmC,OA0CnC,CAAA;;mBAxCG,MAAA,CAAO,OAAO;;gCAED,MAAA,CAAO,OAAO;;iCAEb,WAAA,CAAY,gBAAgB,MAAA,CAAO,OAAO,cAAc,mBAAmB;;+BAE7E,MAAA,CAAO,aAAa,mBAAmB;;wCAE9B,MAAA,CAAO,aAAa,mBAAmB;;wBAEvD,MAAA,CAAO;;;;;;;;;gCAMC,MAAA,CAAO;;sCAED,MAAA,CAAO;;cA2bhC;yBA1awB,SAAA,CAAU,0CAErC,uBAAuB,uBAClB,yBACD,eACX,MAAA,CAAO,OAAO,iBAAiB,UAAU,mBAAmB"}
1
+ {"version":3,"file":"DocumentInstance.d.mts","names":[],"sources":["../src/DocumentInstance.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;AAkDA;AASiB,KA/BL,YAAA,GA+BqB;EAAiB,SAAU,OAAA,EAAA,IAAA;EAET,SAAA,OAAA,EAAA,MAAA;CAA9B,GAAA;EAEY,SAAA,OAAA,EAAA,KAAA;EAAd,SAAO,MAAA,EAAA,MAAA;CAEoB;;;;AAE2C,UAhCxE,sBAgCwE,CAAA,gBAhCjC,SAAA,CAAU,YAgCuB,CAAA,CAAA;EAAmB,SAAA,MAAA,EA/BzF,OA+ByF;EAA/C,SAAO,OAAA,CAAA,EA7B9D,SAAA,CAAU,aA6BoD,CA7BtC,OA6BsC,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA;IAEjB,UAAA,EAAA,MAAA;EAAmB,CAAA,EAAA,GA9B9B,MAAA,CAAO,MA8BuB,CA9BhB,SAAA,CAAU,aA8BM,CA9BQ,OA8BR,CAAA,CAAA,CAAA;EAAvC,SAAO,qBAAA,EAAA,MAAA;EAEsB,SAAA,QAAA,EAAA;IAAmB,SAAA,QAAA,EA7BxD,QAAA,CAAS,QA6B+C;IAAhC,SAAA,oBAAA,EAAA,MAAA;EAEvB,CAAA;;;;AAucxB;AA9aqC,UAhDpB,gBAAA,CAgD8B;EAEd,SAAA,mBAAA,EAAA,MAAA;EAAvB,SAAA,gBAAA,EAAA,MAAA;EACK,SAAA,yBAAA,EAAA,MAAA;;;;;AAEgD,UA5C9C,gBA4C8C,CAAA,gBA5Cb,SAAA,CAAU,YA4CG,CAAA,CAAA;EAA5D;EAAa,SAAA,QAAA,EA1CK,cAAA,CAAe,cA0CpB,CA1CmC,OA0CnC,CAAA;;mBAxCG,MAAA,CAAO,OAAO;;gCAED,MAAA,CAAO,OAAO;;iCAEb,WAAA,CAAY,gBAAgB,MAAA,CAAO,OAAO,cAAc,mBAAmB;;+BAE7E,MAAA,CAAO,aAAa,mBAAmB;;wCAE9B,MAAA,CAAO,aAAa,mBAAmB;;wBAEvD,MAAA,CAAO;;;;;;;;;gCAMC,MAAA,CAAO;;sCAED,MAAA,CAAO;;cA+bhC;yBA9awB,SAAA,CAAU,0CAErC,uBAAuB,uBAClB,yBACD,eACX,MAAA,CAAO,OAAO,iBAAiB,UAAU,mBAAmB"}
@@ -126,7 +126,8 @@ const make = (documentId, config, coldStorage, hotStorage) => Effect.gen(functio
126
126
  version: validation.nextVersion,
127
127
  timestamp: Date.now()
128
128
  };
129
- const appendResult = yield* Effect.either(hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion));
129
+ const snapshotVersion = yield* Ref.get(lastSnapshotVersionRef);
130
+ const appendResult = yield* Effect.either(hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion, snapshotVersion));
130
131
  if (appendResult._tag === "Left") {
131
132
  yield* Effect.logError("WAL append failed", {
132
133
  documentId,
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentInstance.mjs","names":["initialState: Primitive.InferState<TSchema> | undefined","initial: Primitive.InferSetInput<TSchema> | undefined","Metrics.documentsRestored","Metrics.documentsCreated","Metrics.documentsActive","walEntries","storedDoc","Metrics.storageSnapshots","Metrics.storageSnapshotLatency","Metrics.transactionsRejected","latency","Metrics.transactionsLatency","walEntry: WalEntry","Metrics.walAppendFailures","Metrics.transactionsProcessed","Metrics.storageWalAppends","Metrics.storageVersionGaps","snapshotState: Primitive.InferState<TSchema> | undefined"],"sources":["../src/DocumentInstance.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - DocumentInstance\n *\n * Manages the lifecycle of a single document including:\n * - Restoration from storage (cold storage + WAL replay)\n * - Transaction submission with WAL persistence\n * - Snapshot saving and trigger checking\n *\n * Used by both MimicServerEngine (single-node) and MimicClusterServerEngine (clustered).\n */\nimport { Duration, Effect, Metric, PubSub, Ref } from \"effect\";\nimport { Document, type Primitive, type Transaction } from \"@voidhash/mimic\";\nimport { ServerDocument } from \"@voidhash/mimic/server\";\nimport type { StoredDocument, WalEntry } from \"./Types\";\nimport type { ServerBroadcast } from \"./Protocol\";\nimport type { ColdStorage } from \"./ColdStorage\";\nimport type { HotStorage } from \"./HotStorage\";\nimport type { ColdStorageError } from \"./Errors\";\nimport type { HotStorageError } from \"./Errors\";\nimport * as Metrics from \"./Metrics\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Result of submitting a transaction\n */\nexport type SubmitResult =\n | { readonly success: true; readonly version: number }\n | { readonly success: false; readonly reason: string };\n\n/**\n * Configuration for a DocumentInstance\n */\nexport interface DocumentInstanceConfig<TSchema extends Primitive.AnyPrimitive> {\n readonly schema: TSchema;\n readonly initial?:\n | Primitive.InferSetInput<TSchema>\n | ((ctx: { documentId: string }) => Effect.Effect<Primitive.InferSetInput<TSchema>>);\n readonly maxTransactionHistory: number;\n readonly snapshot: {\n readonly interval: Duration.Duration;\n readonly transactionThreshold: number;\n };\n}\n\n/**\n * Snapshot tracking state\n */\nexport interface SnapshotTracking {\n readonly lastSnapshotVersion: number;\n readonly lastSnapshotTime: number;\n readonly transactionsSinceSnapshot: number;\n}\n\n/**\n * A DocumentInstance manages a single document's lifecycle\n */\nexport interface DocumentInstance<TSchema extends Primitive.AnyPrimitive> {\n /** The underlying ServerDocument */\n readonly document: ServerDocument.ServerDocument<TSchema>;\n /** PubSub for broadcasting messages to subscribers */\n readonly pubsub: PubSub.PubSub<ServerBroadcast>;\n /** Current snapshot tracking state */\n readonly getSnapshotTracking: Effect.Effect<SnapshotTracking>;\n /** Submit a transaction */\n readonly submit: (transaction: Transaction.Transaction) => Effect.Effect<SubmitResult, ColdStorageError | HotStorageError>;\n /** Save a snapshot to cold storage */\n readonly saveSnapshot: () => Effect.Effect<void, ColdStorageError | HotStorageError>;\n /** Check if snapshot should be triggered and save if needed */\n readonly checkSnapshotTriggers: () => Effect.Effect<void, ColdStorageError | HotStorageError>;\n /** Update last activity time (for external tracking) */\n readonly touch: () => Effect.Effect<void>;\n /** Get current document version */\n readonly getVersion: () => number;\n /** Get document snapshot */\n readonly getSnapshot: () => { state: unknown; version: number };\n /** Check if document has unsnapshot transactions that need persisting */\n readonly needsSnapshot: () => Effect.Effect<boolean>;\n /** Get the last activity timestamp for idle detection */\n readonly getLastActivityTime: () => Effect.Effect<number>;\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a DocumentInstance for a single document.\n *\n * This handles:\n * - Loading from cold storage or computing initial state\n * - Persisting initial state immediately (crash safety)\n * - Replaying WAL entries\n * - Transaction submission with WAL persistence\n * - Snapshot saving\n */\nexport const make = <TSchema extends Primitive.AnyPrimitive>(\n documentId: string,\n config: DocumentInstanceConfig<TSchema>,\n coldStorage: ColdStorage,\n hotStorage: HotStorage\n): Effect.Effect<DocumentInstance<TSchema>, ColdStorageError | HotStorageError> =>\n Effect.gen(function* () {\n // Current schema version (hard-coded to 1 for now)\n const SCHEMA_VERSION = 1;\n\n // 1. Load snapshot from ColdStorage\n const storedDoc = yield* coldStorage.load(documentId);\n\n // Track initial values - only one will be set:\n // - initialState: raw state from storage (already in internal format)\n // - initial: computed from config (needs conversion to state format)\n let initialState: Primitive.InferState<TSchema> | undefined;\n let initial: Primitive.InferSetInput<TSchema> | undefined;\n let initialVersion = 0;\n\n if (storedDoc) {\n // Loading from storage - state is already in internal format\n initialState = storedDoc.state as Primitive.InferState<TSchema>;\n initialVersion = storedDoc.version;\n } else {\n // New document - compute initial value (set input format)\n initial = yield* computeInitialState(config, documentId);\n }\n\n // 2. Create PubSub for broadcasting\n const pubsub = yield* PubSub.unbounded<ServerBroadcast>();\n\n // 3. Create refs for tracking\n const lastSnapshotVersionRef = yield* Ref.make(initialVersion);\n const lastSnapshotTimeRef = yield* Ref.make(Date.now());\n const transactionsSinceSnapshotRef = yield* Ref.make(0);\n const lastActivityTimeRef = yield* Ref.make(Date.now());\n\n // 4. Create ServerDocument with callbacks\n const document = ServerDocument.make({\n schema: config.schema,\n initial,\n initialState,\n initialVersion,\n maxTransactionHistory: config.maxTransactionHistory,\n onBroadcast: (message: ServerDocument.TransactionMessage) => {\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"transaction\",\n transaction: message.transaction,\n version: message.version,\n })\n );\n },\n onRejection: (transactionId: string, reason: string) => {\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"error\",\n transactionId,\n reason,\n })\n );\n },\n });\n\n // 5. If this is a new document, immediately save to cold storage\n // This ensures the initial state is durable before any transactions are accepted.\n if (!storedDoc) {\n const initialStoredDoc = createStoredDocument(document.get(), 0, SCHEMA_VERSION);\n yield* coldStorage.save(documentId, initialStoredDoc);\n yield* Effect.logDebug(\"Initial state persisted to cold storage\", { documentId });\n }\n\n // 6. Load WAL entries\n const walEntries = yield* hotStorage.getEntries(documentId, initialVersion);\n\n // 7. Verify WAL continuity (warning only, non-blocking)\n yield* verifyWalContinuity(documentId, walEntries, initialVersion);\n\n // 8. Replay WAL entries\n yield* replayWalEntries(documentId, document, walEntries);\n\n // Track metrics\n if (storedDoc) {\n yield* Metric.increment(Metrics.documentsRestored);\n } else {\n yield* Metric.increment(Metrics.documentsCreated);\n }\n yield* Metric.incrementBy(Metrics.documentsActive, 1);\n\n // ==========================================================================\n // Instance Methods\n // ==========================================================================\n\n const getSnapshotTracking = Effect.gen(function* () {\n return {\n lastSnapshotVersion: yield* Ref.get(lastSnapshotVersionRef),\n lastSnapshotTime: yield* Ref.get(lastSnapshotTimeRef),\n transactionsSinceSnapshot: yield* Ref.get(transactionsSinceSnapshotRef),\n };\n });\n\n const saveSnapshot = Effect.fn(\"document.snapshot.save\")(function* () {\n const targetVersion = document.getVersion();\n const lastSnapshotVersion = yield* Ref.get(lastSnapshotVersionRef);\n\n // Idempotency check: skip if already snapshotted at this version\n if (targetVersion <= lastSnapshotVersion) {\n return;\n }\n\n const snapshotStartTime = Date.now();\n\n // Load base snapshot from cold storage\n const baseSnapshot = yield* coldStorage.load(documentId);\n const baseVersion = baseSnapshot?.version ?? 0;\n const baseState = baseSnapshot?.state as Primitive.InferState<TSchema> | undefined;\n\n // Load WAL entries from base to target\n const walEntries = yield* hotStorage.getEntries(documentId, baseVersion);\n\n // Compute snapshot state by replaying WAL on base\n const snapshotResult = computeSnapshotState(\n config.schema,\n baseState,\n walEntries,\n targetVersion\n );\n\n if (!snapshotResult) {\n return;\n }\n\n // Re-check before saving (in case another snapshot completed while we were working)\n const currentLastSnapshot = yield* Ref.get(lastSnapshotVersionRef);\n if (snapshotResult.version <= currentLastSnapshot) {\n return;\n }\n\n const storedDoc = createStoredDocument(\n snapshotResult.state,\n snapshotResult.version,\n SCHEMA_VERSION\n );\n\n // Save to ColdStorage\n yield* coldStorage.save(documentId, storedDoc);\n\n // Track snapshot metrics\n const snapshotDuration = Date.now() - snapshotStartTime;\n yield* Metric.increment(Metrics.storageSnapshots);\n yield* Metric.update(Metrics.storageSnapshotLatency, snapshotDuration);\n\n // Update tracking BEFORE truncate (for idempotency on retry)\n yield* Ref.set(lastSnapshotVersionRef, snapshotResult.version);\n yield* Ref.set(lastSnapshotTimeRef, Date.now());\n yield* Ref.set(transactionsSinceSnapshotRef, 0);\n\n // Truncate WAL - non-fatal, will be retried on next snapshot\n yield* Effect.catchAll(hotStorage.truncate(documentId, snapshotResult.version), (e) =>\n Effect.logWarning(\"WAL truncate failed - will retry on next snapshot\", {\n documentId,\n version: snapshotResult.version,\n error: e,\n })\n );\n });\n\n const checkSnapshotTriggers = Effect.fn(\"document.snapshot.check-triggers\")(function* () {\n const txCount = yield* Ref.get(transactionsSinceSnapshotRef);\n const lastTime = yield* Ref.get(lastSnapshotTimeRef);\n\n if (shouldTriggerSnapshot(txCount, lastTime, config.snapshot)) {\n yield* saveSnapshot();\n }\n });\n\n const submit = Effect.fn(\"document.transaction.submit\")(function* (\n transaction: Transaction.Transaction\n ) {\n const submitStartTime = Date.now();\n\n // Update activity time\n yield* Ref.set(lastActivityTimeRef, Date.now());\n\n // Phase 1: Validate (no side effects)\n const validation = document.validate(transaction);\n\n if (!validation.valid) {\n yield* Metric.increment(Metrics.transactionsRejected);\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n\n return {\n success: false as const,\n reason: validation.reason,\n };\n }\n\n // Phase 2: Append to WAL with gap check (BEFORE state mutation)\n const walEntry: WalEntry = {\n transaction,\n version: validation.nextVersion,\n timestamp: Date.now(),\n };\n\n const appendResult = yield* Effect.either(\n hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion)\n );\n\n if (appendResult._tag === \"Left\") {\n yield* Effect.logError(\"WAL append failed\", {\n documentId,\n version: validation.nextVersion,\n error: appendResult.left,\n });\n yield* Metric.increment(Metrics.walAppendFailures);\n\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n\n return {\n success: false as const,\n reason: \"Storage unavailable. Please retry.\",\n };\n }\n\n // Phase 3: Apply (state mutation + broadcast)\n document.apply(transaction);\n\n // Track metrics\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n yield* Metric.increment(Metrics.transactionsProcessed);\n yield* Metric.increment(Metrics.storageWalAppends);\n\n // Increment transaction count\n yield* Ref.update(transactionsSinceSnapshotRef, (n) => n + 1);\n\n // Check snapshot triggers\n yield* checkSnapshotTriggers();\n\n return {\n success: true as const,\n version: validation.nextVersion,\n };\n });\n\n const touch = Effect.fn(\"document.touch\")(function* () {\n yield* Ref.set(lastActivityTimeRef, Date.now());\n });\n\n const needsSnapshot = () =>\n Effect.map(Ref.get(transactionsSinceSnapshotRef), (n) => n > 0);\n\n const getLastActivityTime = () => Ref.get(lastActivityTimeRef);\n\n return {\n document,\n pubsub,\n getSnapshotTracking,\n submit,\n saveSnapshot,\n checkSnapshotTriggers,\n touch,\n getVersion: () => document.getVersion(),\n getSnapshot: () => document.getSnapshot(),\n needsSnapshot,\n getLastActivityTime,\n };\n });\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Compute initial state for a new document.\n */\nconst computeInitialState = <TSchema extends Primitive.AnyPrimitive>(\n config: DocumentInstanceConfig<TSchema>,\n documentId: string\n): Effect.Effect<Primitive.InferSetInput<TSchema> | undefined> => {\n if (config.initial === undefined) {\n return Effect.succeed(undefined);\n }\n\n if (typeof config.initial === \"function\") {\n return (config.initial as (ctx: { documentId: string }) => Effect.Effect<Primitive.InferSetInput<TSchema>>)({\n documentId,\n });\n }\n\n return Effect.succeed(config.initial as Primitive.InferSetInput<TSchema>);\n};\n\n/**\n * Verify WAL continuity and log warnings for any gaps.\n */\nconst verifyWalContinuity = Effect.fn(\"document.wal.verify\")(function* (\n documentId: string,\n walEntries: readonly WalEntry[],\n baseVersion: number\n) {\n if (walEntries.length === 0) {\n return;\n }\n\n const firstWalVersion = walEntries[0]!.version;\n const expectedFirst = baseVersion + 1;\n\n if (firstWalVersion !== expectedFirst) {\n yield* Effect.logWarning(\"WAL version gap detected\", {\n documentId,\n snapshotVersion: baseVersion,\n firstWalVersion,\n expectedFirst,\n });\n yield* Metric.increment(Metrics.storageVersionGaps);\n }\n\n for (let i = 1; i < walEntries.length; i++) {\n const prev = walEntries[i - 1]!.version;\n const curr = walEntries[i]!.version;\n if (curr !== prev + 1) {\n yield* Effect.logWarning(\"WAL internal gap detected\", {\n documentId,\n previousVersion: prev,\n currentVersion: curr,\n });\n }\n }\n});\n\n/**\n * Replay WAL entries onto a ServerDocument.\n */\nconst replayWalEntries = Effect.fn(\"document.wal.replay\")(function* (\n documentId: string,\n document: ServerDocument.ServerDocument<Primitive.AnyPrimitive>,\n walEntries: readonly WalEntry[]\n) {\n for (const entry of walEntries) {\n const result = document.submit(entry.transaction);\n if (!result.success) {\n yield* Effect.logWarning(\"Skipping corrupted WAL entry\", {\n documentId,\n version: entry.version,\n reason: result.reason,\n });\n }\n }\n});\n\n/**\n * Compute snapshot state by replaying WAL entries on a base state.\n */\nconst computeSnapshotState = <TSchema extends Primitive.AnyPrimitive>(\n schema: TSchema,\n baseState: Primitive.InferState<TSchema> | undefined,\n walEntries: readonly WalEntry[],\n targetVersion: number\n): { state: Primitive.InferState<TSchema>; version: number } | undefined => {\n const relevantEntries = walEntries.filter((e) => e.version <= targetVersion);\n\n if (relevantEntries.length === 0 && baseState === undefined) {\n return undefined;\n }\n\n let snapshotState: Primitive.InferState<TSchema> | undefined = baseState;\n for (const entry of relevantEntries) {\n const tempDoc = Document.make(schema, { initialState: snapshotState });\n tempDoc.apply(entry.transaction.ops);\n snapshotState = tempDoc.get();\n }\n\n if (snapshotState === undefined) {\n return undefined;\n }\n\n const snapshotVersion =\n relevantEntries.length > 0 ? relevantEntries[relevantEntries.length - 1]!.version : 0;\n\n return { state: snapshotState, version: snapshotVersion };\n};\n\n/**\n * Check if a snapshot should be triggered.\n */\nconst shouldTriggerSnapshot = (\n transactionsSinceSnapshot: number,\n lastSnapshotTime: number,\n config: { interval: Duration.Duration; transactionThreshold: number }\n): boolean => {\n const now = Date.now();\n const intervalMs = Duration.toMillis(config.interval);\n\n if (transactionsSinceSnapshot >= config.transactionThreshold) {\n return true;\n }\n\n if (now - lastSnapshotTime >= intervalMs) {\n return true;\n }\n\n return false;\n};\n\n/**\n * Create a StoredDocument for persistence.\n */\nconst createStoredDocument = (\n state: unknown,\n version: number,\n schemaVersion: number\n): StoredDocument => ({\n state,\n version,\n schemaVersion,\n savedAt: Date.now(),\n});\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const DocumentInstance = {\n make,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"DocumentInstance.mjs","names":["initialState: Primitive.InferState<TSchema> | undefined","initial: Primitive.InferSetInput<TSchema> | undefined","Metrics.documentsRestored","Metrics.documentsCreated","Metrics.documentsActive","walEntries","storedDoc","Metrics.storageSnapshots","Metrics.storageSnapshotLatency","Metrics.transactionsRejected","latency","Metrics.transactionsLatency","walEntry: WalEntry","Metrics.walAppendFailures","Metrics.transactionsProcessed","Metrics.storageWalAppends","Metrics.storageVersionGaps","snapshotState: Primitive.InferState<TSchema> | undefined"],"sources":["../src/DocumentInstance.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - DocumentInstance\n *\n * Manages the lifecycle of a single document including:\n * - Restoration from storage (cold storage + WAL replay)\n * - Transaction submission with WAL persistence\n * - Snapshot saving and trigger checking\n *\n * Used by both MimicServerEngine (single-node) and MimicClusterServerEngine (clustered).\n */\nimport { Duration, Effect, Metric, PubSub, Ref } from \"effect\";\nimport { Document, type Primitive, type Transaction } from \"@voidhash/mimic\";\nimport { ServerDocument } from \"@voidhash/mimic/server\";\nimport type { StoredDocument, WalEntry } from \"./Types\";\nimport type { ServerBroadcast } from \"./Protocol\";\nimport type { ColdStorage } from \"./ColdStorage\";\nimport type { HotStorage } from \"./HotStorage\";\nimport type { ColdStorageError } from \"./Errors\";\nimport type { HotStorageError } from \"./Errors\";\nimport * as Metrics from \"./Metrics\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Result of submitting a transaction\n */\nexport type SubmitResult =\n | { readonly success: true; readonly version: number }\n | { readonly success: false; readonly reason: string };\n\n/**\n * Configuration for a DocumentInstance\n */\nexport interface DocumentInstanceConfig<TSchema extends Primitive.AnyPrimitive> {\n readonly schema: TSchema;\n readonly initial?:\n | Primitive.InferSetInput<TSchema>\n | ((ctx: { documentId: string }) => Effect.Effect<Primitive.InferSetInput<TSchema>>);\n readonly maxTransactionHistory: number;\n readonly snapshot: {\n readonly interval: Duration.Duration;\n readonly transactionThreshold: number;\n };\n}\n\n/**\n * Snapshot tracking state\n */\nexport interface SnapshotTracking {\n readonly lastSnapshotVersion: number;\n readonly lastSnapshotTime: number;\n readonly transactionsSinceSnapshot: number;\n}\n\n/**\n * A DocumentInstance manages a single document's lifecycle\n */\nexport interface DocumentInstance<TSchema extends Primitive.AnyPrimitive> {\n /** The underlying ServerDocument */\n readonly document: ServerDocument.ServerDocument<TSchema>;\n /** PubSub for broadcasting messages to subscribers */\n readonly pubsub: PubSub.PubSub<ServerBroadcast>;\n /** Current snapshot tracking state */\n readonly getSnapshotTracking: Effect.Effect<SnapshotTracking>;\n /** Submit a transaction */\n readonly submit: (transaction: Transaction.Transaction) => Effect.Effect<SubmitResult, ColdStorageError | HotStorageError>;\n /** Save a snapshot to cold storage */\n readonly saveSnapshot: () => Effect.Effect<void, ColdStorageError | HotStorageError>;\n /** Check if snapshot should be triggered and save if needed */\n readonly checkSnapshotTriggers: () => Effect.Effect<void, ColdStorageError | HotStorageError>;\n /** Update last activity time (for external tracking) */\n readonly touch: () => Effect.Effect<void>;\n /** Get current document version */\n readonly getVersion: () => number;\n /** Get document snapshot */\n readonly getSnapshot: () => { state: unknown; version: number };\n /** Check if document has unsnapshot transactions that need persisting */\n readonly needsSnapshot: () => Effect.Effect<boolean>;\n /** Get the last activity timestamp for idle detection */\n readonly getLastActivityTime: () => Effect.Effect<number>;\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a DocumentInstance for a single document.\n *\n * This handles:\n * - Loading from cold storage or computing initial state\n * - Persisting initial state immediately (crash safety)\n * - Replaying WAL entries\n * - Transaction submission with WAL persistence\n * - Snapshot saving\n */\nexport const make = <TSchema extends Primitive.AnyPrimitive>(\n documentId: string,\n config: DocumentInstanceConfig<TSchema>,\n coldStorage: ColdStorage,\n hotStorage: HotStorage\n): Effect.Effect<DocumentInstance<TSchema>, ColdStorageError | HotStorageError> =>\n Effect.gen(function* () {\n // Current schema version (hard-coded to 1 for now)\n const SCHEMA_VERSION = 1;\n\n // 1. Load snapshot from ColdStorage\n const storedDoc = yield* coldStorage.load(documentId);\n\n // Track initial values - only one will be set:\n // - initialState: raw state from storage (already in internal format)\n // - initial: computed from config (needs conversion to state format)\n let initialState: Primitive.InferState<TSchema> | undefined;\n let initial: Primitive.InferSetInput<TSchema> | undefined;\n let initialVersion = 0;\n\n if (storedDoc) {\n // Loading from storage - state is already in internal format\n initialState = storedDoc.state as Primitive.InferState<TSchema>;\n initialVersion = storedDoc.version;\n } else {\n // New document - compute initial value (set input format)\n initial = yield* computeInitialState(config, documentId);\n }\n\n // 2. Create PubSub for broadcasting\n const pubsub = yield* PubSub.unbounded<ServerBroadcast>();\n\n // 3. Create refs for tracking\n const lastSnapshotVersionRef = yield* Ref.make(initialVersion);\n const lastSnapshotTimeRef = yield* Ref.make(Date.now());\n const transactionsSinceSnapshotRef = yield* Ref.make(0);\n const lastActivityTimeRef = yield* Ref.make(Date.now());\n\n // 4. Create ServerDocument with callbacks\n const document = ServerDocument.make({\n schema: config.schema,\n initial,\n initialState,\n initialVersion,\n maxTransactionHistory: config.maxTransactionHistory,\n onBroadcast: (message: ServerDocument.TransactionMessage) => {\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"transaction\",\n transaction: message.transaction,\n version: message.version,\n })\n );\n },\n onRejection: (transactionId: string, reason: string) => {\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"error\",\n transactionId,\n reason,\n })\n );\n },\n });\n\n // 5. If this is a new document, immediately save to cold storage\n // This ensures the initial state is durable before any transactions are accepted.\n if (!storedDoc) {\n const initialStoredDoc = createStoredDocument(document.get(), 0, SCHEMA_VERSION);\n yield* coldStorage.save(documentId, initialStoredDoc);\n yield* Effect.logDebug(\"Initial state persisted to cold storage\", { documentId });\n }\n\n // 6. Load WAL entries\n const walEntries = yield* hotStorage.getEntries(documentId, initialVersion);\n\n // 7. Verify WAL continuity (warning only, non-blocking)\n yield* verifyWalContinuity(documentId, walEntries, initialVersion);\n\n // 8. Replay WAL entries\n yield* replayWalEntries(documentId, document, walEntries);\n\n // Track metrics\n if (storedDoc) {\n yield* Metric.increment(Metrics.documentsRestored);\n } else {\n yield* Metric.increment(Metrics.documentsCreated);\n }\n yield* Metric.incrementBy(Metrics.documentsActive, 1);\n\n // ==========================================================================\n // Instance Methods\n // ==========================================================================\n\n const getSnapshotTracking = Effect.gen(function* () {\n return {\n lastSnapshotVersion: yield* Ref.get(lastSnapshotVersionRef),\n lastSnapshotTime: yield* Ref.get(lastSnapshotTimeRef),\n transactionsSinceSnapshot: yield* Ref.get(transactionsSinceSnapshotRef),\n };\n });\n\n const saveSnapshot = Effect.fn(\"document.snapshot.save\")(function* () {\n const targetVersion = document.getVersion();\n const lastSnapshotVersion = yield* Ref.get(lastSnapshotVersionRef);\n\n // Idempotency check: skip if already snapshotted at this version\n if (targetVersion <= lastSnapshotVersion) {\n return;\n }\n\n const snapshotStartTime = Date.now();\n\n // Load base snapshot from cold storage\n const baseSnapshot = yield* coldStorage.load(documentId);\n const baseVersion = baseSnapshot?.version ?? 0;\n const baseState = baseSnapshot?.state as Primitive.InferState<TSchema> | undefined;\n\n // Load WAL entries from base to target\n const walEntries = yield* hotStorage.getEntries(documentId, baseVersion);\n\n // Compute snapshot state by replaying WAL on base\n const snapshotResult = computeSnapshotState(\n config.schema,\n baseState,\n walEntries,\n targetVersion\n );\n\n if (!snapshotResult) {\n return;\n }\n\n // Re-check before saving (in case another snapshot completed while we were working)\n const currentLastSnapshot = yield* Ref.get(lastSnapshotVersionRef);\n if (snapshotResult.version <= currentLastSnapshot) {\n return;\n }\n\n const storedDoc = createStoredDocument(\n snapshotResult.state,\n snapshotResult.version,\n SCHEMA_VERSION\n );\n\n // Save to ColdStorage\n yield* coldStorage.save(documentId, storedDoc);\n\n // Track snapshot metrics\n const snapshotDuration = Date.now() - snapshotStartTime;\n yield* Metric.increment(Metrics.storageSnapshots);\n yield* Metric.update(Metrics.storageSnapshotLatency, snapshotDuration);\n\n // Update tracking BEFORE truncate (for idempotency on retry)\n yield* Ref.set(lastSnapshotVersionRef, snapshotResult.version);\n yield* Ref.set(lastSnapshotTimeRef, Date.now());\n yield* Ref.set(transactionsSinceSnapshotRef, 0);\n\n // Truncate WAL - non-fatal, will be retried on next snapshot\n yield* Effect.catchAll(hotStorage.truncate(documentId, snapshotResult.version), (e) =>\n Effect.logWarning(\"WAL truncate failed - will retry on next snapshot\", {\n documentId,\n version: snapshotResult.version,\n error: e,\n })\n );\n });\n\n const checkSnapshotTriggers = Effect.fn(\"document.snapshot.check-triggers\")(function* () {\n const txCount = yield* Ref.get(transactionsSinceSnapshotRef);\n const lastTime = yield* Ref.get(lastSnapshotTimeRef);\n\n if (shouldTriggerSnapshot(txCount, lastTime, config.snapshot)) {\n yield* saveSnapshot();\n }\n });\n\n const submit = Effect.fn(\"document.transaction.submit\")(function* (\n transaction: Transaction.Transaction\n ) {\n const submitStartTime = Date.now();\n\n // Update activity time\n yield* Ref.set(lastActivityTimeRef, Date.now());\n\n // Phase 1: Validate (no side effects)\n const validation = document.validate(transaction);\n\n if (!validation.valid) {\n yield* Metric.increment(Metrics.transactionsRejected);\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n\n return {\n success: false as const,\n reason: validation.reason,\n };\n }\n\n // Phase 2: Append to WAL with gap check (BEFORE state mutation)\n const walEntry: WalEntry = {\n transaction,\n version: validation.nextVersion,\n timestamp: Date.now(),\n };\n\n // Get the current snapshot version to pass as baseVersion for gap checking\n // This ensures correct validation after truncation or restart\n const snapshotVersion = yield* Ref.get(lastSnapshotVersionRef);\n\n const appendResult = yield* Effect.either(\n hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion, snapshotVersion)\n );\n\n if (appendResult._tag === \"Left\") {\n yield* Effect.logError(\"WAL append failed\", {\n documentId,\n version: validation.nextVersion,\n error: appendResult.left,\n });\n yield* Metric.increment(Metrics.walAppendFailures);\n\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n\n return {\n success: false as const,\n reason: \"Storage unavailable. Please retry.\",\n };\n }\n\n // Phase 3: Apply (state mutation + broadcast)\n document.apply(transaction);\n\n // Track metrics\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n yield* Metric.increment(Metrics.transactionsProcessed);\n yield* Metric.increment(Metrics.storageWalAppends);\n\n // Increment transaction count\n yield* Ref.update(transactionsSinceSnapshotRef, (n) => n + 1);\n\n // Check snapshot triggers\n yield* checkSnapshotTriggers();\n\n return {\n success: true as const,\n version: validation.nextVersion,\n };\n });\n\n const touch = Effect.fn(\"document.touch\")(function* () {\n yield* Ref.set(lastActivityTimeRef, Date.now());\n });\n\n const needsSnapshot = () =>\n Effect.map(Ref.get(transactionsSinceSnapshotRef), (n) => n > 0);\n\n const getLastActivityTime = () => Ref.get(lastActivityTimeRef);\n\n return {\n document,\n pubsub,\n getSnapshotTracking,\n submit,\n saveSnapshot,\n checkSnapshotTriggers,\n touch,\n getVersion: () => document.getVersion(),\n getSnapshot: () => document.getSnapshot(),\n needsSnapshot,\n getLastActivityTime,\n };\n });\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Compute initial state for a new document.\n */\nconst computeInitialState = <TSchema extends Primitive.AnyPrimitive>(\n config: DocumentInstanceConfig<TSchema>,\n documentId: string\n): Effect.Effect<Primitive.InferSetInput<TSchema> | undefined> => {\n if (config.initial === undefined) {\n return Effect.succeed(undefined);\n }\n\n if (typeof config.initial === \"function\") {\n return (config.initial as (ctx: { documentId: string }) => Effect.Effect<Primitive.InferSetInput<TSchema>>)({\n documentId,\n });\n }\n\n return Effect.succeed(config.initial as Primitive.InferSetInput<TSchema>);\n};\n\n/**\n * Verify WAL continuity and log warnings for any gaps.\n */\nconst verifyWalContinuity = Effect.fn(\"document.wal.verify\")(function* (\n documentId: string,\n walEntries: readonly WalEntry[],\n baseVersion: number\n) {\n if (walEntries.length === 0) {\n return;\n }\n\n const firstWalVersion = walEntries[0]!.version;\n const expectedFirst = baseVersion + 1;\n\n if (firstWalVersion !== expectedFirst) {\n yield* Effect.logWarning(\"WAL version gap detected\", {\n documentId,\n snapshotVersion: baseVersion,\n firstWalVersion,\n expectedFirst,\n });\n yield* Metric.increment(Metrics.storageVersionGaps);\n }\n\n for (let i = 1; i < walEntries.length; i++) {\n const prev = walEntries[i - 1]!.version;\n const curr = walEntries[i]!.version;\n if (curr !== prev + 1) {\n yield* Effect.logWarning(\"WAL internal gap detected\", {\n documentId,\n previousVersion: prev,\n currentVersion: curr,\n });\n }\n }\n});\n\n/**\n * Replay WAL entries onto a ServerDocument.\n */\nconst replayWalEntries = Effect.fn(\"document.wal.replay\")(function* (\n documentId: string,\n document: ServerDocument.ServerDocument<Primitive.AnyPrimitive>,\n walEntries: readonly WalEntry[]\n) {\n for (const entry of walEntries) {\n const result = document.submit(entry.transaction);\n if (!result.success) {\n yield* Effect.logWarning(\"Skipping corrupted WAL entry\", {\n documentId,\n version: entry.version,\n reason: result.reason,\n });\n }\n }\n});\n\n/**\n * Compute snapshot state by replaying WAL entries on a base state.\n */\nconst computeSnapshotState = <TSchema extends Primitive.AnyPrimitive>(\n schema: TSchema,\n baseState: Primitive.InferState<TSchema> | undefined,\n walEntries: readonly WalEntry[],\n targetVersion: number\n): { state: Primitive.InferState<TSchema>; version: number } | undefined => {\n const relevantEntries = walEntries.filter((e) => e.version <= targetVersion);\n\n if (relevantEntries.length === 0 && baseState === undefined) {\n return undefined;\n }\n\n let snapshotState: Primitive.InferState<TSchema> | undefined = baseState;\n for (const entry of relevantEntries) {\n const tempDoc = Document.make(schema, { initialState: snapshotState });\n tempDoc.apply(entry.transaction.ops);\n snapshotState = tempDoc.get();\n }\n\n if (snapshotState === undefined) {\n return undefined;\n }\n\n const snapshotVersion =\n relevantEntries.length > 0 ? relevantEntries[relevantEntries.length - 1]!.version : 0;\n\n return { state: snapshotState, version: snapshotVersion };\n};\n\n/**\n * Check if a snapshot should be triggered.\n */\nconst shouldTriggerSnapshot = (\n transactionsSinceSnapshot: number,\n lastSnapshotTime: number,\n config: { interval: Duration.Duration; transactionThreshold: number }\n): boolean => {\n const now = Date.now();\n const intervalMs = Duration.toMillis(config.interval);\n\n if (transactionsSinceSnapshot >= config.transactionThreshold) {\n return true;\n }\n\n if (now - lastSnapshotTime >= intervalMs) {\n return true;\n }\n\n return false;\n};\n\n/**\n * Create a StoredDocument for persistence.\n */\nconst createStoredDocument = (\n state: unknown,\n version: number,\n schemaVersion: number\n): StoredDocument => ({\n state,\n version,\n schemaVersion,\n savedAt: Date.now(),\n});\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const DocumentInstance = {\n make,\n};\n"],"mappings":"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"}
@@ -55,19 +55,20 @@ let InMemory;
55
55
  return effect.HashMap.set(map, documentId, [...entries, entry]);
56
56
  });
57
57
  }),
58
- appendWithCheck: effect.Effect.fn("hot-storage.append-with-check")(function* (documentId, entry, expectedVersion) {
58
+ appendWithCheck: effect.Effect.fn("hot-storage.append-with-check")(function* (documentId, entry, expectedVersion, baseVersion) {
59
59
  const result = yield* effect.Ref.modify(store, (map) => {
60
60
  const existing = effect.HashMap.get(map, documentId);
61
61
  const entries = existing._tag === "Some" ? existing.value : [];
62
- const lastVersion = entries.length > 0 ? Math.max(...entries.map((e) => e.version)) : 0;
62
+ const lastEntryVersion = entries.length > 0 ? Math.max(...entries.map((e) => e.version)) : 0;
63
+ const effectiveLastVersion = baseVersion !== void 0 ? Math.max(lastEntryVersion, baseVersion) : lastEntryVersion;
63
64
  if (expectedVersion === 1) {
64
- if (lastVersion >= 1) return [{
65
+ if (effectiveLastVersion >= 1) return [{
65
66
  type: "gap",
66
- lastVersion
67
+ lastVersion: effectiveLastVersion
67
68
  }, map];
68
- } else if (lastVersion !== expectedVersion - 1) return [{
69
+ } else if (effectiveLastVersion !== expectedVersion - 1) return [{
69
70
  type: "gap",
70
- lastVersion: lastVersion > 0 ? lastVersion : void 0
71
+ lastVersion: effectiveLastVersion > 0 ? effectiveLastVersion : void 0
71
72
  }, map];
72
73
  return [{ type: "ok" }, effect.HashMap.set(map, documentId, [...entries, entry])];
73
74
  });
@@ -21,7 +21,7 @@ interface HotStorage {
21
21
  *
22
22
  * This is an atomic operation that:
23
23
  * 1. Verifies the previous entry has version = expectedVersion - 1
24
- * (or this is the first entry if expectedVersion === 1)
24
+ * (or this is the first entry if expectedVersion === 1, accounting for baseVersion)
25
25
  * 2. Appends the entry if check passes
26
26
  *
27
27
  * Use this for two-phase commit to guarantee WAL ordering at write time.
@@ -29,9 +29,13 @@ interface HotStorage {
29
29
  * @param documentId - Document ID
30
30
  * @param entry - WAL entry to append
31
31
  * @param expectedVersion - The version this entry should have (entry.version)
32
+ * @param baseVersion - Optional known snapshot version. When provided, an empty WAL
33
+ * is treated as "at this version" rather than "new document at version 0".
34
+ * This is necessary after truncation or restart to correctly validate
35
+ * that the next entry is baseVersion + 1.
32
36
  * @returns Effect that fails with WalVersionGapError if gap detected
33
37
  */
34
- readonly appendWithCheck: (documentId: string, entry: WalEntry, expectedVersion: number) => Effect.Effect<void, HotStorageError | WalVersionGapError>;
38
+ readonly appendWithCheck: (documentId: string, entry: WalEntry, expectedVersion: number, baseVersion?: number) => Effect.Effect<void, HotStorageError | WalVersionGapError>;
35
39
  /**
36
40
  * Get all WAL entries for a document since a given version.
37
41
  * Returns entries with version > sinceVersion, ordered by version.
@@ -1 +1 @@
1
- {"version":3,"file":"HotStorage.d.cts","names":[],"sources":["../src/HotStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAyDO,UArCU,UAAA,CAqCH;EASa;;;EAC1B,SAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EAzCU,QAyCV,EAAA,GAxCM,MAAA,CAAO,MAwCb,CAAA,IAAA,EAxC0B,eAwC1B,CAAA;;;;;AASD;AAuDA;AAwHA;;;;;;;;;EArIG,SAAM,eAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EAxEE,QAwEF,EAAA,eAAA,EAAA,MAAA,EAAA,GAtEF,MAAA,CAAO,MAsEL,CAAA,IAAA,EAtEkB,eAsElB,GAtEoC,kBAsEpC,CAAA;;;;;qEA7DF,MAAA,CAAO,OAAO,YAAY;;;;;kEAS1B,MAAA,CAAO,aAAa;;cAC1B;;;;cASY,aAAA,SAAsB,kBAAA;;;;;;;kBAuDlB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cAoHzB;;uBAtIH,MAAA,CAAO,OAAO,YAAY,GAAG,OACpC,KAAA,CAAM,MAAM,eAAe,GAAG"}
1
+ {"version":3,"file":"HotStorage.d.cts","names":[],"sources":["../src/HotStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AA8DO,UA1CU,UAAA,CA0CH;EASa;;;EAC1B,SAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EA9CU,QA8CV,EAAA,GA7CM,MAAA,CAAO,MA6Cb,CAAA,IAAA,EA7C0B,eA6C1B,CAAA;;;;;AASD;AAuDA;AAkIA;;;;;;;;;;;;;wDAxNW,4DAGJ,MAAA,CAAO,aAAa,kBAAkB;;;;;qEAStC,MAAA,CAAO,OAAO,YAAY;;;;;kEAS1B,MAAA,CAAO,aAAa;;cAC1B;;;;cASY,aAAA,SAAsB,kBAAA;;;;;;;kBAuDlB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cA8HzB;;uBAhJH,MAAA,CAAO,OAAO,YAAY,GAAG,OACpC,KAAA,CAAM,MAAM,eAAe,GAAG"}
@@ -21,7 +21,7 @@ interface HotStorage {
21
21
  *
22
22
  * This is an atomic operation that:
23
23
  * 1. Verifies the previous entry has version = expectedVersion - 1
24
- * (or this is the first entry if expectedVersion === 1)
24
+ * (or this is the first entry if expectedVersion === 1, accounting for baseVersion)
25
25
  * 2. Appends the entry if check passes
26
26
  *
27
27
  * Use this for two-phase commit to guarantee WAL ordering at write time.
@@ -29,9 +29,13 @@ interface HotStorage {
29
29
  * @param documentId - Document ID
30
30
  * @param entry - WAL entry to append
31
31
  * @param expectedVersion - The version this entry should have (entry.version)
32
+ * @param baseVersion - Optional known snapshot version. When provided, an empty WAL
33
+ * is treated as "at this version" rather than "new document at version 0".
34
+ * This is necessary after truncation or restart to correctly validate
35
+ * that the next entry is baseVersion + 1.
32
36
  * @returns Effect that fails with WalVersionGapError if gap detected
33
37
  */
34
- readonly appendWithCheck: (documentId: string, entry: WalEntry, expectedVersion: number) => Effect.Effect<void, HotStorageError | WalVersionGapError>;
38
+ readonly appendWithCheck: (documentId: string, entry: WalEntry, expectedVersion: number, baseVersion?: number) => Effect.Effect<void, HotStorageError | WalVersionGapError>;
35
39
  /**
36
40
  * Get all WAL entries for a document since a given version.
37
41
  * Returns entries with version > sinceVersion, ordered by version.
@@ -1 +1 @@
1
- {"version":3,"file":"HotStorage.d.mts","names":[],"sources":["../src/HotStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAyDO,UArCU,UAAA,CAqCH;EASa;;;EAC1B,SAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EAzCU,QAyCV,EAAA,GAxCM,MAAA,CAAO,MAwCb,CAAA,IAAA,EAxC0B,eAwC1B,CAAA;;;;;AASD;AAuDA;AAwHA;;;;;;;;;EArIG,SAAM,eAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EAxEE,QAwEF,EAAA,eAAA,EAAA,MAAA,EAAA,GAtEF,MAAA,CAAO,MAsEL,CAAA,IAAA,EAtEkB,eAsElB,GAtEoC,kBAsEpC,CAAA;;;;;qEA7DF,MAAA,CAAO,OAAO,YAAY;;;;;kEAS1B,MAAA,CAAO,aAAa;;cAC1B;;;;cASY,aAAA,SAAsB,kBAAA;;;;;;;kBAuDlB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cAoHzB;;uBAtIH,MAAA,CAAO,OAAO,YAAY,GAAG,OACpC,KAAA,CAAM,MAAM,eAAe,GAAG"}
1
+ {"version":3,"file":"HotStorage.d.mts","names":[],"sources":["../src/HotStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AA8DO,UA1CU,UAAA,CA0CH;EASa;;;EAC1B,SAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EA9CU,QA8CV,EAAA,GA7CM,MAAA,CAAO,MA6Cb,CAAA,IAAA,EA7C0B,eA6C1B,CAAA;;;;;AASD;AAuDA;AAkIA;;;;;;;;;;;;;wDAxNW,4DAGJ,MAAA,CAAO,aAAa,kBAAkB;;;;;qEAStC,MAAA,CAAO,OAAO,YAAY;;;;;kEAS1B,MAAA,CAAO,aAAa;;cAC1B;;;;cASY,aAAA,SAAsB,kBAAA;;;;;;;kBAuDlB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cA8HzB;;uBAhJH,MAAA,CAAO,OAAO,YAAY,GAAG,OACpC,KAAA,CAAM,MAAM,eAAe,GAAG"}
@@ -55,19 +55,20 @@ let InMemory;
55
55
  return HashMap.set(map, documentId, [...entries, entry]);
56
56
  });
57
57
  }),
58
- appendWithCheck: Effect.fn("hot-storage.append-with-check")(function* (documentId, entry, expectedVersion) {
58
+ appendWithCheck: Effect.fn("hot-storage.append-with-check")(function* (documentId, entry, expectedVersion, baseVersion) {
59
59
  const result = yield* Ref.modify(store, (map) => {
60
60
  const existing = HashMap.get(map, documentId);
61
61
  const entries = existing._tag === "Some" ? existing.value : [];
62
- const lastVersion = entries.length > 0 ? Math.max(...entries.map((e) => e.version)) : 0;
62
+ const lastEntryVersion = entries.length > 0 ? Math.max(...entries.map((e) => e.version)) : 0;
63
+ const effectiveLastVersion = baseVersion !== void 0 ? Math.max(lastEntryVersion, baseVersion) : lastEntryVersion;
63
64
  if (expectedVersion === 1) {
64
- if (lastVersion >= 1) return [{
65
+ if (effectiveLastVersion >= 1) return [{
65
66
  type: "gap",
66
- lastVersion
67
+ lastVersion: effectiveLastVersion
67
68
  }, map];
68
- } else if (lastVersion !== expectedVersion - 1) return [{
69
+ } else if (effectiveLastVersion !== expectedVersion - 1) return [{
69
70
  type: "gap",
70
- lastVersion: lastVersion > 0 ? lastVersion : void 0
71
+ lastVersion: effectiveLastVersion > 0 ? effectiveLastVersion : void 0
71
72
  }, map];
72
73
  return [{ type: "ok" }, HashMap.set(map, documentId, [...entries, entry])];
73
74
  });
@@ -1 +1 @@
1
- {"version":3,"file":"HotStorage.mjs","names":["result: CheckResult"],"sources":["../src/HotStorage.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - HotStorage\n *\n * Interface and implementations for Write-Ahead Log (WAL) storage.\n */\nimport { Context, Effect, HashMap, Layer, Ref } from \"effect\";\nimport type { WalEntry } from \"./Types\";\nimport { HotStorageError, WalVersionGapError } from \"./Errors\";\n\n// =============================================================================\n// HotStorage Interface\n// =============================================================================\n\n/**\n * HotStorage interface for storing Write-Ahead Log entries.\n *\n * This is the \"hot\" tier of the two-tier storage system.\n * It stores every transaction as a WAL entry for durability between snapshots.\n * WAL entries are small (just the transaction) and writes are append-only.\n */\nexport interface HotStorage {\n /**\n * Append a WAL entry for a document.\n */\n readonly append: (\n documentId: string,\n entry: WalEntry\n ) => Effect.Effect<void, HotStorageError>;\n\n /**\n * Append a WAL entry with version gap checking.\n *\n * This is an atomic operation that:\n * 1. Verifies the previous entry has version = expectedVersion - 1\n * (or this is the first entry if expectedVersion === 1)\n * 2. Appends the entry if check passes\n *\n * Use this for two-phase commit to guarantee WAL ordering at write time.\n *\n * @param documentId - Document ID\n * @param entry - WAL entry to append\n * @param expectedVersion - The version this entry should have (entry.version)\n * @returns Effect that fails with WalVersionGapError if gap detected\n */\n readonly appendWithCheck: (\n documentId: string,\n entry: WalEntry,\n expectedVersion: number\n ) => Effect.Effect<void, HotStorageError | WalVersionGapError>;\n\n /**\n * Get all WAL entries for a document since a given version.\n * Returns entries with version > sinceVersion, ordered by version.\n */\n readonly getEntries: (\n documentId: string,\n sinceVersion: number\n ) => Effect.Effect<WalEntry[], HotStorageError>;\n\n /**\n * Truncate WAL entries up to (and including) a given version.\n * Called after a snapshot is saved to remove entries that are now in the snapshot.\n */\n readonly truncate: (\n documentId: string,\n upToVersion: number\n ) => Effect.Effect<void, HotStorageError>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for HotStorage service\n */\nexport class HotStorageTag extends Context.Tag(\"@voidhash/mimic-effect/HotStorage\")<\n HotStorageTag,\n HotStorage\n>() {}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a HotStorage layer from an Effect that produces a HotStorage service.\n *\n * This allows you to access other Effect services when implementing custom storage.\n *\n * @example\n * ```typescript\n * const Hot = HotStorage.make(\n * Effect.gen(function*() {\n * const redis = yield* RedisService\n *\n * return {\n * append: (documentId, entry) =>\n * redis.rpush(`wal:${documentId}`, JSON.stringify(entry)),\n * getEntries: (documentId, sinceVersion) =>\n * redis.lrange(`wal:${documentId}`, 0, -1).pipe(\n * Effect.map(entries =>\n * entries\n * .map(e => JSON.parse(e))\n * .filter(e => e.version > sinceVersion)\n * .sort((a, b) => a.version - b.version)\n * )\n * ),\n * truncate: (documentId, upToVersion) =>\n * // Implementation depends on Redis data structure\n * Effect.void,\n * }\n * })\n * )\n * ```\n */\nexport const make = <E, R>(\n effect: Effect.Effect<HotStorage, E, R>\n): Layer.Layer<HotStorageTag, E, R> =>\n Layer.effect(HotStorageTag, effect);\n\n// =============================================================================\n// InMemory Implementation\n// =============================================================================\n\n/**\n * In-memory HotStorage implementation.\n *\n * Useful for testing and development. Not suitable for production\n * as data is lost when the process restarts.\n */\nexport namespace InMemory {\n /**\n * Create an in-memory HotStorage layer.\n */\n export const make = (): Layer.Layer<HotStorageTag> =>\n Layer.effect(\n HotStorageTag,\n Effect.fn(\"hot-storage.in-memory.create\")(function* () {\n const store = yield* Ref.make(HashMap.empty<string, WalEntry[]>());\n\n return {\n append: Effect.fn(\"hot-storage.append\")(\n function* (documentId: string, entry: WalEntry) {\n yield* Ref.update(store, (map) => {\n const existing = HashMap.get(map, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n return HashMap.set(map, documentId, [...entries, entry]);\n });\n }\n ),\n\n appendWithCheck: Effect.fn(\"hot-storage.append-with-check\")(\n function* (\n documentId: string,\n entry: WalEntry,\n expectedVersion: number\n ) {\n type CheckResult =\n | { type: \"ok\" }\n | { type: \"gap\"; lastVersion: number | undefined };\n\n // Use Ref.modify for atomic check + update\n const result: CheckResult = yield* Ref.modify(\n store,\n (map): [CheckResult, HashMap.HashMap<string, WalEntry[]>] => {\n const existing = HashMap.get(map, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n\n // Find the highest version in existing entries\n const lastVersion =\n entries.length > 0\n ? Math.max(...entries.map((e) => e.version))\n : 0;\n\n // Gap check\n if (expectedVersion === 1) {\n // First entry: should have no entries with version >= 1\n if (lastVersion >= 1) {\n return [{ type: \"gap\", lastVersion }, map];\n }\n } else {\n // Not first: last entry should have version = expectedVersion - 1\n if (lastVersion !== expectedVersion - 1) {\n return [\n {\n type: \"gap\",\n lastVersion: lastVersion > 0 ? lastVersion : undefined,\n },\n map,\n ];\n }\n }\n\n // No gap: append and return success\n return [\n { type: \"ok\" },\n HashMap.set(map, documentId, [...entries, entry]),\n ];\n }\n );\n\n if (result.type === \"gap\") {\n return yield* Effect.fail(\n new WalVersionGapError({\n documentId,\n expectedVersion,\n actualPreviousVersion: result.lastVersion,\n })\n );\n }\n }\n ),\n\n getEntries: Effect.fn(\"hot-storage.get-entries\")(\n function* (documentId: string, sinceVersion: number) {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n return entries\n .filter((e) => e.version > sinceVersion)\n .sort((a, b) => a.version - b.version);\n }\n ),\n\n truncate: Effect.fn(\"hot-storage.truncate\")(\n function* (documentId: string, upToVersion: number) {\n yield* Ref.update(store, (map) => {\n const existing = HashMap.get(map, documentId);\n if (existing._tag === \"None\") {\n return map;\n }\n const filtered = existing.value.filter(\n (e) => e.version > upToVersion\n );\n return HashMap.set(map, documentId, filtered);\n });\n }\n ),\n };\n })()\n );\n}\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const HotStorage = {\n Tag: HotStorageTag,\n make,\n InMemory,\n};\n"],"mappings":";;;;;;;;;;;;AA4EA,IAAa,gBAAb,cAAmC,QAAQ,IAAI,oCAAoC,EAGhF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCJ,MAAa,QACX,WAEA,MAAM,OAAO,eAAe,OAAO;;;wBAiBjC,MAAM,OACJ,eACA,OAAO,GAAG,+BAA+B,CAAC,aAAa;EACrD,MAAM,QAAQ,OAAO,IAAI,KAAK,QAAQ,OAA2B,CAAC;AAElE,SAAO;GACL,QAAQ,OAAO,GAAG,qBAAqB,CACrC,WAAW,YAAoB,OAAiB;AAC9C,WAAO,IAAI,OAAO,QAAQ,QAAQ;KAChC,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;KAC7C,MAAM,UACJ,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE;AAChD,YAAO,QAAQ,IAAI,KAAK,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC;MACxD;KAEL;GAED,iBAAiB,OAAO,GAAG,gCAAgC,CACzD,WACE,YACA,OACA,iBACA;IAMA,MAAMA,SAAsB,OAAO,IAAI,OACrC,QACC,QAA4D;KAC3D,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;KAC7C,MAAM,UACJ,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE;KAGhD,MAAM,cACJ,QAAQ,SAAS,IACb,KAAK,IAAI,GAAG,QAAQ,KAAK,MAAM,EAAE,QAAQ,CAAC,GAC1C;AAGN,SAAI,oBAAoB,GAEtB;UAAI,eAAe,EACjB,QAAO,CAAC;OAAE,MAAM;OAAO;OAAa,EAAE,IAAI;gBAIxC,gBAAgB,kBAAkB,EACpC,QAAO,CACL;MACE,MAAM;MACN,aAAa,cAAc,IAAI,cAAc;MAC9C,EACD,IACD;AAKL,YAAO,CACL,EAAE,MAAM,MAAM,EACd,QAAQ,IAAI,KAAK,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC,CAClD;MAEJ;AAED,QAAI,OAAO,SAAS,MAClB,QAAO,OAAO,OAAO,KACnB,IAAI,mBAAmB;KACrB;KACA;KACA,uBAAuB,OAAO;KAC/B,CAAC,CACH;KAGN;GAED,YAAY,OAAO,GAAG,0BAA0B,CAC9C,WAAW,YAAoB,cAAsB;IACnD,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;IACrC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AAGjD,YADE,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE,EAE7C,QAAQ,MAAM,EAAE,UAAU,aAAa,CACvC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,QAAQ;KAE3C;GAED,UAAU,OAAO,GAAG,uBAAuB,CACzC,WAAW,YAAoB,aAAqB;AAClD,WAAO,IAAI,OAAO,QAAQ,QAAQ;KAChC,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;AAC7C,SAAI,SAAS,SAAS,OACpB,QAAO;KAET,MAAM,WAAW,SAAS,MAAM,QAC7B,MAAM,EAAE,UAAU,YACpB;AACD,YAAO,QAAQ,IAAI,KAAK,YAAY,SAAS;MAC7C;KAEL;GACF;GACD,EAAE,CACL;;AAOL,MAAa,aAAa;CACxB,KAAK;CACL;CACA;CACD"}
1
+ {"version":3,"file":"HotStorage.mjs","names":["result: CheckResult"],"sources":["../src/HotStorage.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - HotStorage\n *\n * Interface and implementations for Write-Ahead Log (WAL) storage.\n */\nimport { Context, Effect, HashMap, Layer, Ref } from \"effect\";\nimport type { WalEntry } from \"./Types\";\nimport { HotStorageError, WalVersionGapError } from \"./Errors\";\n\n// =============================================================================\n// HotStorage Interface\n// =============================================================================\n\n/**\n * HotStorage interface for storing Write-Ahead Log entries.\n *\n * This is the \"hot\" tier of the two-tier storage system.\n * It stores every transaction as a WAL entry for durability between snapshots.\n * WAL entries are small (just the transaction) and writes are append-only.\n */\nexport interface HotStorage {\n /**\n * Append a WAL entry for a document.\n */\n readonly append: (\n documentId: string,\n entry: WalEntry\n ) => Effect.Effect<void, HotStorageError>;\n\n /**\n * Append a WAL entry with version gap checking.\n *\n * This is an atomic operation that:\n * 1. Verifies the previous entry has version = expectedVersion - 1\n * (or this is the first entry if expectedVersion === 1, accounting for baseVersion)\n * 2. Appends the entry if check passes\n *\n * Use this for two-phase commit to guarantee WAL ordering at write time.\n *\n * @param documentId - Document ID\n * @param entry - WAL entry to append\n * @param expectedVersion - The version this entry should have (entry.version)\n * @param baseVersion - Optional known snapshot version. When provided, an empty WAL\n * is treated as \"at this version\" rather than \"new document at version 0\".\n * This is necessary after truncation or restart to correctly validate\n * that the next entry is baseVersion + 1.\n * @returns Effect that fails with WalVersionGapError if gap detected\n */\n readonly appendWithCheck: (\n documentId: string,\n entry: WalEntry,\n expectedVersion: number,\n baseVersion?: number\n ) => Effect.Effect<void, HotStorageError | WalVersionGapError>;\n\n /**\n * Get all WAL entries for a document since a given version.\n * Returns entries with version > sinceVersion, ordered by version.\n */\n readonly getEntries: (\n documentId: string,\n sinceVersion: number\n ) => Effect.Effect<WalEntry[], HotStorageError>;\n\n /**\n * Truncate WAL entries up to (and including) a given version.\n * Called after a snapshot is saved to remove entries that are now in the snapshot.\n */\n readonly truncate: (\n documentId: string,\n upToVersion: number\n ) => Effect.Effect<void, HotStorageError>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for HotStorage service\n */\nexport class HotStorageTag extends Context.Tag(\"@voidhash/mimic-effect/HotStorage\")<\n HotStorageTag,\n HotStorage\n>() {}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a HotStorage layer from an Effect that produces a HotStorage service.\n *\n * This allows you to access other Effect services when implementing custom storage.\n *\n * @example\n * ```typescript\n * const Hot = HotStorage.make(\n * Effect.gen(function*() {\n * const redis = yield* RedisService\n *\n * return {\n * append: (documentId, entry) =>\n * redis.rpush(`wal:${documentId}`, JSON.stringify(entry)),\n * getEntries: (documentId, sinceVersion) =>\n * redis.lrange(`wal:${documentId}`, 0, -1).pipe(\n * Effect.map(entries =>\n * entries\n * .map(e => JSON.parse(e))\n * .filter(e => e.version > sinceVersion)\n * .sort((a, b) => a.version - b.version)\n * )\n * ),\n * truncate: (documentId, upToVersion) =>\n * // Implementation depends on Redis data structure\n * Effect.void,\n * }\n * })\n * )\n * ```\n */\nexport const make = <E, R>(\n effect: Effect.Effect<HotStorage, E, R>\n): Layer.Layer<HotStorageTag, E, R> =>\n Layer.effect(HotStorageTag, effect);\n\n// =============================================================================\n// InMemory Implementation\n// =============================================================================\n\n/**\n * In-memory HotStorage implementation.\n *\n * Useful for testing and development. Not suitable for production\n * as data is lost when the process restarts.\n */\nexport namespace InMemory {\n /**\n * Create an in-memory HotStorage layer.\n */\n export const make = (): Layer.Layer<HotStorageTag> =>\n Layer.effect(\n HotStorageTag,\n Effect.fn(\"hot-storage.in-memory.create\")(function* () {\n const store = yield* Ref.make(HashMap.empty<string, WalEntry[]>());\n\n return {\n append: Effect.fn(\"hot-storage.append\")(\n function* (documentId: string, entry: WalEntry) {\n yield* Ref.update(store, (map) => {\n const existing = HashMap.get(map, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n return HashMap.set(map, documentId, [...entries, entry]);\n });\n }\n ),\n\n appendWithCheck: Effect.fn(\"hot-storage.append-with-check\")(\n function* (\n documentId: string,\n entry: WalEntry,\n expectedVersion: number,\n baseVersion?: number\n ) {\n type CheckResult =\n | { type: \"ok\" }\n | { type: \"gap\"; lastVersion: number | undefined };\n\n // Use Ref.modify for atomic check + update\n const result: CheckResult = yield* Ref.modify(\n store,\n (map): [CheckResult, HashMap.HashMap<string, WalEntry[]>] => {\n const existing = HashMap.get(map, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n\n // Find the highest version in existing entries\n const lastEntryVersion =\n entries.length > 0\n ? Math.max(...entries.map((e) => e.version))\n : 0;\n\n // Effective \"last version\" is max of entries and baseVersion\n // This handles the case after truncation or restart where\n // WAL is empty but we know the snapshot version\n const effectiveLastVersion =\n baseVersion !== undefined\n ? Math.max(lastEntryVersion, baseVersion)\n : lastEntryVersion;\n\n // Gap check\n if (expectedVersion === 1) {\n // First entry: should have no entries with version >= 1\n // and baseVersion should be 0 or undefined\n if (effectiveLastVersion >= 1) {\n return [{ type: \"gap\", lastVersion: effectiveLastVersion }, map];\n }\n } else {\n // Not first: effective last version should be expectedVersion - 1\n if (effectiveLastVersion !== expectedVersion - 1) {\n return [\n {\n type: \"gap\",\n lastVersion: effectiveLastVersion > 0 ? effectiveLastVersion : undefined,\n },\n map,\n ];\n }\n }\n\n // No gap: append and return success\n return [\n { type: \"ok\" },\n HashMap.set(map, documentId, [...entries, entry]),\n ];\n }\n );\n\n if (result.type === \"gap\") {\n return yield* Effect.fail(\n new WalVersionGapError({\n documentId,\n expectedVersion,\n actualPreviousVersion: result.lastVersion,\n })\n );\n }\n }\n ),\n\n getEntries: Effect.fn(\"hot-storage.get-entries\")(\n function* (documentId: string, sinceVersion: number) {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n return entries\n .filter((e) => e.version > sinceVersion)\n .sort((a, b) => a.version - b.version);\n }\n ),\n\n truncate: Effect.fn(\"hot-storage.truncate\")(\n function* (documentId: string, upToVersion: number) {\n yield* Ref.update(store, (map) => {\n const existing = HashMap.get(map, documentId);\n if (existing._tag === \"None\") {\n return map;\n }\n const filtered = existing.value.filter(\n (e) => e.version > upToVersion\n );\n return HashMap.set(map, documentId, filtered);\n });\n }\n ),\n };\n })()\n );\n}\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const HotStorage = {\n Tag: HotStorageTag,\n make,\n InMemory,\n};\n"],"mappings":";;;;;;;;;;;;AAiFA,IAAa,gBAAb,cAAmC,QAAQ,IAAI,oCAAoC,EAGhF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCJ,MAAa,QACX,WAEA,MAAM,OAAO,eAAe,OAAO;;;wBAiBjC,MAAM,OACJ,eACA,OAAO,GAAG,+BAA+B,CAAC,aAAa;EACrD,MAAM,QAAQ,OAAO,IAAI,KAAK,QAAQ,OAA2B,CAAC;AAElE,SAAO;GACL,QAAQ,OAAO,GAAG,qBAAqB,CACrC,WAAW,YAAoB,OAAiB;AAC9C,WAAO,IAAI,OAAO,QAAQ,QAAQ;KAChC,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;KAC7C,MAAM,UACJ,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE;AAChD,YAAO,QAAQ,IAAI,KAAK,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC;MACxD;KAEL;GAED,iBAAiB,OAAO,GAAG,gCAAgC,CACzD,WACE,YACA,OACA,iBACA,aACA;IAMA,MAAMA,SAAsB,OAAO,IAAI,OACrC,QACC,QAA4D;KAC3D,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;KAC7C,MAAM,UACJ,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE;KAGhD,MAAM,mBACJ,QAAQ,SAAS,IACb,KAAK,IAAI,GAAG,QAAQ,KAAK,MAAM,EAAE,QAAQ,CAAC,GAC1C;KAKN,MAAM,uBACJ,gBAAgB,SACZ,KAAK,IAAI,kBAAkB,YAAY,GACvC;AAGN,SAAI,oBAAoB,GAGtB;UAAI,wBAAwB,EAC1B,QAAO,CAAC;OAAE,MAAM;OAAO,aAAa;OAAsB,EAAE,IAAI;gBAI9D,yBAAyB,kBAAkB,EAC7C,QAAO,CACL;MACE,MAAM;MACN,aAAa,uBAAuB,IAAI,uBAAuB;MAChE,EACD,IACD;AAKL,YAAO,CACL,EAAE,MAAM,MAAM,EACd,QAAQ,IAAI,KAAK,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC,CAClD;MAEJ;AAED,QAAI,OAAO,SAAS,MAClB,QAAO,OAAO,OAAO,KACnB,IAAI,mBAAmB;KACrB;KACA;KACA,uBAAuB,OAAO;KAC/B,CAAC,CACH;KAGN;GAED,YAAY,OAAO,GAAG,0BAA0B,CAC9C,WAAW,YAAoB,cAAsB;IACnD,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;IACrC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AAGjD,YADE,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE,EAE7C,QAAQ,MAAM,EAAE,UAAU,aAAa,CACvC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,QAAQ;KAE3C;GAED,UAAU,OAAO,GAAG,uBAAuB,CACzC,WAAW,YAAoB,aAAqB;AAClD,WAAO,IAAI,OAAO,QAAQ,QAAQ;KAChC,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;AAC7C,SAAI,SAAS,SAAS,OACpB,QAAO;KAET,MAAM,WAAW,SAAS,MAAM,QAC7B,MAAM,EAAE,UAAU,YACpB;AACD,YAAO,QAAQ,IAAI,KAAK,YAAY,SAAS;MAC7C;KAEL;GACF;GACD,EAAE,CACL;;AAOL,MAAa,aAAa;CACxB,KAAK;CACL;CACA;CACD"}
@@ -552,6 +552,44 @@ const tests = [
552
552
  yield* require_assertions.assertEqual(entries[1].version, 4, "Second should be version 4");
553
553
  })
554
554
  },
555
+ {
556
+ name: "appendWithCheck with baseVersion after full truncate works",
557
+ category: Categories.GapChecking,
558
+ run: effect.Effect.gen(function* () {
559
+ const storage = yield* require_HotStorage.HotStorageTag;
560
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(1), 1, 0);
561
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(2), 2, 0);
562
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(3), 3, 0);
563
+ yield* storage.truncate("gap-base-version", 3);
564
+ yield* require_assertions.assertEmpty(yield* storage.getEntries("gap-base-version", 0), "WAL should be empty after full truncate");
565
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(4), 4, 3);
566
+ const entries = yield* storage.getEntries("gap-base-version", 0);
567
+ yield* require_assertions.assertLength(entries, 1, "Should have version 4");
568
+ yield* require_assertions.assertEqual(entries[0].version, 4, "Entry should be version 4");
569
+ })
570
+ },
571
+ {
572
+ name: "appendWithCheck with baseVersion still detects gaps",
573
+ category: Categories.GapChecking,
574
+ run: effect.Effect.gen(function* () {
575
+ const storage = yield* require_HotStorage.HotStorageTag;
576
+ yield* storage.truncate("gap-base-detect", 5);
577
+ const result = yield* effect.Effect.either(storage.appendWithCheck("gap-base-detect", makeEntry(7), 7, 5));
578
+ yield* require_assertions.assertTrue(result._tag === "Left", "Should fail when skipping version 6");
579
+ if (result._tag === "Left") yield* require_assertions.assertTrue(result.left._tag === "WalVersionGapError", "Error should be WalVersionGapError");
580
+ })
581
+ },
582
+ {
583
+ name: "appendWithCheck with baseVersion=0 allows version 1",
584
+ category: Categories.GapChecking,
585
+ run: effect.Effect.gen(function* () {
586
+ const storage = yield* require_HotStorage.HotStorageTag;
587
+ yield* storage.appendWithCheck("gap-base-zero", makeEntry(1), 1, 0);
588
+ const entries = yield* storage.getEntries("gap-base-zero", 0);
589
+ yield* require_assertions.assertLength(entries, 1, "Should have version 1");
590
+ yield* require_assertions.assertEqual(entries[0].version, 1, "Entry should be version 1");
591
+ })
592
+ },
555
593
  {
556
594
  name: "OperationPath has _tag after roundtrip",
557
595
  category: Categories.TransactionEncoding,
@@ -1 +1 @@
1
- {"version":3,"file":"HotStorageTestSuite.d.cts","names":[],"sources":["../../src/testing/HotStorageTestSuite.ts"],"sourcesContent":[],"mappings":";;;;;;;AA4kCA;;;AAxC6B,KA3gCjB,mBAAA,GAAsB,SA2gCL,GA3gCiB,eA2gCjB,GA3gCmC,kBA2gCnC;AAWf,cAv/BD,UAu/BC,EAAA;EAAqB,SAAA,eAAA,EAAA,kBAAA;EAAjC,SAAA,gBAAA,EAAA,mBAAA;EAEA,SAAA,kBAAA,EAAA,qBAAA;EAHwB,SAAO,mBAAA,EAAA,uBAAA;EAAM,SAAA,iBAAA,EAAA,qBAAA;;;;;;;cA8B1B;;;;;;;;;;;;;mBAxCgB,gBAC3B,qBACA;gBAQwB,MAAA,CAAO,OAC/B,YAAY,qBAAqB,uBAEjC"}
1
+ {"version":3,"file":"HotStorageTestSuite.d.cts","names":[],"sources":["../../src/testing/HotStorageTestSuite.ts"],"sourcesContent":[],"mappings":";;;;;;;AAooCA;;;AAxC6B,KAnkCjB,mBAAA,GAAsB,SAmkCL,GAnkCiB,eAmkCjB,GAnkCmC,kBAmkCnC;AAWf,cA/iCD,UA+iCC,EAAA;EAAqB,SAAA,eAAA,EAAA,kBAAA;EAAjC,SAAA,gBAAA,EAAA,mBAAA;EAEA,SAAA,kBAAA,EAAA,qBAAA;EAHwB,SAAO,mBAAA,EAAA,uBAAA;EAAM,SAAA,iBAAA,EAAA,qBAAA;;;;;;;cA8B1B;;;;;;;;;;;;;mBAxCgB,gBAC3B,qBACA;gBAQwB,MAAA,CAAO,OAC/B,YAAY,qBAAqB,uBAEjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"HotStorageTestSuite.d.mts","names":[],"sources":["../../src/testing/HotStorageTestSuite.ts"],"sourcesContent":[],"mappings":";;;;;;;AA4kCA;;;AAxC6B,KA3gCjB,mBAAA,GAAsB,SA2gCL,GA3gCiB,eA2gCjB,GA3gCmC,kBA2gCnC;AAWf,cAv/BD,UAu/BC,EAAA;EAAqB,SAAA,eAAA,EAAA,kBAAA;EAAjC,SAAA,gBAAA,EAAA,mBAAA;EAEA,SAAA,kBAAA,EAAA,qBAAA;EAHwB,SAAO,mBAAA,EAAA,uBAAA;EAAM,SAAA,iBAAA,EAAA,qBAAA;;;;;;;cA8B1B;;;;;;;;;;;;;mBAxCgB,gBAC3B,qBACA;gBAQwB,MAAA,CAAO,OAC/B,YAAY,qBAAqB,uBAEjC"}
1
+ {"version":3,"file":"HotStorageTestSuite.d.mts","names":[],"sources":["../../src/testing/HotStorageTestSuite.ts"],"sourcesContent":[],"mappings":";;;;;;;AAooCA;;;AAxC6B,KAnkCjB,mBAAA,GAAsB,SAmkCL,GAnkCiB,eAmkCjB,GAnkCmC,kBAmkCnC;AAWf,cA/iCD,UA+iCC,EAAA;EAAqB,SAAA,eAAA,EAAA,kBAAA;EAAjC,SAAA,gBAAA,EAAA,mBAAA;EAEA,SAAA,kBAAA,EAAA,qBAAA;EAHwB,SAAO,mBAAA,EAAA,uBAAA;EAAM,SAAA,iBAAA,EAAA,qBAAA;;;;;;;cA8B1B;;;;;;;;;;;;;mBAxCgB,gBAC3B,qBACA;gBAQwB,MAAA,CAAO,OAC/B,YAAY,qBAAqB,uBAEjC"}
@@ -552,6 +552,44 @@ const tests = [
552
552
  yield* assertEqual(entries[1].version, 4, "Second should be version 4");
553
553
  })
554
554
  },
555
+ {
556
+ name: "appendWithCheck with baseVersion after full truncate works",
557
+ category: Categories.GapChecking,
558
+ run: Effect.gen(function* () {
559
+ const storage = yield* HotStorageTag;
560
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(1), 1, 0);
561
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(2), 2, 0);
562
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(3), 3, 0);
563
+ yield* storage.truncate("gap-base-version", 3);
564
+ yield* assertEmpty(yield* storage.getEntries("gap-base-version", 0), "WAL should be empty after full truncate");
565
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(4), 4, 3);
566
+ const entries = yield* storage.getEntries("gap-base-version", 0);
567
+ yield* assertLength(entries, 1, "Should have version 4");
568
+ yield* assertEqual(entries[0].version, 4, "Entry should be version 4");
569
+ })
570
+ },
571
+ {
572
+ name: "appendWithCheck with baseVersion still detects gaps",
573
+ category: Categories.GapChecking,
574
+ run: Effect.gen(function* () {
575
+ const storage = yield* HotStorageTag;
576
+ yield* storage.truncate("gap-base-detect", 5);
577
+ const result = yield* Effect.either(storage.appendWithCheck("gap-base-detect", makeEntry(7), 7, 5));
578
+ yield* assertTrue(result._tag === "Left", "Should fail when skipping version 6");
579
+ if (result._tag === "Left") yield* assertTrue(result.left._tag === "WalVersionGapError", "Error should be WalVersionGapError");
580
+ })
581
+ },
582
+ {
583
+ name: "appendWithCheck with baseVersion=0 allows version 1",
584
+ category: Categories.GapChecking,
585
+ run: Effect.gen(function* () {
586
+ const storage = yield* HotStorageTag;
587
+ yield* storage.appendWithCheck("gap-base-zero", makeEntry(1), 1, 0);
588
+ const entries = yield* storage.getEntries("gap-base-zero", 0);
589
+ yield* assertLength(entries, 1, "Should have version 1");
590
+ yield* assertEqual(entries[0].version, 1, "Entry should be version 1");
591
+ })
592
+ },
555
593
  {
556
594
  name: "OperationPath has _tag after roundtrip",
557
595
  category: Categories.TransactionEncoding,
@@ -1 +1 @@
1
- {"version":3,"file":"HotStorageTestSuite.mjs","names":["tests: StorageTestCase<HotStorageTestError, HotStorageTag>[]","entry: WalEntry","passed: StorageTestCase<HotStorageTestError, HotStorageTag>[]","failed: Array<{\n test: StorageTestCase<HotStorageTestError, HotStorageTag>;\n error: HotStorageTestError;\n }>"],"sources":["../../src/testing/HotStorageTestSuite.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect/testing - HotStorage Test Suite\n *\n * Comprehensive test suite for HotStorage (WAL) adapter implementations.\n * These tests verify that an adapter correctly implements the HotStorage interface\n * and can reliably store/retrieve WAL entries for document recovery.\n */\nimport { Effect, Schema } from \"effect\";\nimport { Transaction, OperationPath, Operation, OperationDefinition } from \"@voidhash/mimic\";\nimport { HotStorageTag } from \"../HotStorage\";\nimport { type HotStorageError, WalVersionGapError } from \"../Errors\";\nimport type { WalEntry } from \"../Types\";\nimport type { StorageTestCase, TestResults } from \"./types\";\nimport { TestError } from \"./types\";\nimport {\n assertEqual,\n assertLength,\n assertEmpty,\n assertSortedBy,\n assertTrue,\n} from \"./assertions\";\n\n/**\n * Error type for HotStorage tests - can be either a TestError, HotStorageError, or WalVersionGapError\n */\nexport type HotStorageTestError = TestError | HotStorageError | WalVersionGapError;\n\n// =============================================================================\n// Test Operation Definitions\n// =============================================================================\n\n/**\n * Test operation definition for creating proper Operation objects in tests.\n * Using Schema.Unknown allows any payload type for flexibility in testing.\n */\nconst TestSetDefinition = OperationDefinition.make({\n kind: \"test.set\" as const,\n payload: Schema.Unknown,\n target: Schema.Unknown,\n apply: (payload: unknown) => payload,\n});\n\n/**\n * Custom operation definition for testing operation kind preservation.\n */\nconst CustomOpDefinition = OperationDefinition.make({\n kind: \"custom.operation\" as const,\n payload: Schema.Unknown,\n target: Schema.Unknown,\n apply: (payload: unknown) => payload,\n});\n\n// =============================================================================\n// Categories\n// =============================================================================\n\nexport const Categories = {\n BasicOperations: \"Basic Operations\",\n VersionFiltering: \"Version Filtering\",\n OrderingGuarantees: \"Ordering Guarantees\",\n TruncationEdgeCases: \"Truncation Edge Cases\",\n WalEntryIntegrity: \"WAL Entry Integrity\",\n DocumentIsolation: \"Document Isolation\",\n LargeScaleOperations: \"Large-Scale Operations\",\n DocumentIdEdgeCases: \"Document ID Edge Cases\",\n GapChecking: \"Gap Checking\",\n TransactionEncoding: \"Transaction Encoding\",\n} as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\nconst makeEntry = (version: number, timestamp?: number): WalEntry => ({\n transaction: Transaction.make([]),\n version,\n timestamp: timestamp ?? Date.now(),\n});\n\nconst makeEntryWithData = (\n version: number,\n data: unknown,\n timestamp?: number\n): WalEntry => ({\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(\"data\"), TestSetDefinition, data),\n ]),\n version,\n timestamp: timestamp ?? Date.now(),\n});\n\nconst makeEntryWithPath = (\n version: number,\n pathString: string,\n payload: unknown,\n timestamp?: number\n): WalEntry => ({\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(pathString), TestSetDefinition, payload),\n ]),\n version,\n timestamp: timestamp ?? Date.now(),\n});\n\n// =============================================================================\n// Test Definitions\n// =============================================================================\n\nconst tests: StorageTestCase<HotStorageTestError, HotStorageTag>[] = [\n // ---------------------------------------------------------------------------\n // Basic Operations\n // ---------------------------------------------------------------------------\n {\n name: \"getEntries returns empty array for non-existent document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const result = yield* storage.getEntries(\"non-existent-hot-doc\", 0);\n yield* assertEmpty(result, \"Should return empty array for non-existent document\");\n }),\n },\n\n {\n name: \"append then getEntries returns the entry\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntry(1);\n yield* storage.append(\"basic-append\", entry);\n const entries = yield* storage.getEntries(\"basic-append\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(entries[0]!.version, 1, \"Entry version should match\");\n }),\n },\n\n {\n name: \"multiple append calls accumulate entries\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"multi-append\", makeEntry(1));\n yield* storage.append(\"multi-append\", makeEntry(2));\n yield* storage.append(\"multi-append\", makeEntry(3));\n const entries = yield* storage.getEntries(\"multi-append\", 0);\n yield* assertLength(entries, 3, \"Should have three entries\");\n }),\n },\n\n {\n name: \"truncate removes entries with version <= upToVersion\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"truncate-basic\", makeEntry(1));\n yield* storage.append(\"truncate-basic\", makeEntry(2));\n yield* storage.append(\"truncate-basic\", makeEntry(3));\n yield* storage.truncate(\"truncate-basic\", 2);\n const entries = yield* storage.getEntries(\"truncate-basic\", 0);\n yield* assertLength(entries, 1, \"Should have one entry after truncate\");\n yield* assertEqual(entries[0]!.version, 3, \"Only version 3 should remain\");\n }),\n },\n\n {\n name: \"truncate on non-existent document does not error\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.truncate(\"non-existent-truncate\", 100);\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Version Filtering (Critical for Recovery)\n // ---------------------------------------------------------------------------\n {\n name: \"getEntries(doc, 0) returns all entries\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-all\", makeEntry(1));\n yield* storage.append(\"filter-all\", makeEntry(2));\n yield* storage.append(\"filter-all\", makeEntry(3));\n const entries = yield* storage.getEntries(\"filter-all\", 0);\n yield* assertLength(entries, 3, \"sinceVersion=0 should return all entries\");\n }),\n },\n\n {\n name: \"getEntries(doc, n) returns only entries with version > n\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-n\", makeEntry(1));\n yield* storage.append(\"filter-n\", makeEntry(2));\n yield* storage.append(\"filter-n\", makeEntry(3));\n yield* storage.append(\"filter-n\", makeEntry(4));\n const entries = yield* storage.getEntries(\"filter-n\", 2);\n yield* assertLength(entries, 2, \"Should return entries with version > 2\");\n yield* assertEqual(entries[0]!.version, 3, \"First entry should be version 3\");\n yield* assertEqual(entries[1]!.version, 4, \"Second entry should be version 4\");\n }),\n },\n\n {\n name: \"getEntries(doc, exactVersion) excludes that exact version\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-exact\", makeEntry(5));\n yield* storage.append(\"filter-exact\", makeEntry(6));\n yield* storage.append(\"filter-exact\", makeEntry(7));\n const entries = yield* storage.getEntries(\"filter-exact\", 6);\n yield* assertLength(entries, 1, \"Should exclude version 6\");\n yield* assertEqual(entries[0]!.version, 7, \"Only version 7 should be returned\");\n }),\n },\n\n {\n name: \"getEntries(doc, maxVersion) returns empty array\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-max\", makeEntry(1));\n yield* storage.append(\"filter-max\", makeEntry(2));\n yield* storage.append(\"filter-max\", makeEntry(3));\n const entries = yield* storage.getEntries(\"filter-max\", 3);\n yield* assertEmpty(entries, \"sinceVersion >= maxVersion should return empty\");\n }),\n },\n\n {\n name: \"getEntries(doc, MAX_SAFE_INTEGER) returns empty array\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-huge\", makeEntry(1));\n yield* storage.append(\"filter-huge\", makeEntry(1000000));\n const entries = yield* storage.getEntries(\"filter-huge\", Number.MAX_SAFE_INTEGER);\n yield* assertEmpty(entries, \"sinceVersion=MAX_SAFE_INTEGER should return empty\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Ordering Guarantees\n // ---------------------------------------------------------------------------\n {\n name: \"entries returned sorted by version ascending\",\n category: Categories.OrderingGuarantees,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"order-test\", makeEntry(1));\n yield* storage.append(\"order-test\", makeEntry(2));\n yield* storage.append(\"order-test\", makeEntry(3));\n const entries = yield* storage.getEntries(\"order-test\", 0);\n yield* assertSortedBy(entries, \"version\", \"Entries should be sorted by version\");\n }),\n },\n\n {\n name: \"out-of-order appends are sorted correctly on retrieval\",\n category: Categories.OrderingGuarantees,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"ooo-test\", makeEntry(3));\n yield* storage.append(\"ooo-test\", makeEntry(1));\n yield* storage.append(\"ooo-test\", makeEntry(4));\n yield* storage.append(\"ooo-test\", makeEntry(2));\n const entries = yield* storage.getEntries(\"ooo-test\", 0);\n yield* assertLength(entries, 4, \"Should have all 4 entries\");\n yield* assertEqual(entries[0]!.version, 1, \"First should be version 1\");\n yield* assertEqual(entries[1]!.version, 2, \"Second should be version 2\");\n yield* assertEqual(entries[2]!.version, 3, \"Third should be version 3\");\n yield* assertEqual(entries[3]!.version, 4, \"Fourth should be version 4\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Truncation Edge Cases\n // ---------------------------------------------------------------------------\n {\n name: \"truncate(doc, 0) removes nothing (versions > 0 kept)\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-0\", makeEntry(1));\n yield* storage.append(\"trunc-0\", makeEntry(2));\n yield* storage.truncate(\"trunc-0\", 0);\n const entries = yield* storage.getEntries(\"trunc-0\", 0);\n yield* assertLength(entries, 2, \"truncate(0) should keep all entries with version > 0\");\n }),\n },\n\n {\n name: \"truncate(doc, maxVersion) removes all entries\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-all\", makeEntry(1));\n yield* storage.append(\"trunc-all\", makeEntry(2));\n yield* storage.append(\"trunc-all\", makeEntry(3));\n yield* storage.truncate(\"trunc-all\", 3);\n const entries = yield* storage.getEntries(\"trunc-all\", 0);\n yield* assertEmpty(entries, \"truncate(maxVersion) should remove all entries\");\n }),\n },\n\n {\n name: \"truncate(doc, middleVersion) removes correct entries\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-mid\", makeEntry(1));\n yield* storage.append(\"trunc-mid\", makeEntry(2));\n yield* storage.append(\"trunc-mid\", makeEntry(3));\n yield* storage.append(\"trunc-mid\", makeEntry(4));\n yield* storage.append(\"trunc-mid\", makeEntry(5));\n yield* storage.truncate(\"trunc-mid\", 3);\n const entries = yield* storage.getEntries(\"trunc-mid\", 0);\n yield* assertLength(entries, 2, \"Should keep versions 4 and 5\");\n yield* assertEqual(entries[0]!.version, 4, \"First remaining should be 4\");\n yield* assertEqual(entries[1]!.version, 5, \"Second remaining should be 5\");\n }),\n },\n\n {\n name: \"multiple truncates work correctly\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"multi-trunc\", makeEntry(1));\n yield* storage.append(\"multi-trunc\", makeEntry(2));\n yield* storage.append(\"multi-trunc\", makeEntry(3));\n yield* storage.append(\"multi-trunc\", makeEntry(4));\n yield* storage.append(\"multi-trunc\", makeEntry(5));\n yield* storage.truncate(\"multi-trunc\", 2);\n yield* storage.truncate(\"multi-trunc\", 4);\n const entries = yield* storage.getEntries(\"multi-trunc\", 0);\n yield* assertLength(entries, 1, \"Should only have version 5\");\n yield* assertEqual(entries[0]!.version, 5, \"Only version 5 should remain\");\n }),\n },\n\n {\n name: \"truncate followed by append works correctly\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-append\", makeEntry(1));\n yield* storage.append(\"trunc-append\", makeEntry(2));\n yield* storage.truncate(\"trunc-append\", 2);\n yield* storage.append(\"trunc-append\", makeEntry(3));\n yield* storage.append(\"trunc-append\", makeEntry(4));\n const entries = yield* storage.getEntries(\"trunc-append\", 0);\n yield* assertLength(entries, 2, \"Should have versions 3 and 4\");\n yield* assertEqual(entries[0]!.version, 3, \"First should be 3\");\n yield* assertEqual(entries[1]!.version, 4, \"Second should be 4\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // WAL Entry Data Integrity\n // ---------------------------------------------------------------------------\n {\n name: \"transaction data is preserved exactly\",\n category: Categories.WalEntryIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithData(1, { key: \"value\", nested: { a: 1 } });\n yield* storage.append(\"tx-data\", entry);\n const entries = yield* storage.getEntries(\"tx-data\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction,\n entry.transaction,\n \"Transaction should be preserved exactly\"\n );\n }),\n },\n\n {\n name: \"version number is preserved exactly\",\n category: Categories.WalEntryIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const version = 42;\n const entry = makeEntry(version);\n yield* storage.append(\"version-preserve\", entry);\n const entries = yield* storage.getEntries(\"version-preserve\", 0);\n yield* assertEqual(entries[0]!.version, version, \"Version should be preserved exactly\");\n }),\n },\n\n {\n name: \"timestamp is preserved exactly\",\n category: Categories.WalEntryIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const timestamp = 1704067200000;\n const entry = makeEntry(1, timestamp);\n yield* storage.append(\"timestamp-preserve\", entry);\n const entries = yield* storage.getEntries(\"timestamp-preserve\", 0);\n yield* assertEqual(\n entries[0]!.timestamp,\n timestamp,\n \"Timestamp should be preserved exactly\"\n );\n }),\n },\n\n {\n name: \"complex transaction operations survive roundtrip\",\n category: Categories.WalEntryIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry: WalEntry = {\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(\"a\"), TestSetDefinition, 1),\n Operation.fromDefinition(OperationPath.make(\"b/c\"), TestSetDefinition, \"nested\"),\n Operation.fromDefinition(OperationPath.make(\"arr\"), TestSetDefinition, [1, 2, 3]),\n ]),\n version: 1,\n timestamp: Date.now(),\n };\n yield* storage.append(\"complex-tx\", entry);\n const entries = yield* storage.getEntries(\"complex-tx\", 0);\n yield* assertEqual(\n entries[0]!.transaction,\n entry.transaction,\n \"Complex transaction should survive roundtrip\"\n );\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Document Isolation\n // ---------------------------------------------------------------------------\n {\n name: \"different documents have independent entry lists\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"iso-hot-1\", makeEntry(1));\n yield* storage.append(\"iso-hot-1\", makeEntry(2));\n yield* storage.append(\"iso-hot-2\", makeEntry(10));\n const entries1 = yield* storage.getEntries(\"iso-hot-1\", 0);\n const entries2 = yield* storage.getEntries(\"iso-hot-2\", 0);\n yield* assertLength(entries1, 2, \"Doc 1 should have 2 entries\");\n yield* assertLength(entries2, 1, \"Doc 2 should have 1 entry\");\n yield* assertEqual(entries1[0]!.version, 1, \"Doc 1 first entry version\");\n yield* assertEqual(entries2[0]!.version, 10, \"Doc 2 first entry version\");\n }),\n },\n\n {\n name: \"appending to one doc does not affect others\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"append-iso-1\", makeEntry(1));\n const beforeAppend = yield* storage.getEntries(\"append-iso-1\", 0);\n yield* storage.append(\"append-iso-2\", makeEntry(100));\n yield* storage.append(\"append-iso-2\", makeEntry(101));\n const afterAppend = yield* storage.getEntries(\"append-iso-1\", 0);\n yield* assertEqual(\n beforeAppend.length,\n afterAppend.length,\n \"Appending to doc 2 should not affect doc 1\"\n );\n }),\n },\n\n {\n name: \"truncating one doc does not affect others\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-iso-1\", makeEntry(1));\n yield* storage.append(\"trunc-iso-1\", makeEntry(2));\n yield* storage.append(\"trunc-iso-2\", makeEntry(1));\n yield* storage.append(\"trunc-iso-2\", makeEntry(2));\n yield* storage.truncate(\"trunc-iso-1\", 2);\n const entries1 = yield* storage.getEntries(\"trunc-iso-1\", 0);\n const entries2 = yield* storage.getEntries(\"trunc-iso-2\", 0);\n yield* assertEmpty(entries1, \"Doc 1 should be empty after truncate\");\n yield* assertLength(entries2, 2, \"Doc 2 should still have 2 entries\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Large-Scale Operations\n // ---------------------------------------------------------------------------\n {\n name: \"handle 1000+ entries per document\",\n category: Categories.LargeScaleOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const count = 1000;\n for (let i = 1; i <= count; i++) {\n yield* storage.append(\"large-entries\", makeEntry(i));\n }\n const entries = yield* storage.getEntries(\"large-entries\", 0);\n yield* assertLength(entries, count, `Should have ${count} entries`);\n yield* assertSortedBy(entries, \"version\", \"Should be sorted by version\");\n yield* assertEqual(entries[0]!.version, 1, \"First should be version 1\");\n yield* assertEqual(entries[count - 1]!.version, count, `Last should be version ${count}`);\n }),\n },\n\n {\n name: \"handle 100+ documents\",\n category: Categories.LargeScaleOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const docCount = 100;\n for (let i = 0; i < docCount; i++) {\n yield* storage.append(`multi-doc-${i}`, makeEntry(1));\n yield* storage.append(`multi-doc-${i}`, makeEntry(2));\n }\n let totalEntries = 0;\n for (let i = 0; i < docCount; i++) {\n const entries = yield* storage.getEntries(`multi-doc-${i}`, 0);\n totalEntries += entries.length;\n yield* assertLength(entries, 2, `Doc ${i} should have 2 entries`);\n }\n yield* assertEqual(totalEntries, docCount * 2, \"Total entries should match\");\n }),\n },\n\n {\n name: \"large transaction data (10KB+) survives roundtrip\",\n category: Categories.LargeScaleOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const largeData = \"x\".repeat(10 * 1024);\n const entry = makeEntryWithData(1, { content: largeData });\n yield* storage.append(\"large-tx\", entry);\n const entries = yield* storage.getEntries(\"large-tx\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction,\n entry.transaction,\n \"Large transaction data should survive roundtrip\"\n );\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Document ID Edge Cases\n // ---------------------------------------------------------------------------\n {\n name: \"long documentId (1000+ chars) works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const longId = \"h\".repeat(1000);\n const entry = makeEntry(1);\n yield* storage.append(longId, entry);\n const entries = yield* storage.getEntries(longId, 0);\n yield* assertLength(entries, 1, \"Long documentId should work\");\n }),\n },\n\n {\n name: \"unicode documentId works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const unicodeId = \"hot-doc-id\";\n const entry = makeEntry(1);\n yield* storage.append(unicodeId, entry);\n const entries = yield* storage.getEntries(unicodeId, 0);\n yield* assertLength(entries, 1, \"Unicode documentId should work\");\n }),\n },\n\n {\n name: \"documentId with special chars works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const specialId = \"hot/path:to.wal\";\n const entry = makeEntry(1);\n yield* storage.append(specialId, entry);\n const entries = yield* storage.getEntries(specialId, 0);\n yield* assertLength(entries, 1, \"DocumentId with special chars should work\");\n }),\n },\n\n {\n name: \"documentId with spaces works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const spacedId = \"hot doc with spaces\";\n const entry = makeEntry(1);\n yield* storage.append(spacedId, entry);\n const entries = yield* storage.getEntries(spacedId, 0);\n yield* assertLength(entries, 1, \"DocumentId with spaces should work\");\n }),\n },\n\n {\n name: \"version 0 entry is handled correctly\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"version-0-entry\", makeEntry(0));\n yield* storage.append(\"version-0-entry\", makeEntry(1));\n const entriesFromNeg = yield* storage.getEntries(\"version-0-entry\", -1);\n yield* assertTrue(\n entriesFromNeg.some((e) => e.version === 0),\n \"Version 0 entry should be retrievable with sinceVersion < 0\"\n );\n const entriesFrom0 = yield* storage.getEntries(\"version-0-entry\", 0);\n yield* assertTrue(\n !entriesFrom0.some((e) => e.version === 0),\n \"Version 0 entry should be excluded with sinceVersion = 0\"\n );\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Gap Checking (appendWithCheck)\n // ---------------------------------------------------------------------------\n {\n name: \"appendWithCheck succeeds for first entry (expectedVersion=1)\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntry(1);\n yield* storage.appendWithCheck(\"gap-check-first\", entry, 1);\n const entries = yield* storage.getEntries(\"gap-check-first\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(entries[0]!.version, 1, \"Entry version should be 1\");\n }),\n },\n\n {\n name: \"appendWithCheck succeeds for consecutive versions\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.appendWithCheck(\"gap-check-consecutive\", makeEntry(1), 1);\n yield* storage.appendWithCheck(\"gap-check-consecutive\", makeEntry(2), 2);\n yield* storage.appendWithCheck(\"gap-check-consecutive\", makeEntry(3), 3);\n const entries = yield* storage.getEntries(\"gap-check-consecutive\", 0);\n yield* assertLength(entries, 3, \"Should have three entries\");\n yield* assertEqual(entries[0]!.version, 1, \"First entry version should be 1\");\n yield* assertEqual(entries[1]!.version, 2, \"Second entry version should be 2\");\n yield* assertEqual(entries[2]!.version, 3, \"Third entry version should be 3\");\n }),\n },\n\n {\n name: \"appendWithCheck fails for version gap (skipping version 2)\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.appendWithCheck(\"gap-check-fail\", makeEntry(1), 1);\n // Attempt to append version 3, skipping version 2\n const result = yield* Effect.either(\n storage.appendWithCheck(\"gap-check-fail\", makeEntry(3), 3)\n );\n yield* assertTrue(\n result._tag === \"Left\",\n \"appendWithCheck should fail when there's a version gap\"\n );\n if (result._tag === \"Left\") {\n yield* assertTrue(\n result.left._tag === \"WalVersionGapError\",\n \"Error should be WalVersionGapError\"\n );\n }\n // Verify version 3 was not appended\n const entries = yield* storage.getEntries(\"gap-check-fail\", 0);\n yield* assertLength(entries, 1, \"Should only have version 1\");\n yield* assertEqual(entries[0]!.version, 1, \"Only version 1 should exist\");\n }),\n },\n\n {\n name: \"appendWithCheck fails if first entry is not version 1\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n // Attempt to append version 2 as first entry (expecting gap error)\n const result = yield* Effect.either(\n storage.appendWithCheck(\"gap-check-not-first\", makeEntry(2), 2)\n );\n yield* assertTrue(\n result._tag === \"Left\",\n \"appendWithCheck should fail when first entry is not version 1\"\n );\n if (result._tag === \"Left\") {\n yield* assertTrue(\n result.left._tag === \"WalVersionGapError\",\n \"Error should be WalVersionGapError\"\n );\n }\n // Verify nothing was appended\n const entries = yield* storage.getEntries(\"gap-check-not-first\", 0);\n yield* assertEmpty(entries, \"No entries should exist after failed append\");\n }),\n },\n\n {\n name: \"appendWithCheck fails when entry already exists at expectedVersion\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.appendWithCheck(\"gap-check-duplicate\", makeEntry(1), 1);\n // Attempt to append another version 1\n const result = yield* Effect.either(\n storage.appendWithCheck(\"gap-check-duplicate\", makeEntry(1), 1)\n );\n yield* assertTrue(\n result._tag === \"Left\",\n \"appendWithCheck should fail when version already exists\"\n );\n // Verify still only one entry\n const entries = yield* storage.getEntries(\"gap-check-duplicate\", 0);\n yield* assertLength(entries, 1, \"Should still only have one entry\");\n }),\n },\n\n {\n name: \"appendWithCheck after truncate works correctly\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n // Append versions 1, 2, 3\n yield* storage.appendWithCheck(\"gap-check-truncate\", makeEntry(1), 1);\n yield* storage.appendWithCheck(\"gap-check-truncate\", makeEntry(2), 2);\n yield* storage.appendWithCheck(\"gap-check-truncate\", makeEntry(3), 3);\n // Truncate up to version 2\n yield* storage.truncate(\"gap-check-truncate\", 2);\n // Now append version 4 (should succeed since last entry is version 3)\n yield* storage.appendWithCheck(\"gap-check-truncate\", makeEntry(4), 4);\n const entries = yield* storage.getEntries(\"gap-check-truncate\", 0);\n yield* assertLength(entries, 2, \"Should have versions 3 and 4\");\n yield* assertEqual(entries[0]!.version, 3, \"First should be version 3\");\n yield* assertEqual(entries[1]!.version, 4, \"Second should be version 4\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Transaction Encoding (Critical for OperationPath preservation)\n // ---------------------------------------------------------------------------\n {\n name: \"OperationPath has _tag after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0/name\", \"Alice\");\n yield* storage.append(\"op-path-tag\", entry);\n const entries = yield* storage.getEntries(\"op-path-tag\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n op.path._tag === \"OperationPath\",\n \"path should have _tag 'OperationPath'\"\n );\n }),\n },\n\n {\n name: \"OperationPath.toTokens() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0/name\", \"Alice\");\n yield* storage.append(\"op-path-tokens\", entry);\n const entries = yield* storage.getEntries(\"op-path-tokens\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.toTokens === \"function\",\n \"path.toTokens should be a function\"\n );\n const tokens = op.path.toTokens();\n yield* assertEqual(\n tokens,\n [\"users\", \"0\", \"name\"],\n \"toTokens() should return correct path tokens\"\n );\n }),\n },\n\n {\n name: \"OperationPath.concat() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0\", { name: \"Alice\" });\n yield* storage.append(\"op-path-concat\", entry);\n const entries = yield* storage.getEntries(\"op-path-concat\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.concat === \"function\",\n \"path.concat should be a function\"\n );\n const extended = op.path.concat(OperationPath.make(\"name\"));\n yield* assertEqual(\n extended.toTokens(),\n [\"users\", \"0\", \"name\"],\n \"concat() should work correctly\"\n );\n }),\n },\n\n {\n name: \"OperationPath.append() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users\", []);\n yield* storage.append(\"op-path-append\", entry);\n const entries = yield* storage.getEntries(\"op-path-append\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.append === \"function\",\n \"path.append should be a function\"\n );\n const extended = op.path.append(\"0\");\n yield* assertEqual(\n extended.toTokens(),\n [\"users\", \"0\"],\n \"append() should work correctly\"\n );\n }),\n },\n\n {\n name: \"OperationPath.pop() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0/name\", \"Alice\");\n yield* storage.append(\"op-path-pop\", entry);\n const entries = yield* storage.getEntries(\"op-path-pop\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.pop === \"function\",\n \"path.pop should be a function\"\n );\n const popped = op.path.pop();\n yield* assertEqual(\n popped.toTokens(),\n [\"users\", \"0\"],\n \"pop() should remove last token\"\n );\n }),\n },\n\n {\n name: \"OperationPath.shift() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0/name\", \"Alice\");\n yield* storage.append(\"op-path-shift\", entry);\n const entries = yield* storage.getEntries(\"op-path-shift\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.shift === \"function\",\n \"path.shift should be a function\"\n );\n const shifted = op.path.shift();\n yield* assertEqual(\n shifted.toTokens(),\n [\"0\", \"name\"],\n \"shift() should remove first token\"\n );\n }),\n },\n\n {\n name: \"transaction with multiple operations preserves all OperationPaths\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry: WalEntry = {\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(\"users/0/name\"), TestSetDefinition, \"Alice\"),\n Operation.fromDefinition(OperationPath.make(\"users/1/name\"), TestSetDefinition, \"Bob\"),\n Operation.fromDefinition(OperationPath.make(\"count\"), TestSetDefinition, 2),\n ]),\n version: 1,\n timestamp: Date.now(),\n };\n yield* storage.append(\"multi-op-paths\", entry);\n const entries = yield* storage.getEntries(\"multi-op-paths\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const ops = entries[0]!.transaction.ops;\n yield* assertLength([...ops], 3, \"Should have 3 operations\");\n // Verify all paths have working methods\n for (const op of ops) {\n yield* assertTrue(\n op.path._tag === \"OperationPath\",\n \"Each operation path should have _tag\"\n );\n yield* assertTrue(\n typeof op.path.toTokens === \"function\",\n \"Each operation path should have toTokens method\"\n );\n }\n yield* assertEqual(\n ops[0]!.path.toTokens(),\n [\"users\", \"0\", \"name\"],\n \"First path should be correct\"\n );\n yield* assertEqual(\n ops[1]!.path.toTokens(),\n [\"users\", \"1\", \"name\"],\n \"Second path should be correct\"\n );\n yield* assertEqual(\n ops[2]!.path.toTokens(),\n [\"count\"],\n \"Third path should be correct\"\n );\n }),\n },\n\n {\n name: \"nested path with many segments survives roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const deepPath = \"level1/level2/level3/level4/level5\";\n const entry = makeEntryWithPath(1, deepPath, \"deep value\");\n yield* storage.append(\"deep-path\", entry);\n const entries = yield* storage.getEntries(\"deep-path\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertEqual(\n op.path.toTokens(),\n [\"level1\", \"level2\", \"level3\", \"level4\", \"level5\"],\n \"Deep nested path should survive roundtrip\"\n );\n }),\n },\n\n {\n name: \"empty path survives roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"\", { root: true });\n yield* storage.append(\"empty-path\", entry);\n const entries = yield* storage.getEntries(\"empty-path\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n op.path._tag === \"OperationPath\",\n \"Empty path should still be OperationPath\"\n );\n yield* assertTrue(\n typeof op.path.toTokens === \"function\",\n \"Empty path should have toTokens method\"\n );\n }),\n },\n\n {\n name: \"transaction id is preserved after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"test\", \"value\");\n const originalId = entry.transaction.id;\n yield* storage.append(\"tx-id-preserve\", entry);\n const entries = yield* storage.getEntries(\"tx-id-preserve\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction.id,\n originalId,\n \"Transaction id should be preserved\"\n );\n }),\n },\n\n {\n name: \"transaction timestamp is preserved after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"test\", \"value\");\n const originalTimestamp = entry.transaction.timestamp;\n yield* storage.append(\"tx-timestamp-preserve\", entry);\n const entries = yield* storage.getEntries(\"tx-timestamp-preserve\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction.timestamp,\n originalTimestamp,\n \"Transaction timestamp should be preserved\"\n );\n }),\n },\n\n {\n name: \"operation kind is preserved after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry: WalEntry = {\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(\"data\"), CustomOpDefinition, \"test\"),\n ]),\n version: 1,\n timestamp: Date.now(),\n };\n yield* storage.append(\"op-kind-preserve\", entry);\n const entries = yield* storage.getEntries(\"op-kind-preserve\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction.ops[0]!.kind,\n \"custom.operation\",\n \"Operation kind should be preserved\"\n );\n }),\n },\n\n {\n name: \"operation payload with complex object survives roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const complexPayload = {\n nested: { value: 42, array: [1, 2, 3] },\n nullValue: null,\n string: \"test\",\n };\n const entry = makeEntryWithPath(1, \"data\", complexPayload);\n yield* storage.append(\"complex-payload\", entry);\n const entries = yield* storage.getEntries(\"complex-payload\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction.ops[0]!.payload,\n complexPayload,\n \"Complex payload should survive roundtrip\"\n );\n }),\n },\n];\n\n// =============================================================================\n// Exports\n// =============================================================================\n\n/**\n * Get all HotStorage test cases.\n *\n * @returns Array of test cases that require HotStorageTag\n */\nexport const makeTests = (): StorageTestCase<\n HotStorageTestError,\n HotStorageTag\n>[] => tests;\n\n/**\n * Run all tests and collect results.\n *\n * @returns Effect that produces TestResults\n */\nexport const runAll = (): Effect.Effect<\n TestResults<HotStorageTestError, HotStorageTag>,\n never,\n HotStorageTag\n> =>\n Effect.gen(function* () {\n const passed: StorageTestCase<HotStorageTestError, HotStorageTag>[] = [];\n const failed: Array<{\n test: StorageTestCase<HotStorageTestError, HotStorageTag>;\n error: HotStorageTestError;\n }> = [];\n\n for (const test of tests) {\n const result = yield* Effect.either(test.run);\n if (result._tag === \"Right\") {\n passed.push(test);\n } else {\n failed.push({ test, error: result.left });\n }\n }\n\n return {\n passed,\n failed,\n total: tests.length,\n passCount: passed.length,\n failCount: failed.length,\n };\n });\n\nexport const HotStorageTestSuite = {\n Categories,\n makeTests,\n runAll,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"HotStorageTestSuite.mjs","names":["tests: StorageTestCase<HotStorageTestError, HotStorageTag>[]","entry: WalEntry","passed: StorageTestCase<HotStorageTestError, HotStorageTag>[]","failed: Array<{\n test: StorageTestCase<HotStorageTestError, HotStorageTag>;\n error: HotStorageTestError;\n }>"],"sources":["../../src/testing/HotStorageTestSuite.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect/testing - HotStorage Test Suite\n *\n * Comprehensive test suite for HotStorage (WAL) adapter implementations.\n * These tests verify that an adapter correctly implements the HotStorage interface\n * and can reliably store/retrieve WAL entries for document recovery.\n */\nimport { Effect, Schema } from \"effect\";\nimport { Transaction, OperationPath, Operation, OperationDefinition } from \"@voidhash/mimic\";\nimport { HotStorageTag } from \"../HotStorage\";\nimport { type HotStorageError, WalVersionGapError } from \"../Errors\";\nimport type { WalEntry } from \"../Types\";\nimport type { StorageTestCase, TestResults } from \"./types\";\nimport { TestError } from \"./types\";\nimport {\n assertEqual,\n assertLength,\n assertEmpty,\n assertSortedBy,\n assertTrue,\n} from \"./assertions\";\n\n/**\n * Error type for HotStorage tests - can be either a TestError, HotStorageError, or WalVersionGapError\n */\nexport type HotStorageTestError = TestError | HotStorageError | WalVersionGapError;\n\n// =============================================================================\n// Test Operation Definitions\n// =============================================================================\n\n/**\n * Test operation definition for creating proper Operation objects in tests.\n * Using Schema.Unknown allows any payload type for flexibility in testing.\n */\nconst TestSetDefinition = OperationDefinition.make({\n kind: \"test.set\" as const,\n payload: Schema.Unknown,\n target: Schema.Unknown,\n apply: (payload: unknown) => payload,\n});\n\n/**\n * Custom operation definition for testing operation kind preservation.\n */\nconst CustomOpDefinition = OperationDefinition.make({\n kind: \"custom.operation\" as const,\n payload: Schema.Unknown,\n target: Schema.Unknown,\n apply: (payload: unknown) => payload,\n});\n\n// =============================================================================\n// Categories\n// =============================================================================\n\nexport const Categories = {\n BasicOperations: \"Basic Operations\",\n VersionFiltering: \"Version Filtering\",\n OrderingGuarantees: \"Ordering Guarantees\",\n TruncationEdgeCases: \"Truncation Edge Cases\",\n WalEntryIntegrity: \"WAL Entry Integrity\",\n DocumentIsolation: \"Document Isolation\",\n LargeScaleOperations: \"Large-Scale Operations\",\n DocumentIdEdgeCases: \"Document ID Edge Cases\",\n GapChecking: \"Gap Checking\",\n TransactionEncoding: \"Transaction Encoding\",\n} as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\nconst makeEntry = (version: number, timestamp?: number): WalEntry => ({\n transaction: Transaction.make([]),\n version,\n timestamp: timestamp ?? Date.now(),\n});\n\nconst makeEntryWithData = (\n version: number,\n data: unknown,\n timestamp?: number\n): WalEntry => ({\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(\"data\"), TestSetDefinition, data),\n ]),\n version,\n timestamp: timestamp ?? Date.now(),\n});\n\nconst makeEntryWithPath = (\n version: number,\n pathString: string,\n payload: unknown,\n timestamp?: number\n): WalEntry => ({\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(pathString), TestSetDefinition, payload),\n ]),\n version,\n timestamp: timestamp ?? Date.now(),\n});\n\n// =============================================================================\n// Test Definitions\n// =============================================================================\n\nconst tests: StorageTestCase<HotStorageTestError, HotStorageTag>[] = [\n // ---------------------------------------------------------------------------\n // Basic Operations\n // ---------------------------------------------------------------------------\n {\n name: \"getEntries returns empty array for non-existent document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const result = yield* storage.getEntries(\"non-existent-hot-doc\", 0);\n yield* assertEmpty(result, \"Should return empty array for non-existent document\");\n }),\n },\n\n {\n name: \"append then getEntries returns the entry\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntry(1);\n yield* storage.append(\"basic-append\", entry);\n const entries = yield* storage.getEntries(\"basic-append\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(entries[0]!.version, 1, \"Entry version should match\");\n }),\n },\n\n {\n name: \"multiple append calls accumulate entries\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"multi-append\", makeEntry(1));\n yield* storage.append(\"multi-append\", makeEntry(2));\n yield* storage.append(\"multi-append\", makeEntry(3));\n const entries = yield* storage.getEntries(\"multi-append\", 0);\n yield* assertLength(entries, 3, \"Should have three entries\");\n }),\n },\n\n {\n name: \"truncate removes entries with version <= upToVersion\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"truncate-basic\", makeEntry(1));\n yield* storage.append(\"truncate-basic\", makeEntry(2));\n yield* storage.append(\"truncate-basic\", makeEntry(3));\n yield* storage.truncate(\"truncate-basic\", 2);\n const entries = yield* storage.getEntries(\"truncate-basic\", 0);\n yield* assertLength(entries, 1, \"Should have one entry after truncate\");\n yield* assertEqual(entries[0]!.version, 3, \"Only version 3 should remain\");\n }),\n },\n\n {\n name: \"truncate on non-existent document does not error\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.truncate(\"non-existent-truncate\", 100);\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Version Filtering (Critical for Recovery)\n // ---------------------------------------------------------------------------\n {\n name: \"getEntries(doc, 0) returns all entries\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-all\", makeEntry(1));\n yield* storage.append(\"filter-all\", makeEntry(2));\n yield* storage.append(\"filter-all\", makeEntry(3));\n const entries = yield* storage.getEntries(\"filter-all\", 0);\n yield* assertLength(entries, 3, \"sinceVersion=0 should return all entries\");\n }),\n },\n\n {\n name: \"getEntries(doc, n) returns only entries with version > n\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-n\", makeEntry(1));\n yield* storage.append(\"filter-n\", makeEntry(2));\n yield* storage.append(\"filter-n\", makeEntry(3));\n yield* storage.append(\"filter-n\", makeEntry(4));\n const entries = yield* storage.getEntries(\"filter-n\", 2);\n yield* assertLength(entries, 2, \"Should return entries with version > 2\");\n yield* assertEqual(entries[0]!.version, 3, \"First entry should be version 3\");\n yield* assertEqual(entries[1]!.version, 4, \"Second entry should be version 4\");\n }),\n },\n\n {\n name: \"getEntries(doc, exactVersion) excludes that exact version\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-exact\", makeEntry(5));\n yield* storage.append(\"filter-exact\", makeEntry(6));\n yield* storage.append(\"filter-exact\", makeEntry(7));\n const entries = yield* storage.getEntries(\"filter-exact\", 6);\n yield* assertLength(entries, 1, \"Should exclude version 6\");\n yield* assertEqual(entries[0]!.version, 7, \"Only version 7 should be returned\");\n }),\n },\n\n {\n name: \"getEntries(doc, maxVersion) returns empty array\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-max\", makeEntry(1));\n yield* storage.append(\"filter-max\", makeEntry(2));\n yield* storage.append(\"filter-max\", makeEntry(3));\n const entries = yield* storage.getEntries(\"filter-max\", 3);\n yield* assertEmpty(entries, \"sinceVersion >= maxVersion should return empty\");\n }),\n },\n\n {\n name: \"getEntries(doc, MAX_SAFE_INTEGER) returns empty array\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"filter-huge\", makeEntry(1));\n yield* storage.append(\"filter-huge\", makeEntry(1000000));\n const entries = yield* storage.getEntries(\"filter-huge\", Number.MAX_SAFE_INTEGER);\n yield* assertEmpty(entries, \"sinceVersion=MAX_SAFE_INTEGER should return empty\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Ordering Guarantees\n // ---------------------------------------------------------------------------\n {\n name: \"entries returned sorted by version ascending\",\n category: Categories.OrderingGuarantees,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"order-test\", makeEntry(1));\n yield* storage.append(\"order-test\", makeEntry(2));\n yield* storage.append(\"order-test\", makeEntry(3));\n const entries = yield* storage.getEntries(\"order-test\", 0);\n yield* assertSortedBy(entries, \"version\", \"Entries should be sorted by version\");\n }),\n },\n\n {\n name: \"out-of-order appends are sorted correctly on retrieval\",\n category: Categories.OrderingGuarantees,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"ooo-test\", makeEntry(3));\n yield* storage.append(\"ooo-test\", makeEntry(1));\n yield* storage.append(\"ooo-test\", makeEntry(4));\n yield* storage.append(\"ooo-test\", makeEntry(2));\n const entries = yield* storage.getEntries(\"ooo-test\", 0);\n yield* assertLength(entries, 4, \"Should have all 4 entries\");\n yield* assertEqual(entries[0]!.version, 1, \"First should be version 1\");\n yield* assertEqual(entries[1]!.version, 2, \"Second should be version 2\");\n yield* assertEqual(entries[2]!.version, 3, \"Third should be version 3\");\n yield* assertEqual(entries[3]!.version, 4, \"Fourth should be version 4\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Truncation Edge Cases\n // ---------------------------------------------------------------------------\n {\n name: \"truncate(doc, 0) removes nothing (versions > 0 kept)\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-0\", makeEntry(1));\n yield* storage.append(\"trunc-0\", makeEntry(2));\n yield* storage.truncate(\"trunc-0\", 0);\n const entries = yield* storage.getEntries(\"trunc-0\", 0);\n yield* assertLength(entries, 2, \"truncate(0) should keep all entries with version > 0\");\n }),\n },\n\n {\n name: \"truncate(doc, maxVersion) removes all entries\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-all\", makeEntry(1));\n yield* storage.append(\"trunc-all\", makeEntry(2));\n yield* storage.append(\"trunc-all\", makeEntry(3));\n yield* storage.truncate(\"trunc-all\", 3);\n const entries = yield* storage.getEntries(\"trunc-all\", 0);\n yield* assertEmpty(entries, \"truncate(maxVersion) should remove all entries\");\n }),\n },\n\n {\n name: \"truncate(doc, middleVersion) removes correct entries\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-mid\", makeEntry(1));\n yield* storage.append(\"trunc-mid\", makeEntry(2));\n yield* storage.append(\"trunc-mid\", makeEntry(3));\n yield* storage.append(\"trunc-mid\", makeEntry(4));\n yield* storage.append(\"trunc-mid\", makeEntry(5));\n yield* storage.truncate(\"trunc-mid\", 3);\n const entries = yield* storage.getEntries(\"trunc-mid\", 0);\n yield* assertLength(entries, 2, \"Should keep versions 4 and 5\");\n yield* assertEqual(entries[0]!.version, 4, \"First remaining should be 4\");\n yield* assertEqual(entries[1]!.version, 5, \"Second remaining should be 5\");\n }),\n },\n\n {\n name: \"multiple truncates work correctly\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"multi-trunc\", makeEntry(1));\n yield* storage.append(\"multi-trunc\", makeEntry(2));\n yield* storage.append(\"multi-trunc\", makeEntry(3));\n yield* storage.append(\"multi-trunc\", makeEntry(4));\n yield* storage.append(\"multi-trunc\", makeEntry(5));\n yield* storage.truncate(\"multi-trunc\", 2);\n yield* storage.truncate(\"multi-trunc\", 4);\n const entries = yield* storage.getEntries(\"multi-trunc\", 0);\n yield* assertLength(entries, 1, \"Should only have version 5\");\n yield* assertEqual(entries[0]!.version, 5, \"Only version 5 should remain\");\n }),\n },\n\n {\n name: \"truncate followed by append works correctly\",\n category: Categories.TruncationEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-append\", makeEntry(1));\n yield* storage.append(\"trunc-append\", makeEntry(2));\n yield* storage.truncate(\"trunc-append\", 2);\n yield* storage.append(\"trunc-append\", makeEntry(3));\n yield* storage.append(\"trunc-append\", makeEntry(4));\n const entries = yield* storage.getEntries(\"trunc-append\", 0);\n yield* assertLength(entries, 2, \"Should have versions 3 and 4\");\n yield* assertEqual(entries[0]!.version, 3, \"First should be 3\");\n yield* assertEqual(entries[1]!.version, 4, \"Second should be 4\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // WAL Entry Data Integrity\n // ---------------------------------------------------------------------------\n {\n name: \"transaction data is preserved exactly\",\n category: Categories.WalEntryIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithData(1, { key: \"value\", nested: { a: 1 } });\n yield* storage.append(\"tx-data\", entry);\n const entries = yield* storage.getEntries(\"tx-data\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction,\n entry.transaction,\n \"Transaction should be preserved exactly\"\n );\n }),\n },\n\n {\n name: \"version number is preserved exactly\",\n category: Categories.WalEntryIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const version = 42;\n const entry = makeEntry(version);\n yield* storage.append(\"version-preserve\", entry);\n const entries = yield* storage.getEntries(\"version-preserve\", 0);\n yield* assertEqual(entries[0]!.version, version, \"Version should be preserved exactly\");\n }),\n },\n\n {\n name: \"timestamp is preserved exactly\",\n category: Categories.WalEntryIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const timestamp = 1704067200000;\n const entry = makeEntry(1, timestamp);\n yield* storage.append(\"timestamp-preserve\", entry);\n const entries = yield* storage.getEntries(\"timestamp-preserve\", 0);\n yield* assertEqual(\n entries[0]!.timestamp,\n timestamp,\n \"Timestamp should be preserved exactly\"\n );\n }),\n },\n\n {\n name: \"complex transaction operations survive roundtrip\",\n category: Categories.WalEntryIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry: WalEntry = {\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(\"a\"), TestSetDefinition, 1),\n Operation.fromDefinition(OperationPath.make(\"b/c\"), TestSetDefinition, \"nested\"),\n Operation.fromDefinition(OperationPath.make(\"arr\"), TestSetDefinition, [1, 2, 3]),\n ]),\n version: 1,\n timestamp: Date.now(),\n };\n yield* storage.append(\"complex-tx\", entry);\n const entries = yield* storage.getEntries(\"complex-tx\", 0);\n yield* assertEqual(\n entries[0]!.transaction,\n entry.transaction,\n \"Complex transaction should survive roundtrip\"\n );\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Document Isolation\n // ---------------------------------------------------------------------------\n {\n name: \"different documents have independent entry lists\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"iso-hot-1\", makeEntry(1));\n yield* storage.append(\"iso-hot-1\", makeEntry(2));\n yield* storage.append(\"iso-hot-2\", makeEntry(10));\n const entries1 = yield* storage.getEntries(\"iso-hot-1\", 0);\n const entries2 = yield* storage.getEntries(\"iso-hot-2\", 0);\n yield* assertLength(entries1, 2, \"Doc 1 should have 2 entries\");\n yield* assertLength(entries2, 1, \"Doc 2 should have 1 entry\");\n yield* assertEqual(entries1[0]!.version, 1, \"Doc 1 first entry version\");\n yield* assertEqual(entries2[0]!.version, 10, \"Doc 2 first entry version\");\n }),\n },\n\n {\n name: \"appending to one doc does not affect others\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"append-iso-1\", makeEntry(1));\n const beforeAppend = yield* storage.getEntries(\"append-iso-1\", 0);\n yield* storage.append(\"append-iso-2\", makeEntry(100));\n yield* storage.append(\"append-iso-2\", makeEntry(101));\n const afterAppend = yield* storage.getEntries(\"append-iso-1\", 0);\n yield* assertEqual(\n beforeAppend.length,\n afterAppend.length,\n \"Appending to doc 2 should not affect doc 1\"\n );\n }),\n },\n\n {\n name: \"truncating one doc does not affect others\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"trunc-iso-1\", makeEntry(1));\n yield* storage.append(\"trunc-iso-1\", makeEntry(2));\n yield* storage.append(\"trunc-iso-2\", makeEntry(1));\n yield* storage.append(\"trunc-iso-2\", makeEntry(2));\n yield* storage.truncate(\"trunc-iso-1\", 2);\n const entries1 = yield* storage.getEntries(\"trunc-iso-1\", 0);\n const entries2 = yield* storage.getEntries(\"trunc-iso-2\", 0);\n yield* assertEmpty(entries1, \"Doc 1 should be empty after truncate\");\n yield* assertLength(entries2, 2, \"Doc 2 should still have 2 entries\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Large-Scale Operations\n // ---------------------------------------------------------------------------\n {\n name: \"handle 1000+ entries per document\",\n category: Categories.LargeScaleOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const count = 1000;\n for (let i = 1; i <= count; i++) {\n yield* storage.append(\"large-entries\", makeEntry(i));\n }\n const entries = yield* storage.getEntries(\"large-entries\", 0);\n yield* assertLength(entries, count, `Should have ${count} entries`);\n yield* assertSortedBy(entries, \"version\", \"Should be sorted by version\");\n yield* assertEqual(entries[0]!.version, 1, \"First should be version 1\");\n yield* assertEqual(entries[count - 1]!.version, count, `Last should be version ${count}`);\n }),\n },\n\n {\n name: \"handle 100+ documents\",\n category: Categories.LargeScaleOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const docCount = 100;\n for (let i = 0; i < docCount; i++) {\n yield* storage.append(`multi-doc-${i}`, makeEntry(1));\n yield* storage.append(`multi-doc-${i}`, makeEntry(2));\n }\n let totalEntries = 0;\n for (let i = 0; i < docCount; i++) {\n const entries = yield* storage.getEntries(`multi-doc-${i}`, 0);\n totalEntries += entries.length;\n yield* assertLength(entries, 2, `Doc ${i} should have 2 entries`);\n }\n yield* assertEqual(totalEntries, docCount * 2, \"Total entries should match\");\n }),\n },\n\n {\n name: \"large transaction data (10KB+) survives roundtrip\",\n category: Categories.LargeScaleOperations,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const largeData = \"x\".repeat(10 * 1024);\n const entry = makeEntryWithData(1, { content: largeData });\n yield* storage.append(\"large-tx\", entry);\n const entries = yield* storage.getEntries(\"large-tx\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction,\n entry.transaction,\n \"Large transaction data should survive roundtrip\"\n );\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Document ID Edge Cases\n // ---------------------------------------------------------------------------\n {\n name: \"long documentId (1000+ chars) works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const longId = \"h\".repeat(1000);\n const entry = makeEntry(1);\n yield* storage.append(longId, entry);\n const entries = yield* storage.getEntries(longId, 0);\n yield* assertLength(entries, 1, \"Long documentId should work\");\n }),\n },\n\n {\n name: \"unicode documentId works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const unicodeId = \"hot-doc-id\";\n const entry = makeEntry(1);\n yield* storage.append(unicodeId, entry);\n const entries = yield* storage.getEntries(unicodeId, 0);\n yield* assertLength(entries, 1, \"Unicode documentId should work\");\n }),\n },\n\n {\n name: \"documentId with special chars works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const specialId = \"hot/path:to.wal\";\n const entry = makeEntry(1);\n yield* storage.append(specialId, entry);\n const entries = yield* storage.getEntries(specialId, 0);\n yield* assertLength(entries, 1, \"DocumentId with special chars should work\");\n }),\n },\n\n {\n name: \"documentId with spaces works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const spacedId = \"hot doc with spaces\";\n const entry = makeEntry(1);\n yield* storage.append(spacedId, entry);\n const entries = yield* storage.getEntries(spacedId, 0);\n yield* assertLength(entries, 1, \"DocumentId with spaces should work\");\n }),\n },\n\n {\n name: \"version 0 entry is handled correctly\",\n category: Categories.VersionFiltering,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.append(\"version-0-entry\", makeEntry(0));\n yield* storage.append(\"version-0-entry\", makeEntry(1));\n const entriesFromNeg = yield* storage.getEntries(\"version-0-entry\", -1);\n yield* assertTrue(\n entriesFromNeg.some((e) => e.version === 0),\n \"Version 0 entry should be retrievable with sinceVersion < 0\"\n );\n const entriesFrom0 = yield* storage.getEntries(\"version-0-entry\", 0);\n yield* assertTrue(\n !entriesFrom0.some((e) => e.version === 0),\n \"Version 0 entry should be excluded with sinceVersion = 0\"\n );\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Gap Checking (appendWithCheck)\n // ---------------------------------------------------------------------------\n {\n name: \"appendWithCheck succeeds for first entry (expectedVersion=1)\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntry(1);\n yield* storage.appendWithCheck(\"gap-check-first\", entry, 1);\n const entries = yield* storage.getEntries(\"gap-check-first\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(entries[0]!.version, 1, \"Entry version should be 1\");\n }),\n },\n\n {\n name: \"appendWithCheck succeeds for consecutive versions\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.appendWithCheck(\"gap-check-consecutive\", makeEntry(1), 1);\n yield* storage.appendWithCheck(\"gap-check-consecutive\", makeEntry(2), 2);\n yield* storage.appendWithCheck(\"gap-check-consecutive\", makeEntry(3), 3);\n const entries = yield* storage.getEntries(\"gap-check-consecutive\", 0);\n yield* assertLength(entries, 3, \"Should have three entries\");\n yield* assertEqual(entries[0]!.version, 1, \"First entry version should be 1\");\n yield* assertEqual(entries[1]!.version, 2, \"Second entry version should be 2\");\n yield* assertEqual(entries[2]!.version, 3, \"Third entry version should be 3\");\n }),\n },\n\n {\n name: \"appendWithCheck fails for version gap (skipping version 2)\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.appendWithCheck(\"gap-check-fail\", makeEntry(1), 1);\n // Attempt to append version 3, skipping version 2\n const result = yield* Effect.either(\n storage.appendWithCheck(\"gap-check-fail\", makeEntry(3), 3)\n );\n yield* assertTrue(\n result._tag === \"Left\",\n \"appendWithCheck should fail when there's a version gap\"\n );\n if (result._tag === \"Left\") {\n yield* assertTrue(\n result.left._tag === \"WalVersionGapError\",\n \"Error should be WalVersionGapError\"\n );\n }\n // Verify version 3 was not appended\n const entries = yield* storage.getEntries(\"gap-check-fail\", 0);\n yield* assertLength(entries, 1, \"Should only have version 1\");\n yield* assertEqual(entries[0]!.version, 1, \"Only version 1 should exist\");\n }),\n },\n\n {\n name: \"appendWithCheck fails if first entry is not version 1\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n // Attempt to append version 2 as first entry (expecting gap error)\n const result = yield* Effect.either(\n storage.appendWithCheck(\"gap-check-not-first\", makeEntry(2), 2)\n );\n yield* assertTrue(\n result._tag === \"Left\",\n \"appendWithCheck should fail when first entry is not version 1\"\n );\n if (result._tag === \"Left\") {\n yield* assertTrue(\n result.left._tag === \"WalVersionGapError\",\n \"Error should be WalVersionGapError\"\n );\n }\n // Verify nothing was appended\n const entries = yield* storage.getEntries(\"gap-check-not-first\", 0);\n yield* assertEmpty(entries, \"No entries should exist after failed append\");\n }),\n },\n\n {\n name: \"appendWithCheck fails when entry already exists at expectedVersion\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n yield* storage.appendWithCheck(\"gap-check-duplicate\", makeEntry(1), 1);\n // Attempt to append another version 1\n const result = yield* Effect.either(\n storage.appendWithCheck(\"gap-check-duplicate\", makeEntry(1), 1)\n );\n yield* assertTrue(\n result._tag === \"Left\",\n \"appendWithCheck should fail when version already exists\"\n );\n // Verify still only one entry\n const entries = yield* storage.getEntries(\"gap-check-duplicate\", 0);\n yield* assertLength(entries, 1, \"Should still only have one entry\");\n }),\n },\n\n {\n name: \"appendWithCheck after truncate works correctly\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n // Append versions 1, 2, 3\n yield* storage.appendWithCheck(\"gap-check-truncate\", makeEntry(1), 1);\n yield* storage.appendWithCheck(\"gap-check-truncate\", makeEntry(2), 2);\n yield* storage.appendWithCheck(\"gap-check-truncate\", makeEntry(3), 3);\n // Truncate up to version 2\n yield* storage.truncate(\"gap-check-truncate\", 2);\n // Now append version 4 (should succeed since last entry is version 3)\n yield* storage.appendWithCheck(\"gap-check-truncate\", makeEntry(4), 4);\n const entries = yield* storage.getEntries(\"gap-check-truncate\", 0);\n yield* assertLength(entries, 2, \"Should have versions 3 and 4\");\n yield* assertEqual(entries[0]!.version, 3, \"First should be version 3\");\n yield* assertEqual(entries[1]!.version, 4, \"Second should be version 4\");\n }),\n },\n\n {\n name: \"appendWithCheck with baseVersion after full truncate works\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n // Append versions 1, 2, 3\n yield* storage.appendWithCheck(\"gap-base-version\", makeEntry(1), 1, 0);\n yield* storage.appendWithCheck(\"gap-base-version\", makeEntry(2), 2, 0);\n yield* storage.appendWithCheck(\"gap-base-version\", makeEntry(3), 3, 0);\n // Truncate ALL entries (simulates snapshot at version 3)\n yield* storage.truncate(\"gap-base-version\", 3);\n // Verify WAL is empty\n const entriesAfterTruncate = yield* storage.getEntries(\"gap-base-version\", 0);\n yield* assertEmpty(entriesAfterTruncate, \"WAL should be empty after full truncate\");\n // Append version 4 WITH baseVersion=3 (simulates knowing snapshot version)\n yield* storage.appendWithCheck(\"gap-base-version\", makeEntry(4), 4, 3);\n const entries = yield* storage.getEntries(\"gap-base-version\", 0);\n yield* assertLength(entries, 1, \"Should have version 4\");\n yield* assertEqual(entries[0]!.version, 4, \"Entry should be version 4\");\n }),\n },\n\n {\n name: \"appendWithCheck with baseVersion still detects gaps\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n // Truncate (to establish empty WAL scenario)\n yield* storage.truncate(\"gap-base-detect\", 5);\n // Try to append version 7 with baseVersion=5 (skipping version 6)\n const result = yield* Effect.either(\n storage.appendWithCheck(\"gap-base-detect\", makeEntry(7), 7, 5)\n );\n yield* assertTrue(result._tag === \"Left\", \"Should fail when skipping version 6\");\n if (result._tag === \"Left\") {\n yield* assertTrue(\n result.left._tag === \"WalVersionGapError\",\n \"Error should be WalVersionGapError\"\n );\n }\n }),\n },\n\n {\n name: \"appendWithCheck with baseVersion=0 allows version 1\",\n category: Categories.GapChecking,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n // New document scenario: baseVersion=0, first entry should be version 1\n yield* storage.appendWithCheck(\"gap-base-zero\", makeEntry(1), 1, 0);\n const entries = yield* storage.getEntries(\"gap-base-zero\", 0);\n yield* assertLength(entries, 1, \"Should have version 1\");\n yield* assertEqual(entries[0]!.version, 1, \"Entry should be version 1\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Transaction Encoding (Critical for OperationPath preservation)\n // ---------------------------------------------------------------------------\n {\n name: \"OperationPath has _tag after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0/name\", \"Alice\");\n yield* storage.append(\"op-path-tag\", entry);\n const entries = yield* storage.getEntries(\"op-path-tag\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n op.path._tag === \"OperationPath\",\n \"path should have _tag 'OperationPath'\"\n );\n }),\n },\n\n {\n name: \"OperationPath.toTokens() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0/name\", \"Alice\");\n yield* storage.append(\"op-path-tokens\", entry);\n const entries = yield* storage.getEntries(\"op-path-tokens\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.toTokens === \"function\",\n \"path.toTokens should be a function\"\n );\n const tokens = op.path.toTokens();\n yield* assertEqual(\n tokens,\n [\"users\", \"0\", \"name\"],\n \"toTokens() should return correct path tokens\"\n );\n }),\n },\n\n {\n name: \"OperationPath.concat() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0\", { name: \"Alice\" });\n yield* storage.append(\"op-path-concat\", entry);\n const entries = yield* storage.getEntries(\"op-path-concat\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.concat === \"function\",\n \"path.concat should be a function\"\n );\n const extended = op.path.concat(OperationPath.make(\"name\"));\n yield* assertEqual(\n extended.toTokens(),\n [\"users\", \"0\", \"name\"],\n \"concat() should work correctly\"\n );\n }),\n },\n\n {\n name: \"OperationPath.append() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users\", []);\n yield* storage.append(\"op-path-append\", entry);\n const entries = yield* storage.getEntries(\"op-path-append\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.append === \"function\",\n \"path.append should be a function\"\n );\n const extended = op.path.append(\"0\");\n yield* assertEqual(\n extended.toTokens(),\n [\"users\", \"0\"],\n \"append() should work correctly\"\n );\n }),\n },\n\n {\n name: \"OperationPath.pop() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0/name\", \"Alice\");\n yield* storage.append(\"op-path-pop\", entry);\n const entries = yield* storage.getEntries(\"op-path-pop\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.pop === \"function\",\n \"path.pop should be a function\"\n );\n const popped = op.path.pop();\n yield* assertEqual(\n popped.toTokens(),\n [\"users\", \"0\"],\n \"pop() should remove last token\"\n );\n }),\n },\n\n {\n name: \"OperationPath.shift() works after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"users/0/name\", \"Alice\");\n yield* storage.append(\"op-path-shift\", entry);\n const entries = yield* storage.getEntries(\"op-path-shift\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n typeof op.path.shift === \"function\",\n \"path.shift should be a function\"\n );\n const shifted = op.path.shift();\n yield* assertEqual(\n shifted.toTokens(),\n [\"0\", \"name\"],\n \"shift() should remove first token\"\n );\n }),\n },\n\n {\n name: \"transaction with multiple operations preserves all OperationPaths\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry: WalEntry = {\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(\"users/0/name\"), TestSetDefinition, \"Alice\"),\n Operation.fromDefinition(OperationPath.make(\"users/1/name\"), TestSetDefinition, \"Bob\"),\n Operation.fromDefinition(OperationPath.make(\"count\"), TestSetDefinition, 2),\n ]),\n version: 1,\n timestamp: Date.now(),\n };\n yield* storage.append(\"multi-op-paths\", entry);\n const entries = yield* storage.getEntries(\"multi-op-paths\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const ops = entries[0]!.transaction.ops;\n yield* assertLength([...ops], 3, \"Should have 3 operations\");\n // Verify all paths have working methods\n for (const op of ops) {\n yield* assertTrue(\n op.path._tag === \"OperationPath\",\n \"Each operation path should have _tag\"\n );\n yield* assertTrue(\n typeof op.path.toTokens === \"function\",\n \"Each operation path should have toTokens method\"\n );\n }\n yield* assertEqual(\n ops[0]!.path.toTokens(),\n [\"users\", \"0\", \"name\"],\n \"First path should be correct\"\n );\n yield* assertEqual(\n ops[1]!.path.toTokens(),\n [\"users\", \"1\", \"name\"],\n \"Second path should be correct\"\n );\n yield* assertEqual(\n ops[2]!.path.toTokens(),\n [\"count\"],\n \"Third path should be correct\"\n );\n }),\n },\n\n {\n name: \"nested path with many segments survives roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const deepPath = \"level1/level2/level3/level4/level5\";\n const entry = makeEntryWithPath(1, deepPath, \"deep value\");\n yield* storage.append(\"deep-path\", entry);\n const entries = yield* storage.getEntries(\"deep-path\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertEqual(\n op.path.toTokens(),\n [\"level1\", \"level2\", \"level3\", \"level4\", \"level5\"],\n \"Deep nested path should survive roundtrip\"\n );\n }),\n },\n\n {\n name: \"empty path survives roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"\", { root: true });\n yield* storage.append(\"empty-path\", entry);\n const entries = yield* storage.getEntries(\"empty-path\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n const op = entries[0]!.transaction.ops[0]!;\n yield* assertTrue(\n op.path._tag === \"OperationPath\",\n \"Empty path should still be OperationPath\"\n );\n yield* assertTrue(\n typeof op.path.toTokens === \"function\",\n \"Empty path should have toTokens method\"\n );\n }),\n },\n\n {\n name: \"transaction id is preserved after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"test\", \"value\");\n const originalId = entry.transaction.id;\n yield* storage.append(\"tx-id-preserve\", entry);\n const entries = yield* storage.getEntries(\"tx-id-preserve\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction.id,\n originalId,\n \"Transaction id should be preserved\"\n );\n }),\n },\n\n {\n name: \"transaction timestamp is preserved after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry = makeEntryWithPath(1, \"test\", \"value\");\n const originalTimestamp = entry.transaction.timestamp;\n yield* storage.append(\"tx-timestamp-preserve\", entry);\n const entries = yield* storage.getEntries(\"tx-timestamp-preserve\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction.timestamp,\n originalTimestamp,\n \"Transaction timestamp should be preserved\"\n );\n }),\n },\n\n {\n name: \"operation kind is preserved after roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const entry: WalEntry = {\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(\"data\"), CustomOpDefinition, \"test\"),\n ]),\n version: 1,\n timestamp: Date.now(),\n };\n yield* storage.append(\"op-kind-preserve\", entry);\n const entries = yield* storage.getEntries(\"op-kind-preserve\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction.ops[0]!.kind,\n \"custom.operation\",\n \"Operation kind should be preserved\"\n );\n }),\n },\n\n {\n name: \"operation payload with complex object survives roundtrip\",\n category: Categories.TransactionEncoding,\n run: Effect.gen(function* () {\n const storage = yield* HotStorageTag;\n const complexPayload = {\n nested: { value: 42, array: [1, 2, 3] },\n nullValue: null,\n string: \"test\",\n };\n const entry = makeEntryWithPath(1, \"data\", complexPayload);\n yield* storage.append(\"complex-payload\", entry);\n const entries = yield* storage.getEntries(\"complex-payload\", 0);\n yield* assertLength(entries, 1, \"Should have one entry\");\n yield* assertEqual(\n entries[0]!.transaction.ops[0]!.payload,\n complexPayload,\n \"Complex payload should survive roundtrip\"\n );\n }),\n },\n];\n\n// =============================================================================\n// Exports\n// =============================================================================\n\n/**\n * Get all HotStorage test cases.\n *\n * @returns Array of test cases that require HotStorageTag\n */\nexport const makeTests = (): StorageTestCase<\n HotStorageTestError,\n HotStorageTag\n>[] => tests;\n\n/**\n * Run all tests and collect results.\n *\n * @returns Effect that produces TestResults\n */\nexport const runAll = (): Effect.Effect<\n TestResults<HotStorageTestError, HotStorageTag>,\n never,\n HotStorageTag\n> =>\n Effect.gen(function* () {\n const passed: StorageTestCase<HotStorageTestError, HotStorageTag>[] = [];\n const failed: Array<{\n test: StorageTestCase<HotStorageTestError, HotStorageTag>;\n error: HotStorageTestError;\n }> = [];\n\n for (const test of tests) {\n const result = yield* Effect.either(test.run);\n if (result._tag === \"Right\") {\n passed.push(test);\n } else {\n failed.push({ test, error: result.left });\n }\n }\n\n return {\n passed,\n failed,\n total: tests.length,\n passCount: passed.length,\n failCount: failed.length,\n };\n });\n\nexport const HotStorageTestSuite = {\n Categories,\n makeTests,\n runAll,\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidhash/mimic-effect",
3
- "version": "1.0.0-beta.10",
3
+ "version": "1.0.0-beta.11",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,14 +26,14 @@
26
26
  "typescript": "5.8.3",
27
27
  "vite-tsconfig-paths": "^5.1.4",
28
28
  "vitest": "^3.2.4",
29
- "@voidhash/tsconfig": "1.0.0-beta.10"
29
+ "@voidhash/tsconfig": "1.0.0-beta.11"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@effect/platform": "^0.93.8",
33
33
  "@effect/cluster": "^0.55.0",
34
34
  "@effect/rpc": "^0.72.2",
35
35
  "effect": "^3.19.12",
36
- "@voidhash/mimic": "1.0.0-beta.10"
36
+ "@voidhash/mimic": "1.0.0-beta.11"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "@effect/cluster": {
@@ -302,8 +302,12 @@ export const make = <TSchema extends Primitive.AnyPrimitive>(
302
302
  timestamp: Date.now(),
303
303
  };
304
304
 
305
+ // Get the current snapshot version to pass as baseVersion for gap checking
306
+ // This ensures correct validation after truncation or restart
307
+ const snapshotVersion = yield* Ref.get(lastSnapshotVersionRef);
308
+
305
309
  const appendResult = yield* Effect.either(
306
- hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion)
310
+ hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion, snapshotVersion)
307
311
  );
308
312
 
309
313
  if (appendResult._tag === "Left") {
package/src/HotStorage.ts CHANGED
@@ -32,7 +32,7 @@ export interface HotStorage {
32
32
  *
33
33
  * This is an atomic operation that:
34
34
  * 1. Verifies the previous entry has version = expectedVersion - 1
35
- * (or this is the first entry if expectedVersion === 1)
35
+ * (or this is the first entry if expectedVersion === 1, accounting for baseVersion)
36
36
  * 2. Appends the entry if check passes
37
37
  *
38
38
  * Use this for two-phase commit to guarantee WAL ordering at write time.
@@ -40,12 +40,17 @@ export interface HotStorage {
40
40
  * @param documentId - Document ID
41
41
  * @param entry - WAL entry to append
42
42
  * @param expectedVersion - The version this entry should have (entry.version)
43
+ * @param baseVersion - Optional known snapshot version. When provided, an empty WAL
44
+ * is treated as "at this version" rather than "new document at version 0".
45
+ * This is necessary after truncation or restart to correctly validate
46
+ * that the next entry is baseVersion + 1.
43
47
  * @returns Effect that fails with WalVersionGapError if gap detected
44
48
  */
45
49
  readonly appendWithCheck: (
46
50
  documentId: string,
47
51
  entry: WalEntry,
48
- expectedVersion: number
52
+ expectedVersion: number,
53
+ baseVersion?: number
49
54
  ) => Effect.Effect<void, HotStorageError | WalVersionGapError>;
50
55
 
51
56
  /**
@@ -155,7 +160,8 @@ export namespace InMemory {
155
160
  function* (
156
161
  documentId: string,
157
162
  entry: WalEntry,
158
- expectedVersion: number
163
+ expectedVersion: number,
164
+ baseVersion?: number
159
165
  ) {
160
166
  type CheckResult =
161
167
  | { type: "ok" }
@@ -170,24 +176,33 @@ export namespace InMemory {
170
176
  existing._tag === "Some" ? existing.value : [];
171
177
 
172
178
  // Find the highest version in existing entries
173
- const lastVersion =
179
+ const lastEntryVersion =
174
180
  entries.length > 0
175
181
  ? Math.max(...entries.map((e) => e.version))
176
182
  : 0;
177
183
 
184
+ // Effective "last version" is max of entries and baseVersion
185
+ // This handles the case after truncation or restart where
186
+ // WAL is empty but we know the snapshot version
187
+ const effectiveLastVersion =
188
+ baseVersion !== undefined
189
+ ? Math.max(lastEntryVersion, baseVersion)
190
+ : lastEntryVersion;
191
+
178
192
  // Gap check
179
193
  if (expectedVersion === 1) {
180
194
  // First entry: should have no entries with version >= 1
181
- if (lastVersion >= 1) {
182
- return [{ type: "gap", lastVersion }, map];
195
+ // and baseVersion should be 0 or undefined
196
+ if (effectiveLastVersion >= 1) {
197
+ return [{ type: "gap", lastVersion: effectiveLastVersion }, map];
183
198
  }
184
199
  } else {
185
- // Not first: last entry should have version = expectedVersion - 1
186
- if (lastVersion !== expectedVersion - 1) {
200
+ // Not first: effective last version should be expectedVersion - 1
201
+ if (effectiveLastVersion !== expectedVersion - 1) {
187
202
  return [
188
203
  {
189
204
  type: "gap",
190
- lastVersion: lastVersion > 0 ? lastVersion : undefined,
205
+ lastVersion: effectiveLastVersion > 0 ? effectiveLastVersion : undefined,
191
206
  },
192
207
  map,
193
208
  ];
@@ -744,6 +744,62 @@ const tests: StorageTestCase<HotStorageTestError, HotStorageTag>[] = [
744
744
  }),
745
745
  },
746
746
 
747
+ {
748
+ name: "appendWithCheck with baseVersion after full truncate works",
749
+ category: Categories.GapChecking,
750
+ run: Effect.gen(function* () {
751
+ const storage = yield* HotStorageTag;
752
+ // Append versions 1, 2, 3
753
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(1), 1, 0);
754
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(2), 2, 0);
755
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(3), 3, 0);
756
+ // Truncate ALL entries (simulates snapshot at version 3)
757
+ yield* storage.truncate("gap-base-version", 3);
758
+ // Verify WAL is empty
759
+ const entriesAfterTruncate = yield* storage.getEntries("gap-base-version", 0);
760
+ yield* assertEmpty(entriesAfterTruncate, "WAL should be empty after full truncate");
761
+ // Append version 4 WITH baseVersion=3 (simulates knowing snapshot version)
762
+ yield* storage.appendWithCheck("gap-base-version", makeEntry(4), 4, 3);
763
+ const entries = yield* storage.getEntries("gap-base-version", 0);
764
+ yield* assertLength(entries, 1, "Should have version 4");
765
+ yield* assertEqual(entries[0]!.version, 4, "Entry should be version 4");
766
+ }),
767
+ },
768
+
769
+ {
770
+ name: "appendWithCheck with baseVersion still detects gaps",
771
+ category: Categories.GapChecking,
772
+ run: Effect.gen(function* () {
773
+ const storage = yield* HotStorageTag;
774
+ // Truncate (to establish empty WAL scenario)
775
+ yield* storage.truncate("gap-base-detect", 5);
776
+ // Try to append version 7 with baseVersion=5 (skipping version 6)
777
+ const result = yield* Effect.either(
778
+ storage.appendWithCheck("gap-base-detect", makeEntry(7), 7, 5)
779
+ );
780
+ yield* assertTrue(result._tag === "Left", "Should fail when skipping version 6");
781
+ if (result._tag === "Left") {
782
+ yield* assertTrue(
783
+ result.left._tag === "WalVersionGapError",
784
+ "Error should be WalVersionGapError"
785
+ );
786
+ }
787
+ }),
788
+ },
789
+
790
+ {
791
+ name: "appendWithCheck with baseVersion=0 allows version 1",
792
+ category: Categories.GapChecking,
793
+ run: Effect.gen(function* () {
794
+ const storage = yield* HotStorageTag;
795
+ // New document scenario: baseVersion=0, first entry should be version 1
796
+ yield* storage.appendWithCheck("gap-base-zero", makeEntry(1), 1, 0);
797
+ const entries = yield* storage.getEntries("gap-base-zero", 0);
798
+ yield* assertLength(entries, 1, "Should have version 1");
799
+ yield* assertEqual(entries[0]!.version, 1, "Entry should be version 1");
800
+ }),
801
+ },
802
+
747
803
  // ---------------------------------------------------------------------------
748
804
  // Transaction Encoding (Critical for OperationPath preservation)
749
805
  // ---------------------------------------------------------------------------