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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.turbo/turbo-build.log +99 -57
  2. package/dist/DocumentManager.cjs +109 -39
  3. package/dist/DocumentManager.d.cts +12 -4
  4. package/dist/DocumentManager.d.cts.map +1 -1
  5. package/dist/DocumentManager.d.mts +12 -4
  6. package/dist/DocumentManager.d.mts.map +1 -1
  7. package/dist/DocumentManager.mjs +110 -40
  8. package/dist/DocumentManager.mjs.map +1 -1
  9. package/dist/Errors.cjs +10 -1
  10. package/dist/Errors.d.cts +18 -3
  11. package/dist/Errors.d.cts.map +1 -1
  12. package/dist/Errors.d.mts +18 -3
  13. package/dist/Errors.d.mts.map +1 -1
  14. package/dist/Errors.mjs +9 -1
  15. package/dist/Errors.mjs.map +1 -1
  16. package/dist/HotStorage.cjs +23 -0
  17. package/dist/HotStorage.d.cts +17 -1
  18. package/dist/HotStorage.d.cts.map +1 -1
  19. package/dist/HotStorage.d.mts +17 -1
  20. package/dist/HotStorage.d.mts.map +1 -1
  21. package/dist/HotStorage.mjs +23 -0
  22. package/dist/HotStorage.mjs.map +1 -1
  23. package/dist/Metrics.cjs +23 -1
  24. package/dist/Metrics.d.cts +4 -0
  25. package/dist/Metrics.d.cts.map +1 -1
  26. package/dist/Metrics.d.mts +4 -0
  27. package/dist/Metrics.d.mts.map +1 -1
  28. package/dist/Metrics.mjs +21 -1
  29. package/dist/Metrics.mjs.map +1 -1
  30. package/dist/MimicClusterServerEngine.cjs +114 -36
  31. package/dist/MimicClusterServerEngine.d.cts.map +1 -1
  32. package/dist/MimicClusterServerEngine.d.mts +1 -1
  33. package/dist/MimicClusterServerEngine.d.mts.map +1 -1
  34. package/dist/MimicClusterServerEngine.mjs +119 -41
  35. package/dist/MimicClusterServerEngine.mjs.map +1 -1
  36. package/dist/MimicServer.cjs +1 -1
  37. package/dist/MimicServer.d.cts +1 -1
  38. package/dist/MimicServer.d.cts.map +1 -1
  39. package/dist/MimicServer.d.mts +1 -1
  40. package/dist/MimicServer.d.mts.map +1 -1
  41. package/dist/MimicServer.mjs +1 -1
  42. package/dist/MimicServerEngine.d.cts +7 -4
  43. package/dist/MimicServerEngine.d.cts.map +1 -1
  44. package/dist/MimicServerEngine.d.mts +7 -4
  45. package/dist/MimicServerEngine.d.mts.map +1 -1
  46. package/dist/MimicServerEngine.mjs.map +1 -1
  47. package/dist/index.cjs +1 -1
  48. package/dist/index.mjs +1 -1
  49. package/dist/testing/ColdStorageTestSuite.cjs +508 -0
  50. package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
  51. package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
  52. package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
  53. package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
  54. package/dist/testing/ColdStorageTestSuite.mjs +508 -0
  55. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
  56. package/dist/testing/FailingStorage.cjs +135 -0
  57. package/dist/testing/FailingStorage.d.cts +43 -0
  58. package/dist/testing/FailingStorage.d.cts.map +1 -0
  59. package/dist/testing/FailingStorage.d.mts +43 -0
  60. package/dist/testing/FailingStorage.d.mts.map +1 -0
  61. package/dist/testing/FailingStorage.mjs +136 -0
  62. package/dist/testing/FailingStorage.mjs.map +1 -0
  63. package/dist/testing/HotStorageTestSuite.cjs +585 -0
  64. package/dist/testing/HotStorageTestSuite.d.cts +40 -0
  65. package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
  66. package/dist/testing/HotStorageTestSuite.d.mts +40 -0
  67. package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
  68. package/dist/testing/HotStorageTestSuite.mjs +585 -0
  69. package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
  70. package/dist/testing/StorageIntegrationTestSuite.cjs +349 -0
  71. package/dist/testing/StorageIntegrationTestSuite.d.cts +35 -0
  72. package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
  73. package/dist/testing/StorageIntegrationTestSuite.d.mts +35 -0
  74. package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
  75. package/dist/testing/StorageIntegrationTestSuite.mjs +349 -0
  76. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
  77. package/dist/testing/assertions.cjs +114 -0
  78. package/dist/testing/assertions.mjs +109 -0
  79. package/dist/testing/assertions.mjs.map +1 -0
  80. package/dist/testing/index.cjs +14 -0
  81. package/dist/testing/index.d.cts +6 -0
  82. package/dist/testing/index.d.mts +6 -0
  83. package/dist/testing/index.mjs +7 -0
  84. package/dist/testing/types.cjs +15 -0
  85. package/dist/testing/types.d.cts +90 -0
  86. package/dist/testing/types.d.cts.map +1 -0
  87. package/dist/testing/types.d.mts +90 -0
  88. package/dist/testing/types.d.mts.map +1 -0
  89. package/dist/testing/types.mjs +16 -0
  90. package/dist/testing/types.mjs.map +1 -0
  91. package/package.json +8 -3
  92. package/src/DocumentManager.ts +195 -87
  93. package/src/Errors.ts +15 -1
  94. package/src/HotStorage.ts +75 -1
  95. package/src/Metrics.ts +24 -0
  96. package/src/MimicClusterServerEngine.ts +178 -56
  97. package/src/MimicServerEngine.ts +7 -3
  98. package/src/testing/ColdStorageTestSuite.ts +589 -0
  99. package/src/testing/FailingStorage.ts +286 -0
  100. package/src/testing/HotStorageTestSuite.ts +762 -0
  101. package/src/testing/StorageIntegrationTestSuite.ts +504 -0
  102. package/src/testing/assertions.ts +181 -0
  103. package/src/testing/index.ts +83 -0
  104. package/src/testing/types.ts +100 -0
  105. package/tests/ColdStorage.test.ts +8 -120
  106. package/tests/HotStorage.test.ts +7 -126
  107. package/tests/StorageIntegration.test.ts +259 -0
  108. package/tsdown.config.ts +1 -1
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @voidhash/mimic-effect/testing
3
+ *
4
+ * Test utilities for verifying ColdStorage and HotStorage adapter implementations.
5
+ *
6
+ * These utilities help ensure that custom storage adapters correctly implement
7
+ * the required interfaces and can reliably persist/retrieve data without loss.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { ColdStorageTestSuite, HotStorageTestSuite } from "@voidhash/mimic-effect/testing";
12
+ * import { describe, it } from "vitest";
13
+ * import { Effect } from "effect";
14
+ *
15
+ * // Test your ColdStorage adapter
16
+ * describe("MyColdStorageAdapter", () => {
17
+ * const layer = MyColdStorageAdapter.make();
18
+ *
19
+ * for (const test of ColdStorageTestSuite.makeTests()) {
20
+ * it(test.name, () =>
21
+ * Effect.runPromise(test.run.pipe(Effect.provide(layer)))
22
+ * );
23
+ * }
24
+ * });
25
+ *
26
+ * // Test your HotStorage adapter
27
+ * describe("MyHotStorageAdapter", () => {
28
+ * const layer = MyHotStorageAdapter.make();
29
+ *
30
+ * for (const test of HotStorageTestSuite.makeTests()) {
31
+ * it(test.name, () =>
32
+ * Effect.runPromise(test.run.pipe(Effect.provide(layer)))
33
+ * );
34
+ * }
35
+ * });
36
+ * ```
37
+ *
38
+ * @since 1.0.0
39
+ */
40
+
41
+ // =============================================================================
42
+ // Types
43
+ // =============================================================================
44
+
45
+ export type {
46
+ StorageTestCase,
47
+ TestResults,
48
+ FailedTest,
49
+ } from "./types";
50
+
51
+ export { TestError } from "./types";
52
+
53
+ // =============================================================================
54
+ // Test Suites
55
+ // =============================================================================
56
+
57
+ export {
58
+ ColdStorageTestSuite,
59
+ type ColdStorageTestError,
60
+ } from "./ColdStorageTestSuite";
61
+ export {
62
+ HotStorageTestSuite,
63
+ type HotStorageTestError,
64
+ } from "./HotStorageTestSuite";
65
+ export { StorageIntegrationTestSuite } from "./StorageIntegrationTestSuite";
66
+
67
+ // =============================================================================
68
+ // Test Utilities
69
+ // =============================================================================
70
+
71
+ export {
72
+ FailingStorage,
73
+ type FailingColdStorageConfig,
74
+ type FailingHotStorageConfig,
75
+ } from "./FailingStorage";
76
+
77
+ // =============================================================================
78
+ // Re-export Categories for Convenience
79
+ // =============================================================================
80
+
81
+ export { Categories as ColdStorageCategories } from "./ColdStorageTestSuite";
82
+ export { Categories as HotStorageCategories } from "./HotStorageTestSuite";
83
+ export { Categories as IntegrationCategories } from "./StorageIntegrationTestSuite";
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @voidhash/mimic-effect/testing - Core Types
3
+ *
4
+ * Types used by the storage adapter test utilities.
5
+ */
6
+ import { Data, Effect } from "effect";
7
+
8
+ // =============================================================================
9
+ // Error Types
10
+ // =============================================================================
11
+
12
+ /**
13
+ * Error thrown when a test assertion fails.
14
+ */
15
+ export class TestError extends Data.TaggedError("TestError")<{
16
+ /** Description of what failed */
17
+ readonly message: string;
18
+ /** Expected value (if applicable) */
19
+ readonly expected?: unknown;
20
+ /** Actual value received (if applicable) */
21
+ readonly actual?: unknown;
22
+ }> {}
23
+
24
+ // =============================================================================
25
+ // Test Case Types
26
+ // =============================================================================
27
+
28
+ /**
29
+ * A single storage adapter test case.
30
+ *
31
+ * Test cases are framework-agnostic Effects that can be run with any test runner.
32
+ *
33
+ * @template E - The error type for this test case
34
+ * @template R - The Effect requirements (e.g., ColdStorageTag or HotStorageTag)
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * // Using with vitest
39
+ * const tests = ColdStorageTestSuite.makeTests();
40
+ *
41
+ * describe("MyAdapter", () => {
42
+ * for (const test of tests) {
43
+ * it(test.name, () =>
44
+ * Effect.runPromise(test.run.pipe(Effect.provide(myAdapterLayer)))
45
+ * );
46
+ * }
47
+ * });
48
+ * ```
49
+ */
50
+ export interface StorageTestCase<E, R> {
51
+ /** Human-readable test name */
52
+ readonly name: string;
53
+ /** Category for grouping (e.g., "Basic Operations", "Data Integrity") */
54
+ readonly category: string;
55
+ /** The test as an Effect - succeeds if test passes, fails with error if not */
56
+ readonly run: Effect.Effect<void, E, R>;
57
+ }
58
+
59
+ // =============================================================================
60
+ // Test Results Types
61
+ // =============================================================================
62
+
63
+ /**
64
+ * Result of a failed test.
65
+ */
66
+ export interface FailedTest<E, R> {
67
+ /** The test case that failed */
68
+ readonly test: StorageTestCase<E, R>;
69
+ /** The error that caused the failure */
70
+ readonly error: E;
71
+ }
72
+
73
+ /**
74
+ * Results from running all tests in a suite.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const results = await Effect.runPromise(
79
+ * ColdStorageTestSuite.runAll().pipe(Effect.provide(myAdapterLayer))
80
+ * );
81
+ *
82
+ * console.log(`Passed: ${results.passCount}/${results.total}`);
83
+ *
84
+ * for (const { test, error } of results.failed) {
85
+ * console.error(`FAIL: ${test.name} - ${error._tag}`);
86
+ * }
87
+ * ```
88
+ */
89
+ export interface TestResults<E, R> {
90
+ /** Tests that passed */
91
+ readonly passed: StorageTestCase<E, R>[];
92
+ /** Tests that failed with their errors */
93
+ readonly failed: FailedTest<E, R>[];
94
+ /** Total number of tests run */
95
+ readonly total: number;
96
+ /** Number of tests that passed */
97
+ readonly passCount: number;
98
+ /** Number of tests that failed */
99
+ readonly failCount: number;
100
+ }
@@ -1,131 +1,19 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { Effect, Layer } from "effect";
2
+ import { Effect } from "effect";
3
3
  import { ColdStorage, ColdStorageTag } from "../src/ColdStorage";
4
- import type { StoredDocument } from "../src/Types";
4
+ import { ColdStorageTestSuite } from "../src/testing";
5
5
 
6
6
  describe("ColdStorage", () => {
7
7
  describe("InMemory", () => {
8
+ // Use the test suite utilities for comprehensive testing
8
9
  const layer = ColdStorage.InMemory.make();
9
10
 
10
- it("should return undefined for missing document", async () => {
11
- const result = await Effect.runPromise(
12
- Effect.gen(function* () {
13
- const storage = yield* ColdStorageTag;
14
- return yield* storage.load("non-existent");
15
- }).pipe(Effect.provide(layer))
11
+ // Run all test suite tests
12
+ for (const test of ColdStorageTestSuite.makeTests()) {
13
+ it(`[${test.category}] ${test.name}`, () =>
14
+ Effect.runPromise(test.run.pipe(Effect.provide(layer)))
16
15
  );
17
-
18
- expect(result).toBeUndefined();
19
- });
20
-
21
- it("should save and load document", async () => {
22
- const doc: StoredDocument = {
23
- state: { title: "Test", count: 42 },
24
- version: 5,
25
- schemaVersion: 1,
26
- savedAt: Date.now(),
27
- };
28
-
29
- const result = await Effect.runPromise(
30
- Effect.gen(function* () {
31
- const storage = yield* ColdStorageTag;
32
- yield* storage.save("doc-1", doc);
33
- return yield* storage.load("doc-1");
34
- }).pipe(Effect.provide(layer))
35
- );
36
-
37
- expect(result).toEqual(doc);
38
- });
39
-
40
- it("should update existing document", async () => {
41
- const doc1: StoredDocument = {
42
- state: { title: "First" },
43
- version: 1,
44
- schemaVersion: 1,
45
- savedAt: Date.now(),
46
- };
47
-
48
- const doc2: StoredDocument = {
49
- state: { title: "Second" },
50
- version: 2,
51
- schemaVersion: 1,
52
- savedAt: Date.now(),
53
- };
54
-
55
- const result = await Effect.runPromise(
56
- Effect.gen(function* () {
57
- const storage = yield* ColdStorageTag;
58
- yield* storage.save("doc-1", doc1);
59
- yield* storage.save("doc-1", doc2);
60
- return yield* storage.load("doc-1");
61
- }).pipe(Effect.provide(layer))
62
- );
63
-
64
- expect(result).toEqual(doc2);
65
- });
66
-
67
- it("should delete document", async () => {
68
- const doc: StoredDocument = {
69
- state: { title: "To Delete" },
70
- version: 1,
71
- schemaVersion: 1,
72
- savedAt: Date.now(),
73
- };
74
-
75
- const result = await Effect.runPromise(
76
- Effect.gen(function* () {
77
- const storage = yield* ColdStorageTag;
78
- yield* storage.save("doc-1", doc);
79
- yield* storage.delete("doc-1");
80
- return yield* storage.load("doc-1");
81
- }).pipe(Effect.provide(layer))
82
- );
83
-
84
- expect(result).toBeUndefined();
85
- });
86
-
87
- it("should handle multiple documents independently", async () => {
88
- const doc1: StoredDocument = {
89
- state: { title: "Doc 1" },
90
- version: 1,
91
- schemaVersion: 1,
92
- savedAt: Date.now(),
93
- };
94
-
95
- const doc2: StoredDocument = {
96
- state: { title: "Doc 2" },
97
- version: 2,
98
- schemaVersion: 1,
99
- savedAt: Date.now(),
100
- };
101
-
102
- const result = await Effect.runPromise(
103
- Effect.gen(function* () {
104
- const storage = yield* ColdStorageTag;
105
- yield* storage.save("doc-1", doc1);
106
- yield* storage.save("doc-2", doc2);
107
-
108
- const loaded1 = yield* storage.load("doc-1");
109
- const loaded2 = yield* storage.load("doc-2");
110
-
111
- return { loaded1, loaded2 };
112
- }).pipe(Effect.provide(layer))
113
- );
114
-
115
- expect(result.loaded1).toEqual(doc1);
116
- expect(result.loaded2).toEqual(doc2);
117
- });
118
-
119
- it("should not error when deleting non-existent document", async () => {
120
- await expect(
121
- Effect.runPromise(
122
- Effect.gen(function* () {
123
- const storage = yield* ColdStorageTag;
124
- yield* storage.delete("non-existent");
125
- }).pipe(Effect.provide(layer))
126
- )
127
- ).resolves.toBeUndefined();
128
- });
16
+ }
129
17
  });
130
18
 
131
19
  describe("Tag", () => {
@@ -1,138 +1,19 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { Effect } from "effect";
3
3
  import { HotStorage, HotStorageTag } from "../src/HotStorage";
4
- import type { WalEntry } from "../src/Types";
5
- import { Transaction } from "@voidhash/mimic";
4
+ import { HotStorageTestSuite } from "../src/testing";
6
5
 
7
6
  describe("HotStorage", () => {
8
7
  describe("InMemory", () => {
8
+ // Use the test suite utilities for comprehensive testing
9
9
  const layer = HotStorage.InMemory.make();
10
10
 
11
- const makeEntry = (version: number): WalEntry => ({
12
- transaction: Transaction.make([]),
13
- version,
14
- timestamp: Date.now(),
15
- });
16
-
17
- it("should return empty array for missing document", async () => {
18
- const result = await Effect.runPromise(
19
- Effect.gen(function* () {
20
- const storage = yield* HotStorageTag;
21
- return yield* storage.getEntries("non-existent", 0);
22
- }).pipe(Effect.provide(layer))
23
- );
24
-
25
- expect(result).toEqual([]);
26
- });
27
-
28
- it("should append and retrieve entries", async () => {
29
- const entry1 = makeEntry(1);
30
- const entry2 = makeEntry(2);
31
-
32
- const result = await Effect.runPromise(
33
- Effect.gen(function* () {
34
- const storage = yield* HotStorageTag;
35
- yield* storage.append("doc-1", entry1);
36
- yield* storage.append("doc-1", entry2);
37
- return yield* storage.getEntries("doc-1", 0);
38
- }).pipe(Effect.provide(layer))
39
- );
40
-
41
- expect(result.length).toBe(2);
42
- expect(result[0]!.version).toBe(1);
43
- expect(result[1]!.version).toBe(2);
44
- });
45
-
46
- it("should filter entries by sinceVersion", async () => {
47
- const entries = [makeEntry(1), makeEntry(2), makeEntry(3), makeEntry(4)];
48
-
49
- const result = await Effect.runPromise(
50
- Effect.gen(function* () {
51
- const storage = yield* HotStorageTag;
52
- for (const entry of entries) {
53
- yield* storage.append("doc-1", entry);
54
- }
55
- return yield* storage.getEntries("doc-1", 2);
56
- }).pipe(Effect.provide(layer))
57
- );
58
-
59
- expect(result.length).toBe(2);
60
- expect(result[0]!.version).toBe(3);
61
- expect(result[1]!.version).toBe(4);
62
- });
63
-
64
- it("should truncate entries up to version", async () => {
65
- const entries = [makeEntry(1), makeEntry(2), makeEntry(3), makeEntry(4)];
66
-
67
- const result = await Effect.runPromise(
68
- Effect.gen(function* () {
69
- const storage = yield* HotStorageTag;
70
- for (const entry of entries) {
71
- yield* storage.append("doc-1", entry);
72
- }
73
- yield* storage.truncate("doc-1", 2);
74
- return yield* storage.getEntries("doc-1", 0);
75
- }).pipe(Effect.provide(layer))
11
+ // Run all test suite tests
12
+ for (const test of HotStorageTestSuite.makeTests()) {
13
+ it(`[${test.category}] ${test.name}`, () =>
14
+ Effect.runPromise(test.run.pipe(Effect.provide(layer)))
76
15
  );
77
-
78
- expect(result.length).toBe(2);
79
- expect(result[0]!.version).toBe(3);
80
- expect(result[1]!.version).toBe(4);
81
- });
82
-
83
- it("should maintain order after append", async () => {
84
- const entries = [makeEntry(3), makeEntry(1), makeEntry(2)];
85
-
86
- const result = await Effect.runPromise(
87
- Effect.gen(function* () {
88
- const storage = yield* HotStorageTag;
89
- for (const entry of entries) {
90
- yield* storage.append("doc-1", entry);
91
- }
92
- return yield* storage.getEntries("doc-1", 0);
93
- }).pipe(Effect.provide(layer))
94
- );
95
-
96
- // Should be sorted by version
97
- expect(result.length).toBe(3);
98
- expect(result[0]!.version).toBe(1);
99
- expect(result[1]!.version).toBe(2);
100
- expect(result[2]!.version).toBe(3);
101
- });
102
-
103
- it("should isolate documents", async () => {
104
- const entry1 = makeEntry(1);
105
- const entry2 = makeEntry(2);
106
-
107
- const result = await Effect.runPromise(
108
- Effect.gen(function* () {
109
- const storage = yield* HotStorageTag;
110
- yield* storage.append("doc-1", entry1);
111
- yield* storage.append("doc-2", entry2);
112
-
113
- const entries1 = yield* storage.getEntries("doc-1", 0);
114
- const entries2 = yield* storage.getEntries("doc-2", 0);
115
-
116
- return { entries1, entries2 };
117
- }).pipe(Effect.provide(layer))
118
- );
119
-
120
- expect(result.entries1.length).toBe(1);
121
- expect(result.entries1[0]!.version).toBe(1);
122
- expect(result.entries2.length).toBe(1);
123
- expect(result.entries2[0]!.version).toBe(2);
124
- });
125
-
126
- it("should not error when truncating non-existent document", async () => {
127
- await expect(
128
- Effect.runPromise(
129
- Effect.gen(function* () {
130
- const storage = yield* HotStorageTag;
131
- yield* storage.truncate("non-existent", 5);
132
- }).pipe(Effect.provide(layer))
133
- )
134
- ).resolves.toBeUndefined();
135
- });
16
+ }
136
17
  });
137
18
 
138
19
  describe("Tag", () => {