@voidhash/mimic-effect 1.0.0-beta.3 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +21 -21
- package/dist/MimicServer.d.cts +1 -1
- package/dist/MimicServer.d.cts.map +1 -1
- package/dist/MimicServer.d.mts +1 -1
- package/dist/MimicServer.d.mts.map +1 -1
- package/dist/testing/FailingStorage.cjs +27 -0
- package/dist/testing/FailingStorage.d.cts.map +1 -1
- package/dist/testing/FailingStorage.d.mts.map +1 -1
- package/dist/testing/FailingStorage.mjs +28 -1
- package/dist/testing/FailingStorage.mjs.map +1 -1
- package/dist/testing/HotStorageTestSuite.cjs +253 -6
- package/dist/testing/HotStorageTestSuite.d.cts +2 -0
- package/dist/testing/HotStorageTestSuite.d.cts.map +1 -1
- package/dist/testing/HotStorageTestSuite.d.mts +2 -0
- package/dist/testing/HotStorageTestSuite.d.mts.map +1 -1
- package/dist/testing/HotStorageTestSuite.mjs +255 -8
- package/dist/testing/HotStorageTestSuite.mjs.map +1 -1
- package/dist/testing/StorageIntegrationTestSuite.cjs +150 -12
- package/dist/testing/StorageIntegrationTestSuite.d.cts +2 -0
- package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -1
- package/dist/testing/StorageIntegrationTestSuite.d.mts +2 -0
- package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -1
- package/dist/testing/StorageIntegrationTestSuite.mjs +151 -13
- package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -1
- package/dist/testing/types.d.cts +3 -3
- package/package.json +3 -3
- package/src/testing/FailingStorage.ts +53 -1
- package/src/testing/HotStorageTestSuite.ts +346 -3
- package/src/testing/StorageIntegrationTestSuite.ts +239 -7
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { Effect, Layer, Ref, HashMap } from "effect";
|
|
19
19
|
import { ColdStorageTag, type ColdStorage } from "../ColdStorage";
|
|
20
20
|
import { HotStorageTag, type HotStorage } from "../HotStorage";
|
|
21
|
-
import { ColdStorageError, HotStorageError } from "../Errors";
|
|
21
|
+
import { ColdStorageError, HotStorageError, WalVersionGapError } from "../Errors";
|
|
22
22
|
import type { StoredDocument, WalEntry } from "../Types";
|
|
23
23
|
|
|
24
24
|
// =============================================================================
|
|
@@ -224,6 +224,58 @@ export const makeHotStorage = (
|
|
|
224
224
|
});
|
|
225
225
|
}),
|
|
226
226
|
|
|
227
|
+
appendWithCheck: (documentId, entry, expectedVersion) =>
|
|
228
|
+
Effect.gen(function* () {
|
|
229
|
+
const fail = yield* shouldFail("append");
|
|
230
|
+
if (fail) {
|
|
231
|
+
return yield* Effect.fail(
|
|
232
|
+
new HotStorageError({
|
|
233
|
+
documentId,
|
|
234
|
+
operation: "appendWithCheck",
|
|
235
|
+
cause: new Error(errorMessage),
|
|
236
|
+
})
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
type CheckResult =
|
|
241
|
+
| { type: "ok" }
|
|
242
|
+
| { type: "gap"; lastVersion: number | undefined };
|
|
243
|
+
|
|
244
|
+
const result: CheckResult = yield* Ref.modify(store, (map): [CheckResult, HashMap.HashMap<string, WalEntry[]>] => {
|
|
245
|
+
const existing = HashMap.get(map, documentId);
|
|
246
|
+
const entries = existing._tag === "Some" ? existing.value : [];
|
|
247
|
+
|
|
248
|
+
const lastVersion = entries.length > 0
|
|
249
|
+
? Math.max(...entries.map((e) => e.version))
|
|
250
|
+
: 0;
|
|
251
|
+
|
|
252
|
+
if (expectedVersion === 1) {
|
|
253
|
+
if (lastVersion >= 1) {
|
|
254
|
+
return [{ type: "gap", lastVersion }, map];
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
if (lastVersion !== expectedVersion - 1) {
|
|
258
|
+
return [{ type: "gap", lastVersion: lastVersion > 0 ? lastVersion : undefined }, map];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return [
|
|
263
|
+
{ type: "ok" },
|
|
264
|
+
HashMap.set(map, documentId, [...entries, entry]),
|
|
265
|
+
];
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (result.type === "gap") {
|
|
269
|
+
return yield* Effect.fail(
|
|
270
|
+
new WalVersionGapError({
|
|
271
|
+
documentId,
|
|
272
|
+
expectedVersion,
|
|
273
|
+
actualPreviousVersion: result.lastVersion,
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}),
|
|
278
|
+
|
|
227
279
|
getEntries: (documentId, sinceVersion) =>
|
|
228
280
|
Effect.gen(function* () {
|
|
229
281
|
const fail = yield* shouldFail("getEntries");
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* These tests verify that an adapter correctly implements the HotStorage interface
|
|
6
6
|
* and can reliably store/retrieve WAL entries for document recovery.
|
|
7
7
|
*/
|
|
8
|
-
import { Effect } from "effect";
|
|
9
|
-
import { Transaction } from "@voidhash/mimic";
|
|
8
|
+
import { Effect, Schema } from "effect";
|
|
9
|
+
import { Transaction, OperationPath, Operation, OperationDefinition } from "@voidhash/mimic";
|
|
10
10
|
import { HotStorageTag } from "../HotStorage";
|
|
11
11
|
import { type HotStorageError, WalVersionGapError } from "../Errors";
|
|
12
12
|
import type { WalEntry } from "../Types";
|
|
@@ -25,6 +25,31 @@ import {
|
|
|
25
25
|
*/
|
|
26
26
|
export type HotStorageTestError = TestError | HotStorageError | WalVersionGapError;
|
|
27
27
|
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Test Operation Definitions
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Test operation definition for creating proper Operation objects in tests.
|
|
34
|
+
* Using Schema.Unknown allows any payload type for flexibility in testing.
|
|
35
|
+
*/
|
|
36
|
+
const TestSetDefinition = OperationDefinition.make({
|
|
37
|
+
kind: "test.set" as const,
|
|
38
|
+
payload: Schema.Unknown,
|
|
39
|
+
target: Schema.Unknown,
|
|
40
|
+
apply: (payload: unknown) => payload,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Custom operation definition for testing operation kind preservation.
|
|
45
|
+
*/
|
|
46
|
+
const CustomOpDefinition = OperationDefinition.make({
|
|
47
|
+
kind: "custom.operation" as const,
|
|
48
|
+
payload: Schema.Unknown,
|
|
49
|
+
target: Schema.Unknown,
|
|
50
|
+
apply: (payload: unknown) => payload,
|
|
51
|
+
});
|
|
52
|
+
|
|
28
53
|
// =============================================================================
|
|
29
54
|
// Categories
|
|
30
55
|
// =============================================================================
|
|
@@ -39,6 +64,7 @@ export const Categories = {
|
|
|
39
64
|
LargeScaleOperations: "Large-Scale Operations",
|
|
40
65
|
DocumentIdEdgeCases: "Document ID Edge Cases",
|
|
41
66
|
GapChecking: "Gap Checking",
|
|
67
|
+
TransactionEncoding: "Transaction Encoding",
|
|
42
68
|
} as const;
|
|
43
69
|
|
|
44
70
|
// =============================================================================
|
|
@@ -57,7 +83,20 @@ const makeEntryWithData = (
|
|
|
57
83
|
timestamp?: number
|
|
58
84
|
): WalEntry => ({
|
|
59
85
|
transaction: Transaction.make([
|
|
60
|
-
|
|
86
|
+
Operation.fromDefinition(OperationPath.make("data"), TestSetDefinition, data),
|
|
87
|
+
]),
|
|
88
|
+
version,
|
|
89
|
+
timestamp: timestamp ?? Date.now(),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const makeEntryWithPath = (
|
|
93
|
+
version: number,
|
|
94
|
+
pathString: string,
|
|
95
|
+
payload: unknown,
|
|
96
|
+
timestamp?: number
|
|
97
|
+
): WalEntry => ({
|
|
98
|
+
transaction: Transaction.make([
|
|
99
|
+
Operation.fromDefinition(OperationPath.make(pathString), TestSetDefinition, payload),
|
|
61
100
|
]),
|
|
62
101
|
version,
|
|
63
102
|
timestamp: timestamp ?? Date.now(),
|
|
@@ -704,6 +743,310 @@ const tests: StorageTestCase<HotStorageTestError, HotStorageTag>[] = [
|
|
|
704
743
|
yield* assertEqual(entries[1]!.version, 4, "Second should be version 4");
|
|
705
744
|
}),
|
|
706
745
|
},
|
|
746
|
+
|
|
747
|
+
// ---------------------------------------------------------------------------
|
|
748
|
+
// Transaction Encoding (Critical for OperationPath preservation)
|
|
749
|
+
// ---------------------------------------------------------------------------
|
|
750
|
+
{
|
|
751
|
+
name: "OperationPath has _tag after roundtrip",
|
|
752
|
+
category: Categories.TransactionEncoding,
|
|
753
|
+
run: Effect.gen(function* () {
|
|
754
|
+
const storage = yield* HotStorageTag;
|
|
755
|
+
const entry = makeEntryWithPath(1, "users/0/name", "Alice");
|
|
756
|
+
yield* storage.append("op-path-tag", entry);
|
|
757
|
+
const entries = yield* storage.getEntries("op-path-tag", 0);
|
|
758
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
759
|
+
const op = entries[0]!.transaction.ops[0]!;
|
|
760
|
+
yield* assertTrue(
|
|
761
|
+
op.path._tag === "OperationPath",
|
|
762
|
+
"path should have _tag 'OperationPath'"
|
|
763
|
+
);
|
|
764
|
+
}),
|
|
765
|
+
},
|
|
766
|
+
|
|
767
|
+
{
|
|
768
|
+
name: "OperationPath.toTokens() works after roundtrip",
|
|
769
|
+
category: Categories.TransactionEncoding,
|
|
770
|
+
run: Effect.gen(function* () {
|
|
771
|
+
const storage = yield* HotStorageTag;
|
|
772
|
+
const entry = makeEntryWithPath(1, "users/0/name", "Alice");
|
|
773
|
+
yield* storage.append("op-path-tokens", entry);
|
|
774
|
+
const entries = yield* storage.getEntries("op-path-tokens", 0);
|
|
775
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
776
|
+
const op = entries[0]!.transaction.ops[0]!;
|
|
777
|
+
yield* assertTrue(
|
|
778
|
+
typeof op.path.toTokens === "function",
|
|
779
|
+
"path.toTokens should be a function"
|
|
780
|
+
);
|
|
781
|
+
const tokens = op.path.toTokens();
|
|
782
|
+
yield* assertEqual(
|
|
783
|
+
tokens,
|
|
784
|
+
["users", "0", "name"],
|
|
785
|
+
"toTokens() should return correct path tokens"
|
|
786
|
+
);
|
|
787
|
+
}),
|
|
788
|
+
},
|
|
789
|
+
|
|
790
|
+
{
|
|
791
|
+
name: "OperationPath.concat() works after roundtrip",
|
|
792
|
+
category: Categories.TransactionEncoding,
|
|
793
|
+
run: Effect.gen(function* () {
|
|
794
|
+
const storage = yield* HotStorageTag;
|
|
795
|
+
const entry = makeEntryWithPath(1, "users/0", { name: "Alice" });
|
|
796
|
+
yield* storage.append("op-path-concat", entry);
|
|
797
|
+
const entries = yield* storage.getEntries("op-path-concat", 0);
|
|
798
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
799
|
+
const op = entries[0]!.transaction.ops[0]!;
|
|
800
|
+
yield* assertTrue(
|
|
801
|
+
typeof op.path.concat === "function",
|
|
802
|
+
"path.concat should be a function"
|
|
803
|
+
);
|
|
804
|
+
const extended = op.path.concat(OperationPath.make("name"));
|
|
805
|
+
yield* assertEqual(
|
|
806
|
+
extended.toTokens(),
|
|
807
|
+
["users", "0", "name"],
|
|
808
|
+
"concat() should work correctly"
|
|
809
|
+
);
|
|
810
|
+
}),
|
|
811
|
+
},
|
|
812
|
+
|
|
813
|
+
{
|
|
814
|
+
name: "OperationPath.append() works after roundtrip",
|
|
815
|
+
category: Categories.TransactionEncoding,
|
|
816
|
+
run: Effect.gen(function* () {
|
|
817
|
+
const storage = yield* HotStorageTag;
|
|
818
|
+
const entry = makeEntryWithPath(1, "users", []);
|
|
819
|
+
yield* storage.append("op-path-append", entry);
|
|
820
|
+
const entries = yield* storage.getEntries("op-path-append", 0);
|
|
821
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
822
|
+
const op = entries[0]!.transaction.ops[0]!;
|
|
823
|
+
yield* assertTrue(
|
|
824
|
+
typeof op.path.append === "function",
|
|
825
|
+
"path.append should be a function"
|
|
826
|
+
);
|
|
827
|
+
const extended = op.path.append("0");
|
|
828
|
+
yield* assertEqual(
|
|
829
|
+
extended.toTokens(),
|
|
830
|
+
["users", "0"],
|
|
831
|
+
"append() should work correctly"
|
|
832
|
+
);
|
|
833
|
+
}),
|
|
834
|
+
},
|
|
835
|
+
|
|
836
|
+
{
|
|
837
|
+
name: "OperationPath.pop() works after roundtrip",
|
|
838
|
+
category: Categories.TransactionEncoding,
|
|
839
|
+
run: Effect.gen(function* () {
|
|
840
|
+
const storage = yield* HotStorageTag;
|
|
841
|
+
const entry = makeEntryWithPath(1, "users/0/name", "Alice");
|
|
842
|
+
yield* storage.append("op-path-pop", entry);
|
|
843
|
+
const entries = yield* storage.getEntries("op-path-pop", 0);
|
|
844
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
845
|
+
const op = entries[0]!.transaction.ops[0]!;
|
|
846
|
+
yield* assertTrue(
|
|
847
|
+
typeof op.path.pop === "function",
|
|
848
|
+
"path.pop should be a function"
|
|
849
|
+
);
|
|
850
|
+
const popped = op.path.pop();
|
|
851
|
+
yield* assertEqual(
|
|
852
|
+
popped.toTokens(),
|
|
853
|
+
["users", "0"],
|
|
854
|
+
"pop() should remove last token"
|
|
855
|
+
);
|
|
856
|
+
}),
|
|
857
|
+
},
|
|
858
|
+
|
|
859
|
+
{
|
|
860
|
+
name: "OperationPath.shift() works after roundtrip",
|
|
861
|
+
category: Categories.TransactionEncoding,
|
|
862
|
+
run: Effect.gen(function* () {
|
|
863
|
+
const storage = yield* HotStorageTag;
|
|
864
|
+
const entry = makeEntryWithPath(1, "users/0/name", "Alice");
|
|
865
|
+
yield* storage.append("op-path-shift", entry);
|
|
866
|
+
const entries = yield* storage.getEntries("op-path-shift", 0);
|
|
867
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
868
|
+
const op = entries[0]!.transaction.ops[0]!;
|
|
869
|
+
yield* assertTrue(
|
|
870
|
+
typeof op.path.shift === "function",
|
|
871
|
+
"path.shift should be a function"
|
|
872
|
+
);
|
|
873
|
+
const shifted = op.path.shift();
|
|
874
|
+
yield* assertEqual(
|
|
875
|
+
shifted.toTokens(),
|
|
876
|
+
["0", "name"],
|
|
877
|
+
"shift() should remove first token"
|
|
878
|
+
);
|
|
879
|
+
}),
|
|
880
|
+
},
|
|
881
|
+
|
|
882
|
+
{
|
|
883
|
+
name: "transaction with multiple operations preserves all OperationPaths",
|
|
884
|
+
category: Categories.TransactionEncoding,
|
|
885
|
+
run: Effect.gen(function* () {
|
|
886
|
+
const storage = yield* HotStorageTag;
|
|
887
|
+
const entry: WalEntry = {
|
|
888
|
+
transaction: Transaction.make([
|
|
889
|
+
Operation.fromDefinition(OperationPath.make("users/0/name"), TestSetDefinition, "Alice"),
|
|
890
|
+
Operation.fromDefinition(OperationPath.make("users/1/name"), TestSetDefinition, "Bob"),
|
|
891
|
+
Operation.fromDefinition(OperationPath.make("count"), TestSetDefinition, 2),
|
|
892
|
+
]),
|
|
893
|
+
version: 1,
|
|
894
|
+
timestamp: Date.now(),
|
|
895
|
+
};
|
|
896
|
+
yield* storage.append("multi-op-paths", entry);
|
|
897
|
+
const entries = yield* storage.getEntries("multi-op-paths", 0);
|
|
898
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
899
|
+
const ops = entries[0]!.transaction.ops;
|
|
900
|
+
yield* assertLength([...ops], 3, "Should have 3 operations");
|
|
901
|
+
// Verify all paths have working methods
|
|
902
|
+
for (const op of ops) {
|
|
903
|
+
yield* assertTrue(
|
|
904
|
+
op.path._tag === "OperationPath",
|
|
905
|
+
"Each operation path should have _tag"
|
|
906
|
+
);
|
|
907
|
+
yield* assertTrue(
|
|
908
|
+
typeof op.path.toTokens === "function",
|
|
909
|
+
"Each operation path should have toTokens method"
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
yield* assertEqual(
|
|
913
|
+
ops[0]!.path.toTokens(),
|
|
914
|
+
["users", "0", "name"],
|
|
915
|
+
"First path should be correct"
|
|
916
|
+
);
|
|
917
|
+
yield* assertEqual(
|
|
918
|
+
ops[1]!.path.toTokens(),
|
|
919
|
+
["users", "1", "name"],
|
|
920
|
+
"Second path should be correct"
|
|
921
|
+
);
|
|
922
|
+
yield* assertEqual(
|
|
923
|
+
ops[2]!.path.toTokens(),
|
|
924
|
+
["count"],
|
|
925
|
+
"Third path should be correct"
|
|
926
|
+
);
|
|
927
|
+
}),
|
|
928
|
+
},
|
|
929
|
+
|
|
930
|
+
{
|
|
931
|
+
name: "nested path with many segments survives roundtrip",
|
|
932
|
+
category: Categories.TransactionEncoding,
|
|
933
|
+
run: Effect.gen(function* () {
|
|
934
|
+
const storage = yield* HotStorageTag;
|
|
935
|
+
const deepPath = "level1/level2/level3/level4/level5";
|
|
936
|
+
const entry = makeEntryWithPath(1, deepPath, "deep value");
|
|
937
|
+
yield* storage.append("deep-path", entry);
|
|
938
|
+
const entries = yield* storage.getEntries("deep-path", 0);
|
|
939
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
940
|
+
const op = entries[0]!.transaction.ops[0]!;
|
|
941
|
+
yield* assertEqual(
|
|
942
|
+
op.path.toTokens(),
|
|
943
|
+
["level1", "level2", "level3", "level4", "level5"],
|
|
944
|
+
"Deep nested path should survive roundtrip"
|
|
945
|
+
);
|
|
946
|
+
}),
|
|
947
|
+
},
|
|
948
|
+
|
|
949
|
+
{
|
|
950
|
+
name: "empty path survives roundtrip",
|
|
951
|
+
category: Categories.TransactionEncoding,
|
|
952
|
+
run: Effect.gen(function* () {
|
|
953
|
+
const storage = yield* HotStorageTag;
|
|
954
|
+
const entry = makeEntryWithPath(1, "", { root: true });
|
|
955
|
+
yield* storage.append("empty-path", entry);
|
|
956
|
+
const entries = yield* storage.getEntries("empty-path", 0);
|
|
957
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
958
|
+
const op = entries[0]!.transaction.ops[0]!;
|
|
959
|
+
yield* assertTrue(
|
|
960
|
+
op.path._tag === "OperationPath",
|
|
961
|
+
"Empty path should still be OperationPath"
|
|
962
|
+
);
|
|
963
|
+
yield* assertTrue(
|
|
964
|
+
typeof op.path.toTokens === "function",
|
|
965
|
+
"Empty path should have toTokens method"
|
|
966
|
+
);
|
|
967
|
+
}),
|
|
968
|
+
},
|
|
969
|
+
|
|
970
|
+
{
|
|
971
|
+
name: "transaction id is preserved after roundtrip",
|
|
972
|
+
category: Categories.TransactionEncoding,
|
|
973
|
+
run: Effect.gen(function* () {
|
|
974
|
+
const storage = yield* HotStorageTag;
|
|
975
|
+
const entry = makeEntryWithPath(1, "test", "value");
|
|
976
|
+
const originalId = entry.transaction.id;
|
|
977
|
+
yield* storage.append("tx-id-preserve", entry);
|
|
978
|
+
const entries = yield* storage.getEntries("tx-id-preserve", 0);
|
|
979
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
980
|
+
yield* assertEqual(
|
|
981
|
+
entries[0]!.transaction.id,
|
|
982
|
+
originalId,
|
|
983
|
+
"Transaction id should be preserved"
|
|
984
|
+
);
|
|
985
|
+
}),
|
|
986
|
+
},
|
|
987
|
+
|
|
988
|
+
{
|
|
989
|
+
name: "transaction timestamp is preserved after roundtrip",
|
|
990
|
+
category: Categories.TransactionEncoding,
|
|
991
|
+
run: Effect.gen(function* () {
|
|
992
|
+
const storage = yield* HotStorageTag;
|
|
993
|
+
const entry = makeEntryWithPath(1, "test", "value");
|
|
994
|
+
const originalTimestamp = entry.transaction.timestamp;
|
|
995
|
+
yield* storage.append("tx-timestamp-preserve", entry);
|
|
996
|
+
const entries = yield* storage.getEntries("tx-timestamp-preserve", 0);
|
|
997
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
998
|
+
yield* assertEqual(
|
|
999
|
+
entries[0]!.transaction.timestamp,
|
|
1000
|
+
originalTimestamp,
|
|
1001
|
+
"Transaction timestamp should be preserved"
|
|
1002
|
+
);
|
|
1003
|
+
}),
|
|
1004
|
+
},
|
|
1005
|
+
|
|
1006
|
+
{
|
|
1007
|
+
name: "operation kind is preserved after roundtrip",
|
|
1008
|
+
category: Categories.TransactionEncoding,
|
|
1009
|
+
run: Effect.gen(function* () {
|
|
1010
|
+
const storage = yield* HotStorageTag;
|
|
1011
|
+
const entry: WalEntry = {
|
|
1012
|
+
transaction: Transaction.make([
|
|
1013
|
+
Operation.fromDefinition(OperationPath.make("data"), CustomOpDefinition, "test"),
|
|
1014
|
+
]),
|
|
1015
|
+
version: 1,
|
|
1016
|
+
timestamp: Date.now(),
|
|
1017
|
+
};
|
|
1018
|
+
yield* storage.append("op-kind-preserve", entry);
|
|
1019
|
+
const entries = yield* storage.getEntries("op-kind-preserve", 0);
|
|
1020
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
1021
|
+
yield* assertEqual(
|
|
1022
|
+
entries[0]!.transaction.ops[0]!.kind,
|
|
1023
|
+
"custom.operation",
|
|
1024
|
+
"Operation kind should be preserved"
|
|
1025
|
+
);
|
|
1026
|
+
}),
|
|
1027
|
+
},
|
|
1028
|
+
|
|
1029
|
+
{
|
|
1030
|
+
name: "operation payload with complex object survives roundtrip",
|
|
1031
|
+
category: Categories.TransactionEncoding,
|
|
1032
|
+
run: Effect.gen(function* () {
|
|
1033
|
+
const storage = yield* HotStorageTag;
|
|
1034
|
+
const complexPayload = {
|
|
1035
|
+
nested: { value: 42, array: [1, 2, 3] },
|
|
1036
|
+
nullValue: null,
|
|
1037
|
+
string: "test",
|
|
1038
|
+
};
|
|
1039
|
+
const entry = makeEntryWithPath(1, "data", complexPayload);
|
|
1040
|
+
yield* storage.append("complex-payload", entry);
|
|
1041
|
+
const entries = yield* storage.getEntries("complex-payload", 0);
|
|
1042
|
+
yield* assertLength(entries, 1, "Should have one entry");
|
|
1043
|
+
yield* assertEqual(
|
|
1044
|
+
entries[0]!.transaction.ops[0]!.payload,
|
|
1045
|
+
complexPayload,
|
|
1046
|
+
"Complex payload should survive roundtrip"
|
|
1047
|
+
);
|
|
1048
|
+
}),
|
|
1049
|
+
},
|
|
707
1050
|
];
|
|
708
1051
|
|
|
709
1052
|
// =============================================================================
|