@voidhash/mimic-effect 0.0.9 → 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 (227) hide show
  1. package/.turbo/turbo-build.log +136 -90
  2. package/README.md +385 -0
  3. package/dist/ColdStorage.cjs +60 -0
  4. package/dist/ColdStorage.d.cts +53 -0
  5. package/dist/ColdStorage.d.cts.map +1 -0
  6. package/dist/ColdStorage.d.mts +53 -0
  7. package/dist/ColdStorage.d.mts.map +1 -0
  8. package/dist/ColdStorage.mjs +60 -0
  9. package/dist/ColdStorage.mjs.map +1 -0
  10. package/dist/DocumentManager.cjs +263 -82
  11. package/dist/DocumentManager.d.cts +44 -22
  12. package/dist/DocumentManager.d.cts.map +1 -1
  13. package/dist/DocumentManager.d.mts +44 -22
  14. package/dist/DocumentManager.d.mts.map +1 -1
  15. package/dist/DocumentManager.mjs +259 -67
  16. package/dist/DocumentManager.mjs.map +1 -1
  17. package/dist/Errors.cjs +54 -0
  18. package/dist/Errors.d.cts +96 -0
  19. package/dist/Errors.d.cts.map +1 -0
  20. package/dist/Errors.d.mts +96 -0
  21. package/dist/Errors.d.mts.map +1 -0
  22. package/dist/Errors.mjs +48 -0
  23. package/dist/Errors.mjs.map +1 -0
  24. package/dist/HotStorage.cjs +100 -0
  25. package/dist/HotStorage.d.cts +70 -0
  26. package/dist/HotStorage.d.cts.map +1 -0
  27. package/dist/HotStorage.d.mts +70 -0
  28. package/dist/HotStorage.d.mts.map +1 -0
  29. package/dist/HotStorage.mjs +100 -0
  30. package/dist/HotStorage.mjs.map +1 -0
  31. package/dist/Metrics.cjs +143 -0
  32. package/dist/Metrics.d.cts +31 -0
  33. package/dist/Metrics.d.cts.map +1 -0
  34. package/dist/Metrics.d.mts +31 -0
  35. package/dist/Metrics.d.mts.map +1 -0
  36. package/dist/Metrics.mjs +126 -0
  37. package/dist/Metrics.mjs.map +1 -0
  38. package/dist/MimicAuthService.cjs +61 -45
  39. package/dist/MimicAuthService.d.cts +61 -48
  40. package/dist/MimicAuthService.d.cts.map +1 -1
  41. package/dist/MimicAuthService.d.mts +61 -48
  42. package/dist/MimicAuthService.d.mts.map +1 -1
  43. package/dist/MimicAuthService.mjs +60 -36
  44. package/dist/MimicAuthService.mjs.map +1 -1
  45. package/dist/MimicClusterServerEngine.cjs +521 -0
  46. package/dist/MimicClusterServerEngine.d.cts +17 -0
  47. package/dist/MimicClusterServerEngine.d.cts.map +1 -0
  48. package/dist/MimicClusterServerEngine.d.mts +17 -0
  49. package/dist/MimicClusterServerEngine.d.mts.map +1 -0
  50. package/dist/MimicClusterServerEngine.mjs +523 -0
  51. package/dist/MimicClusterServerEngine.mjs.map +1 -0
  52. package/dist/MimicServer.cjs +205 -96
  53. package/dist/MimicServer.d.cts +9 -110
  54. package/dist/MimicServer.d.cts.map +1 -1
  55. package/dist/MimicServer.d.mts +9 -110
  56. package/dist/MimicServer.d.mts.map +1 -1
  57. package/dist/MimicServer.mjs +206 -90
  58. package/dist/MimicServer.mjs.map +1 -1
  59. package/dist/MimicServerEngine.cjs +97 -0
  60. package/dist/MimicServerEngine.d.cts +78 -0
  61. package/dist/MimicServerEngine.d.cts.map +1 -0
  62. package/dist/MimicServerEngine.d.mts +78 -0
  63. package/dist/MimicServerEngine.d.mts.map +1 -0
  64. package/dist/MimicServerEngine.mjs +97 -0
  65. package/dist/MimicServerEngine.mjs.map +1 -0
  66. package/dist/PresenceManager.cjs +75 -91
  67. package/dist/PresenceManager.d.cts +17 -66
  68. package/dist/PresenceManager.d.cts.map +1 -1
  69. package/dist/PresenceManager.d.mts +17 -66
  70. package/dist/PresenceManager.d.mts.map +1 -1
  71. package/dist/PresenceManager.mjs +74 -78
  72. package/dist/PresenceManager.mjs.map +1 -1
  73. package/dist/Protocol.cjs +146 -0
  74. package/dist/Protocol.d.cts +203 -0
  75. package/dist/Protocol.d.cts.map +1 -0
  76. package/dist/Protocol.d.mts +203 -0
  77. package/dist/Protocol.d.mts.map +1 -0
  78. package/dist/Protocol.mjs +132 -0
  79. package/dist/Protocol.mjs.map +1 -0
  80. package/dist/Types.d.cts +172 -0
  81. package/dist/Types.d.cts.map +1 -0
  82. package/dist/Types.d.mts +172 -0
  83. package/dist/Types.d.mts.map +1 -0
  84. package/dist/_virtual/rolldown_runtime.cjs +1 -25
  85. package/dist/_virtual/rolldown_runtime.mjs +4 -1
  86. package/dist/index.cjs +37 -75
  87. package/dist/index.d.cts +13 -12
  88. package/dist/index.d.mts +13 -12
  89. package/dist/index.mjs +12 -12
  90. package/dist/testing/ColdStorageTestSuite.cjs +508 -0
  91. package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
  92. package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
  93. package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
  94. package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
  95. package/dist/testing/ColdStorageTestSuite.mjs +508 -0
  96. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
  97. package/dist/testing/FailingStorage.cjs +135 -0
  98. package/dist/testing/FailingStorage.d.cts +43 -0
  99. package/dist/testing/FailingStorage.d.cts.map +1 -0
  100. package/dist/testing/FailingStorage.d.mts +43 -0
  101. package/dist/testing/FailingStorage.d.mts.map +1 -0
  102. package/dist/testing/FailingStorage.mjs +136 -0
  103. package/dist/testing/FailingStorage.mjs.map +1 -0
  104. package/dist/testing/HotStorageTestSuite.cjs +585 -0
  105. package/dist/testing/HotStorageTestSuite.d.cts +40 -0
  106. package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
  107. package/dist/testing/HotStorageTestSuite.d.mts +40 -0
  108. package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
  109. package/dist/testing/HotStorageTestSuite.mjs +585 -0
  110. package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
  111. package/dist/testing/StorageIntegrationTestSuite.cjs +349 -0
  112. package/dist/testing/StorageIntegrationTestSuite.d.cts +35 -0
  113. package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
  114. package/dist/testing/StorageIntegrationTestSuite.d.mts +35 -0
  115. package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
  116. package/dist/testing/StorageIntegrationTestSuite.mjs +349 -0
  117. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
  118. package/dist/testing/assertions.cjs +114 -0
  119. package/dist/testing/assertions.mjs +109 -0
  120. package/dist/testing/assertions.mjs.map +1 -0
  121. package/dist/testing/index.cjs +14 -0
  122. package/dist/testing/index.d.cts +6 -0
  123. package/dist/testing/index.d.mts +6 -0
  124. package/dist/testing/index.mjs +7 -0
  125. package/dist/testing/types.cjs +15 -0
  126. package/dist/testing/types.d.cts +90 -0
  127. package/dist/testing/types.d.cts.map +1 -0
  128. package/dist/testing/types.d.mts +90 -0
  129. package/dist/testing/types.d.mts.map +1 -0
  130. package/dist/testing/types.mjs +16 -0
  131. package/dist/testing/types.mjs.map +1 -0
  132. package/package.json +18 -3
  133. package/src/ColdStorage.ts +136 -0
  134. package/src/DocumentManager.ts +550 -190
  135. package/src/Errors.ts +114 -0
  136. package/src/HotStorage.ts +239 -0
  137. package/src/Metrics.ts +187 -0
  138. package/src/MimicAuthService.ts +126 -64
  139. package/src/MimicClusterServerEngine.ts +946 -0
  140. package/src/MimicServer.ts +448 -195
  141. package/src/MimicServerEngine.ts +276 -0
  142. package/src/PresenceManager.ts +169 -240
  143. package/src/Protocol.ts +350 -0
  144. package/src/Types.ts +231 -0
  145. package/src/index.ts +57 -23
  146. package/src/testing/ColdStorageTestSuite.ts +589 -0
  147. package/src/testing/FailingStorage.ts +286 -0
  148. package/src/testing/HotStorageTestSuite.ts +762 -0
  149. package/src/testing/StorageIntegrationTestSuite.ts +504 -0
  150. package/src/testing/assertions.ts +181 -0
  151. package/src/testing/index.ts +83 -0
  152. package/src/testing/types.ts +100 -0
  153. package/tests/ColdStorage.test.ts +24 -0
  154. package/tests/DocumentManager.test.ts +158 -287
  155. package/tests/HotStorage.test.ts +24 -0
  156. package/tests/MimicAuthService.test.ts +102 -134
  157. package/tests/MimicClusterServerEngine.test.ts +587 -0
  158. package/tests/MimicServer.test.ts +90 -226
  159. package/tests/MimicServerEngine.test.ts +521 -0
  160. package/tests/PresenceManager.test.ts +22 -63
  161. package/tests/Protocol.test.ts +190 -0
  162. package/tests/StorageIntegration.test.ts +259 -0
  163. package/tsconfig.json +1 -1
  164. package/tsdown.config.ts +1 -1
  165. package/dist/DocumentProtocol.cjs +0 -94
  166. package/dist/DocumentProtocol.d.cts +0 -113
  167. package/dist/DocumentProtocol.d.cts.map +0 -1
  168. package/dist/DocumentProtocol.d.mts +0 -113
  169. package/dist/DocumentProtocol.d.mts.map +0 -1
  170. package/dist/DocumentProtocol.mjs +0 -89
  171. package/dist/DocumentProtocol.mjs.map +0 -1
  172. package/dist/MimicConfig.cjs +0 -60
  173. package/dist/MimicConfig.d.cts +0 -141
  174. package/dist/MimicConfig.d.cts.map +0 -1
  175. package/dist/MimicConfig.d.mts +0 -141
  176. package/dist/MimicConfig.d.mts.map +0 -1
  177. package/dist/MimicConfig.mjs +0 -50
  178. package/dist/MimicConfig.mjs.map +0 -1
  179. package/dist/MimicDataStorage.cjs +0 -83
  180. package/dist/MimicDataStorage.d.cts +0 -113
  181. package/dist/MimicDataStorage.d.cts.map +0 -1
  182. package/dist/MimicDataStorage.d.mts +0 -113
  183. package/dist/MimicDataStorage.d.mts.map +0 -1
  184. package/dist/MimicDataStorage.mjs +0 -74
  185. package/dist/MimicDataStorage.mjs.map +0 -1
  186. package/dist/WebSocketHandler.cjs +0 -365
  187. package/dist/WebSocketHandler.d.cts +0 -34
  188. package/dist/WebSocketHandler.d.cts.map +0 -1
  189. package/dist/WebSocketHandler.d.mts +0 -34
  190. package/dist/WebSocketHandler.d.mts.map +0 -1
  191. package/dist/WebSocketHandler.mjs +0 -355
  192. package/dist/WebSocketHandler.mjs.map +0 -1
  193. package/dist/auth/NoAuth.cjs +0 -43
  194. package/dist/auth/NoAuth.d.cts +0 -22
  195. package/dist/auth/NoAuth.d.cts.map +0 -1
  196. package/dist/auth/NoAuth.d.mts +0 -22
  197. package/dist/auth/NoAuth.d.mts.map +0 -1
  198. package/dist/auth/NoAuth.mjs +0 -36
  199. package/dist/auth/NoAuth.mjs.map +0 -1
  200. package/dist/errors.cjs +0 -74
  201. package/dist/errors.d.cts +0 -89
  202. package/dist/errors.d.cts.map +0 -1
  203. package/dist/errors.d.mts +0 -89
  204. package/dist/errors.d.mts.map +0 -1
  205. package/dist/errors.mjs +0 -67
  206. package/dist/errors.mjs.map +0 -1
  207. package/dist/storage/InMemoryDataStorage.cjs +0 -57
  208. package/dist/storage/InMemoryDataStorage.d.cts +0 -19
  209. package/dist/storage/InMemoryDataStorage.d.cts.map +0 -1
  210. package/dist/storage/InMemoryDataStorage.d.mts +0 -19
  211. package/dist/storage/InMemoryDataStorage.d.mts.map +0 -1
  212. package/dist/storage/InMemoryDataStorage.mjs +0 -48
  213. package/dist/storage/InMemoryDataStorage.mjs.map +0 -1
  214. package/src/DocumentProtocol.ts +0 -112
  215. package/src/MimicConfig.ts +0 -211
  216. package/src/MimicDataStorage.ts +0 -157
  217. package/src/WebSocketHandler.ts +0 -735
  218. package/src/auth/NoAuth.ts +0 -46
  219. package/src/errors.ts +0 -113
  220. package/src/storage/InMemoryDataStorage.ts +0 -66
  221. package/tests/DocumentProtocol.test.ts +0 -113
  222. package/tests/InMemoryDataStorage.test.ts +0 -190
  223. package/tests/MimicConfig.test.ts +0 -290
  224. package/tests/MimicDataStorage.test.ts +0 -190
  225. package/tests/NoAuth.test.ts +0 -94
  226. package/tests/WebSocketHandler.test.ts +0 -321
  227. package/tests/errors.test.ts +0 -77
@@ -1,12 +1,14 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import * as Effect from "effect/Effect";
3
- import * as Stream from "effect/Stream";
4
- import * as Layer from "effect/Layer";
5
- import * as Fiber from "effect/Fiber";
6
- import { Primitive, OperationPath, Document, Transaction } from "@voidhash/mimic";
7
- import * as DocumentManager from "../src/DocumentManager";
8
- import * as MimicConfig from "../src/MimicConfig";
9
- import * as InMemoryDataStorage from "../src/storage/InMemoryDataStorage";
2
+ import { Effect, Layer, Stream, Fiber, Duration } from "effect";
3
+ import { Primitive, Document, Transaction } from "@voidhash/mimic";
4
+ import {
5
+ DocumentManager,
6
+ DocumentManagerTag,
7
+ DocumentManagerConfigTag,
8
+ } from "../src/DocumentManager";
9
+ import { ColdStorage } from "../src/ColdStorage";
10
+ import { HotStorage } from "../src/HotStorage";
11
+ import type { ResolvedConfig } from "../src/Types";
10
12
 
11
13
  // =============================================================================
12
14
  // Test Schema
@@ -21,15 +23,30 @@ const TestSchema = Primitive.Struct({
21
23
  // Test Layer
22
24
  // =============================================================================
23
25
 
24
- const makeTestLayer = () => {
25
- const configLayer = MimicConfig.layer({
26
+ const makeTestLayer = (options?: {
27
+ initial?: { title?: string; count?: number };
28
+ }) => {
29
+ const config: ResolvedConfig<typeof TestSchema> = {
26
30
  schema: TestSchema,
31
+ initial: options?.initial,
32
+ presence: undefined,
33
+ maxIdleTime: Duration.minutes(5),
27
34
  maxTransactionHistory: 100,
28
- });
35
+ snapshot: {
36
+ interval: Duration.minutes(5),
37
+ transactionThreshold: 100,
38
+ },
39
+ };
40
+
41
+ const configLayer = Layer.succeed(
42
+ DocumentManagerConfigTag,
43
+ config as ResolvedConfig<Primitive.AnyPrimitive>
44
+ );
29
45
 
30
46
  return DocumentManager.layer.pipe(
31
47
  Layer.provide(configLayer),
32
- Layer.provide(InMemoryDataStorage.layer)
48
+ Layer.provide(ColdStorage.InMemory.make()),
49
+ Layer.provide(HotStorage.InMemory.make())
33
50
  );
34
51
  };
35
52
 
@@ -37,20 +54,16 @@ const makeTestLayer = () => {
37
54
  // Helper Functions
38
55
  // =============================================================================
39
56
 
40
- /**
41
- * Create a valid operation using the Document API
42
- */
43
- const createValidTransaction = (id: string, title: string): Transaction.Transaction => {
57
+ const createValidTransaction = (
58
+ id: string,
59
+ title: string
60
+ ): Transaction.Transaction => {
44
61
  const doc = Document.make(TestSchema);
45
62
  doc.transaction((root) => {
46
63
  root.title.set(title);
47
64
  });
48
65
  const tx = doc.flush();
49
- // Override the ID to make it deterministic for tests
50
- return {
51
- ...tx,
52
- id,
53
- };
66
+ return { ...tx, id };
54
67
  };
55
68
 
56
69
  const createEmptyTransaction = (id: string): Transaction.Transaction => ({
@@ -67,11 +80,13 @@ describe("DocumentManager", () => {
67
80
  describe("submit", () => {
68
81
  it("should accept valid transactions", async () => {
69
82
  const result = await Effect.runPromise(
70
- Effect.gen(function* () {
71
- const manager = yield* DocumentManager.DocumentManagerTag;
72
- const tx = createValidTransaction("tx-1", "Hello World");
73
- return yield* manager.submit("doc-1", tx);
74
- }).pipe(Effect.provide(makeTestLayer()))
83
+ Effect.scoped(
84
+ Effect.gen(function* () {
85
+ const manager = yield* DocumentManagerTag;
86
+ const tx = createValidTransaction("tx-1", "Hello World");
87
+ return yield* manager.submit("doc-1", tx);
88
+ })
89
+ ).pipe(Effect.provide(makeTestLayer()))
75
90
  );
76
91
 
77
92
  expect(result.success).toBe(true);
@@ -82,11 +97,13 @@ describe("DocumentManager", () => {
82
97
 
83
98
  it("should reject empty transactions", async () => {
84
99
  const result = await Effect.runPromise(
85
- Effect.gen(function* () {
86
- const manager = yield* DocumentManager.DocumentManagerTag;
87
- const tx = createEmptyTransaction("tx-empty");
88
- return yield* manager.submit("doc-1", tx);
89
- }).pipe(Effect.provide(makeTestLayer()))
100
+ Effect.scoped(
101
+ Effect.gen(function* () {
102
+ const manager = yield* DocumentManagerTag;
103
+ const tx = createEmptyTransaction("tx-empty");
104
+ return yield* manager.submit("doc-1", tx);
105
+ })
106
+ ).pipe(Effect.provide(makeTestLayer()))
90
107
  );
91
108
 
92
109
  expect(result.success).toBe(false);
@@ -97,18 +114,17 @@ describe("DocumentManager", () => {
97
114
 
98
115
  it("should reject duplicate transactions", async () => {
99
116
  const result = await Effect.runPromise(
100
- Effect.gen(function* () {
101
- const manager = yield* DocumentManager.DocumentManagerTag;
102
- const tx = createValidTransaction("tx-dup", "First");
117
+ Effect.scoped(
118
+ Effect.gen(function* () {
119
+ const manager = yield* DocumentManagerTag;
120
+ const tx = createValidTransaction("tx-dup", "First");
103
121
 
104
- // Submit first time
105
- const first = yield* manager.submit("doc-1", tx);
122
+ const first = yield* manager.submit("doc-1", tx);
123
+ const second = yield* manager.submit("doc-1", tx);
106
124
 
107
- // Submit same transaction again
108
- const second = yield* manager.submit("doc-1", tx);
109
-
110
- return { first, second };
111
- }).pipe(Effect.provide(makeTestLayer()))
125
+ return { first, second };
126
+ })
127
+ ).pipe(Effect.provide(makeTestLayer()))
112
128
  );
113
129
 
114
130
  expect(result.first.success).toBe(true);
@@ -122,19 +138,21 @@ describe("DocumentManager", () => {
122
138
 
123
139
  it("should increment version with each successful transaction", async () => {
124
140
  const result = await Effect.runPromise(
125
- Effect.gen(function* () {
126
- const manager = yield* DocumentManager.DocumentManagerTag;
141
+ Effect.scoped(
142
+ Effect.gen(function* () {
143
+ const manager = yield* DocumentManagerTag;
127
144
 
128
- const tx1 = createValidTransaction("tx-1", "One");
129
- const tx2 = createValidTransaction("tx-2", "Two");
130
- const tx3 = createValidTransaction("tx-3", "Three");
145
+ const tx1 = createValidTransaction("tx-1", "One");
146
+ const tx2 = createValidTransaction("tx-2", "Two");
147
+ const tx3 = createValidTransaction("tx-3", "Three");
131
148
 
132
- const r1 = yield* manager.submit("doc-1", tx1);
133
- const r2 = yield* manager.submit("doc-1", tx2);
134
- const r3 = yield* manager.submit("doc-1", tx3);
149
+ const r1 = yield* manager.submit("doc-1", tx1);
150
+ const r2 = yield* manager.submit("doc-1", tx2);
151
+ const r3 = yield* manager.submit("doc-1", tx3);
135
152
 
136
- return { r1, r2, r3 };
137
- }).pipe(Effect.provide(makeTestLayer()))
153
+ return { r1, r2, r3 };
154
+ })
155
+ ).pipe(Effect.provide(makeTestLayer()))
138
156
  );
139
157
 
140
158
  expect(result.r1.success).toBe(true);
@@ -150,23 +168,24 @@ describe("DocumentManager", () => {
150
168
 
151
169
  it("should handle different documents independently", async () => {
152
170
  const result = await Effect.runPromise(
153
- Effect.gen(function* () {
154
- const manager = yield* DocumentManager.DocumentManagerTag;
171
+ Effect.scoped(
172
+ Effect.gen(function* () {
173
+ const manager = yield* DocumentManagerTag;
155
174
 
156
- const txDoc1 = createValidTransaction("tx-doc1", "Doc 1");
157
- const txDoc2 = createValidTransaction("tx-doc2", "Doc 2");
175
+ const txDoc1 = createValidTransaction("tx-doc1", "Doc 1");
176
+ const txDoc2 = createValidTransaction("tx-doc2", "Doc 2");
158
177
 
159
- const r1 = yield* manager.submit("doc-1", txDoc1);
160
- const r2 = yield* manager.submit("doc-2", txDoc2);
178
+ const r1 = yield* manager.submit("doc-1", txDoc1);
179
+ const r2 = yield* manager.submit("doc-2", txDoc2);
161
180
 
162
- return { r1, r2 };
163
- }).pipe(Effect.provide(makeTestLayer()))
181
+ return { r1, r2 };
182
+ })
183
+ ).pipe(Effect.provide(makeTestLayer()))
164
184
  );
165
185
 
166
186
  expect(result.r1.success).toBe(true);
167
187
  expect(result.r2.success).toBe(true);
168
188
 
169
- // Both should have version 1 since they are independent documents
170
189
  if (result.r1.success && result.r2.success) {
171
190
  expect(result.r1.version).toBe(1);
172
191
  expect(result.r2.version).toBe(1);
@@ -177,134 +196,124 @@ describe("DocumentManager", () => {
177
196
  describe("getSnapshot", () => {
178
197
  it("should return initial snapshot for new document", async () => {
179
198
  const result = await Effect.runPromise(
180
- Effect.gen(function* () {
181
- const manager = yield* DocumentManager.DocumentManagerTag;
182
- return yield* manager.getSnapshot("new-doc");
183
- }).pipe(Effect.provide(makeTestLayer()))
199
+ Effect.scoped(
200
+ Effect.gen(function* () {
201
+ const manager = yield* DocumentManagerTag;
202
+ return yield* manager.getSnapshot("new-doc");
203
+ })
204
+ ).pipe(Effect.provide(makeTestLayer()))
184
205
  );
185
206
 
186
207
  expect(result.type).toBe("snapshot");
187
208
  expect(result.version).toBe(0);
188
- // Initial state from schema defaults
189
209
  expect(result.state).toEqual({ title: "", count: 0 });
190
210
  });
191
211
 
192
212
  it("should return current state after transactions", async () => {
193
213
  const result = await Effect.runPromise(
194
- Effect.gen(function* () {
195
- const manager = yield* DocumentManager.DocumentManagerTag;
214
+ Effect.scoped(
215
+ Effect.gen(function* () {
216
+ const manager = yield* DocumentManagerTag;
196
217
 
197
- // Apply a transaction
198
- const tx = createValidTransaction("tx-1", "Updated Title");
199
- yield* manager.submit("doc-1", tx);
218
+ const tx = createValidTransaction("tx-1", "Updated Title");
219
+ yield* manager.submit("doc-1", tx);
200
220
 
201
- return yield* manager.getSnapshot("doc-1");
202
- }).pipe(Effect.provide(makeTestLayer()))
221
+ return yield* manager.getSnapshot("doc-1");
222
+ })
223
+ ).pipe(Effect.provide(makeTestLayer()))
203
224
  );
204
225
 
205
226
  expect(result.type).toBe("snapshot");
206
227
  expect(result.version).toBe(1);
207
- expect((result.state as any).title).toBe("Updated Title");
228
+ expect((result.state as { title: string }).title).toBe("Updated Title");
208
229
  });
209
230
 
210
- it("should return snapshot for specific document", async () => {
231
+ it("should use initial state for new documents", async () => {
211
232
  const result = await Effect.runPromise(
212
- Effect.gen(function* () {
213
- const manager = yield* DocumentManager.DocumentManagerTag;
214
-
215
- // Apply transactions to different documents
216
- const tx1 = createValidTransaction("tx-1", "Doc One");
217
- const tx2 = createValidTransaction("tx-2", "Doc Two");
218
-
219
- yield* manager.submit("doc-1", tx1);
220
- yield* manager.submit("doc-2", tx2);
221
-
222
- const snap1 = yield* manager.getSnapshot("doc-1");
223
- const snap2 = yield* manager.getSnapshot("doc-2");
224
-
225
- return { snap1, snap2 };
226
- }).pipe(Effect.provide(makeTestLayer()))
233
+ Effect.scoped(
234
+ Effect.gen(function* () {
235
+ const manager = yield* DocumentManagerTag;
236
+ return yield* manager.getSnapshot("new-doc");
237
+ })
238
+ ).pipe(
239
+ Effect.provide(
240
+ makeTestLayer({ initial: { title: "Initial Title", count: 42 } })
241
+ )
242
+ )
227
243
  );
228
244
 
229
- expect((result.snap1.state as any).title).toBe("Doc One");
230
- expect((result.snap2.state as any).title).toBe("Doc Two");
245
+ expect(result.type).toBe("snapshot");
246
+ expect(result.version).toBe(0);
247
+ expect(result.state).toEqual({ title: "Initial Title", count: 42 });
231
248
  });
232
249
  });
233
250
 
234
251
  describe("subscribe", () => {
235
252
  it("should receive broadcasts for submitted transactions", async () => {
236
253
  const result = await Effect.runPromise(
237
- Effect.gen(function* () {
238
- const manager = yield* DocumentManager.DocumentManagerTag;
239
-
240
- // Subscribe to the document
241
- const broadcastStream = yield* manager.subscribe("doc-1");
254
+ Effect.scoped(
255
+ Effect.gen(function* () {
256
+ const manager = yield* DocumentManagerTag;
242
257
 
243
- // Submit a transaction
244
- const tx = createValidTransaction("tx-broadcast", "Broadcast Test");
258
+ const broadcastStream = yield* manager.subscribe("doc-1");
245
259
 
246
- // Start collecting broadcasts in parallel
247
- const collectFiber = yield* Effect.fork(
248
- broadcastStream.pipe(Stream.take(1), Stream.runCollect)
249
- );
260
+ const collectFiber = yield* Effect.fork(
261
+ broadcastStream.pipe(Stream.take(1), Stream.runCollect)
262
+ );
250
263
 
251
- // Small delay to ensure subscription is ready
252
- yield* Effect.sleep(50);
264
+ yield* Effect.sleep(50);
253
265
 
254
- // Submit the transaction
255
- yield* manager.submit("doc-1", tx);
266
+ const tx = createValidTransaction("tx-broadcast", "Broadcast Test");
267
+ yield* manager.submit("doc-1", tx);
256
268
 
257
- // Wait for the broadcast with Fiber.join
258
- const broadcasts = yield* Fiber.join(collectFiber).pipe(
259
- Effect.timeout(2000)
260
- );
269
+ const broadcasts = yield* Fiber.join(collectFiber).pipe(
270
+ Effect.timeout(2000)
271
+ );
261
272
 
262
- return broadcasts;
263
- }).pipe(Effect.scoped, Effect.provide(makeTestLayer()))
273
+ return broadcasts;
274
+ })
275
+ ).pipe(Effect.provide(makeTestLayer()))
264
276
  );
265
277
 
266
278
  expect(result).toBeDefined();
267
279
  if (result) {
268
280
  const broadcasts = Array.from(result);
269
281
  expect(broadcasts.length).toBe(1);
270
- expect(broadcasts[0].type).toBe("transaction");
282
+ expect(broadcasts[0]!.type).toBe("transaction");
271
283
  }
272
284
  });
273
285
 
274
286
  it("should broadcast to multiple subscribers", async () => {
275
287
  const result = await Effect.runPromise(
276
- Effect.gen(function* () {
277
- const manager = yield* DocumentManager.DocumentManagerTag;
278
-
279
- // Subscribe twice to the same document
280
- const stream1 = yield* manager.subscribe("doc-1");
281
- const stream2 = yield* manager.subscribe("doc-1");
282
-
283
- // Start collecting broadcasts in parallel
284
- const collectFiber1 = yield* Effect.fork(
285
- stream1.pipe(Stream.take(1), Stream.runCollect)
286
- );
287
- const collectFiber2 = yield* Effect.fork(
288
- stream2.pipe(Stream.take(1), Stream.runCollect)
289
- );
290
-
291
- // Small delay to ensure subscriptions are ready
292
- yield* Effect.sleep(50);
293
-
294
- // Submit a transaction
295
- const tx = createValidTransaction("tx-multi", "Multi Broadcast");
296
- yield* manager.submit("doc-1", tx);
297
-
298
- // Wait for both broadcasts with Fiber.join
299
- const broadcasts1 = yield* Fiber.join(collectFiber1).pipe(
300
- Effect.timeout(2000)
301
- );
302
- const broadcasts2 = yield* Fiber.join(collectFiber2).pipe(
303
- Effect.timeout(2000)
304
- );
305
-
306
- return { broadcasts1, broadcasts2 };
307
- }).pipe(Effect.scoped, Effect.provide(makeTestLayer()))
288
+ Effect.scoped(
289
+ Effect.gen(function* () {
290
+ const manager = yield* DocumentManagerTag;
291
+
292
+ const stream1 = yield* manager.subscribe("doc-1");
293
+ const stream2 = yield* manager.subscribe("doc-1");
294
+
295
+ const collectFiber1 = yield* Effect.fork(
296
+ stream1.pipe(Stream.take(1), Stream.runCollect)
297
+ );
298
+ const collectFiber2 = yield* Effect.fork(
299
+ stream2.pipe(Stream.take(1), Stream.runCollect)
300
+ );
301
+
302
+ yield* Effect.sleep(50);
303
+
304
+ const tx = createValidTransaction("tx-multi", "Multi Broadcast");
305
+ yield* manager.submit("doc-1", tx);
306
+
307
+ const broadcasts1 = yield* Fiber.join(collectFiber1).pipe(
308
+ Effect.timeout(2000)
309
+ );
310
+ const broadcasts2 = yield* Fiber.join(collectFiber2).pipe(
311
+ Effect.timeout(2000)
312
+ );
313
+
314
+ return { broadcasts1, broadcasts2 };
315
+ })
316
+ ).pipe(Effect.provide(makeTestLayer()))
308
317
  );
309
318
 
310
319
  expect(result.broadcasts1).toBeDefined();
@@ -316,149 +325,11 @@ describe("DocumentManager", () => {
316
325
  });
317
326
  });
318
327
 
319
- describe("DocumentManagerTag", () => {
320
- it("should have the correct tag identifier", () => {
321
- expect(DocumentManager.DocumentManagerTag.key).toBe(
322
- "@voidhash/mimic-server-effect/DocumentManager"
323
- );
324
- });
325
- });
326
-
327
- describe("layer", () => {
328
- it("should require MimicServerConfigTag and MimicDataStorageTag", async () => {
329
- // This test verifies the layer composition works correctly
330
- const result = await Effect.runPromise(
331
- Effect.gen(function* () {
332
- const manager = yield* DocumentManager.DocumentManagerTag;
333
- return typeof manager.submit === "function";
334
- }).pipe(Effect.provide(makeTestLayer()))
335
- );
336
-
337
- expect(result).toBe(true);
338
- });
339
- });
340
-
341
- describe("initial state", () => {
342
- const makeTestLayerWithInitial = (initial: { title?: string; count?: number }) => {
343
- const configLayer = MimicConfig.layer({
344
- schema: TestSchema,
345
- maxTransactionHistory: 100,
346
- initial,
347
- });
348
-
349
- return DocumentManager.layer.pipe(
350
- Layer.provide(configLayer),
351
- Layer.provide(InMemoryDataStorage.layer)
352
- );
353
- };
354
-
355
- const makeTestLayerWithInitialFn = (
356
- initialFn: (ctx: { documentId: string }) => Effect.Effect<{ title?: string; count?: number }>
357
- ) => {
358
- const configLayer = MimicConfig.layer({
359
- schema: TestSchema,
360
- maxTransactionHistory: 100,
361
- initial: initialFn,
362
- });
363
-
364
- return DocumentManager.layer.pipe(
365
- Layer.provide(configLayer),
366
- Layer.provide(InMemoryDataStorage.layer)
367
- );
368
- };
369
-
370
- it("should use initial state for new documents", async () => {
371
- const result = await Effect.runPromise(
372
- Effect.gen(function* () {
373
- const manager = yield* DocumentManager.DocumentManagerTag;
374
- return yield* manager.getSnapshot("new-doc");
375
- }).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Initial Title", count: 42 })))
376
- );
377
-
378
- expect(result.type).toBe("snapshot");
379
- expect(result.version).toBe(0);
380
- expect(result.state).toEqual({ title: "Initial Title", count: 42 });
381
- });
382
-
383
- it("should apply defaults for omitted fields in initial state", async () => {
384
- const result = await Effect.runPromise(
385
- Effect.gen(function* () {
386
- const manager = yield* DocumentManager.DocumentManagerTag;
387
- return yield* manager.getSnapshot("new-doc");
388
- }).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Only Title" })))
389
- );
390
-
391
- expect(result.type).toBe("snapshot");
392
- // count should be 0 (default)
393
- expect(result.state).toEqual({ title: "Only Title", count: 0 });
394
- });
395
-
396
- it("should prefer stored state over initial state", async () => {
397
- const result = await Effect.runPromise(
398
- Effect.gen(function* () {
399
- const manager = yield* DocumentManager.DocumentManagerTag;
400
-
401
- // Apply a transaction to modify the document
402
- const tx = createValidTransaction("tx-1", "Modified Title");
403
- yield* manager.submit("doc-1", tx);
404
-
405
- // Get snapshot - should show modified state, not initial
406
- return yield* manager.getSnapshot("doc-1");
407
- }).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Initial Title", count: 42 })))
408
- );
409
-
410
- expect(result.type).toBe("snapshot");
411
- expect((result.state as any).title).toBe("Modified Title");
412
- // count should still be 42 since we only modified title
413
- expect((result.state as any).count).toBe(42);
414
- });
415
-
416
- it("should use initial state function with documentId for new documents", async () => {
417
- const result = await Effect.runPromise(
418
- Effect.gen(function* () {
419
- const manager = yield* DocumentManager.DocumentManagerTag;
420
- return yield* manager.getSnapshot("my-special-doc");
421
- }).pipe(Effect.provide(makeTestLayerWithInitialFn(
422
- ({ documentId }) => Effect.succeed({ title: `Doc: ${documentId}`, count: documentId.length })
423
- )))
328
+ describe("Tag", () => {
329
+ it("should have correct identifier", () => {
330
+ expect(DocumentManagerTag.key).toBe(
331
+ "@voidhash/mimic-effect/DocumentManager"
424
332
  );
425
-
426
- expect(result.type).toBe("snapshot");
427
- expect(result.version).toBe(0);
428
- expect(result.state).toEqual({ title: "Doc: my-special-doc", count: 14 });
429
- });
430
-
431
- it("should call initial function with different documentIds", async () => {
432
- const layer = makeTestLayerWithInitialFn(
433
- ({ documentId }) => Effect.succeed({ title: documentId, count: documentId.length })
434
- );
435
-
436
- const result = await Effect.runPromise(
437
- Effect.gen(function* () {
438
- const manager = yield* DocumentManager.DocumentManagerTag;
439
- const snap1 = yield* manager.getSnapshot("short");
440
- const snap2 = yield* manager.getSnapshot("longer-document-id");
441
- return { snap1, snap2 };
442
- }).pipe(Effect.provide(layer))
443
- );
444
-
445
- expect(result.snap1.state).toEqual({ title: "short", count: 5 });
446
- expect(result.snap2.state).toEqual({ title: "longer-document-id", count: 18 });
447
- });
448
-
449
- it("should apply defaults to initial function result", async () => {
450
- const result = await Effect.runPromise(
451
- Effect.gen(function* () {
452
- const manager = yield* DocumentManager.DocumentManagerTag;
453
- return yield* manager.getSnapshot("test-doc");
454
- }).pipe(Effect.provide(makeTestLayerWithInitialFn(
455
- ({ documentId }) => Effect.succeed({ title: documentId }) // count omitted
456
- )))
457
- );
458
-
459
- expect(result.type).toBe("snapshot");
460
- // count should be 0 (default)
461
- expect(result.state).toEqual({ title: "test-doc", count: 0 });
462
333
  });
463
334
  });
464
335
  });
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { Effect } from "effect";
3
+ import { HotStorage, HotStorageTag } from "../src/HotStorage";
4
+ import { HotStorageTestSuite } from "../src/testing";
5
+
6
+ describe("HotStorage", () => {
7
+ describe("InMemory", () => {
8
+ // Use the test suite utilities for comprehensive testing
9
+ const layer = HotStorage.InMemory.make();
10
+
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)))
15
+ );
16
+ }
17
+ });
18
+
19
+ describe("Tag", () => {
20
+ it("should have correct identifier", () => {
21
+ expect(HotStorageTag.key).toBe("@voidhash/mimic-effect/HotStorage");
22
+ });
23
+ });
24
+ });