@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.
- package/.turbo/turbo-build.log +136 -90
- package/README.md +385 -0
- package/dist/ColdStorage.cjs +60 -0
- package/dist/ColdStorage.d.cts +53 -0
- package/dist/ColdStorage.d.cts.map +1 -0
- package/dist/ColdStorage.d.mts +53 -0
- package/dist/ColdStorage.d.mts.map +1 -0
- package/dist/ColdStorage.mjs +60 -0
- package/dist/ColdStorage.mjs.map +1 -0
- package/dist/DocumentManager.cjs +263 -82
- package/dist/DocumentManager.d.cts +44 -22
- package/dist/DocumentManager.d.cts.map +1 -1
- package/dist/DocumentManager.d.mts +44 -22
- package/dist/DocumentManager.d.mts.map +1 -1
- package/dist/DocumentManager.mjs +259 -67
- package/dist/DocumentManager.mjs.map +1 -1
- package/dist/Errors.cjs +54 -0
- package/dist/Errors.d.cts +96 -0
- package/dist/Errors.d.cts.map +1 -0
- package/dist/Errors.d.mts +96 -0
- package/dist/Errors.d.mts.map +1 -0
- package/dist/Errors.mjs +48 -0
- package/dist/Errors.mjs.map +1 -0
- package/dist/HotStorage.cjs +100 -0
- package/dist/HotStorage.d.cts +70 -0
- package/dist/HotStorage.d.cts.map +1 -0
- package/dist/HotStorage.d.mts +70 -0
- package/dist/HotStorage.d.mts.map +1 -0
- package/dist/HotStorage.mjs +100 -0
- package/dist/HotStorage.mjs.map +1 -0
- package/dist/Metrics.cjs +143 -0
- package/dist/Metrics.d.cts +31 -0
- package/dist/Metrics.d.cts.map +1 -0
- package/dist/Metrics.d.mts +31 -0
- package/dist/Metrics.d.mts.map +1 -0
- package/dist/Metrics.mjs +126 -0
- package/dist/Metrics.mjs.map +1 -0
- package/dist/MimicAuthService.cjs +61 -45
- package/dist/MimicAuthService.d.cts +61 -48
- package/dist/MimicAuthService.d.cts.map +1 -1
- package/dist/MimicAuthService.d.mts +61 -48
- package/dist/MimicAuthService.d.mts.map +1 -1
- package/dist/MimicAuthService.mjs +60 -36
- package/dist/MimicAuthService.mjs.map +1 -1
- package/dist/MimicClusterServerEngine.cjs +521 -0
- package/dist/MimicClusterServerEngine.d.cts +17 -0
- package/dist/MimicClusterServerEngine.d.cts.map +1 -0
- package/dist/MimicClusterServerEngine.d.mts +17 -0
- package/dist/MimicClusterServerEngine.d.mts.map +1 -0
- package/dist/MimicClusterServerEngine.mjs +523 -0
- package/dist/MimicClusterServerEngine.mjs.map +1 -0
- package/dist/MimicServer.cjs +205 -96
- package/dist/MimicServer.d.cts +9 -110
- package/dist/MimicServer.d.cts.map +1 -1
- package/dist/MimicServer.d.mts +9 -110
- package/dist/MimicServer.d.mts.map +1 -1
- package/dist/MimicServer.mjs +206 -90
- package/dist/MimicServer.mjs.map +1 -1
- package/dist/MimicServerEngine.cjs +97 -0
- package/dist/MimicServerEngine.d.cts +78 -0
- package/dist/MimicServerEngine.d.cts.map +1 -0
- package/dist/MimicServerEngine.d.mts +78 -0
- package/dist/MimicServerEngine.d.mts.map +1 -0
- package/dist/MimicServerEngine.mjs +97 -0
- package/dist/MimicServerEngine.mjs.map +1 -0
- package/dist/PresenceManager.cjs +75 -91
- package/dist/PresenceManager.d.cts +17 -66
- package/dist/PresenceManager.d.cts.map +1 -1
- package/dist/PresenceManager.d.mts +17 -66
- package/dist/PresenceManager.d.mts.map +1 -1
- package/dist/PresenceManager.mjs +74 -78
- package/dist/PresenceManager.mjs.map +1 -1
- package/dist/Protocol.cjs +146 -0
- package/dist/Protocol.d.cts +203 -0
- package/dist/Protocol.d.cts.map +1 -0
- package/dist/Protocol.d.mts +203 -0
- package/dist/Protocol.d.mts.map +1 -0
- package/dist/Protocol.mjs +132 -0
- package/dist/Protocol.mjs.map +1 -0
- package/dist/Types.d.cts +172 -0
- package/dist/Types.d.cts.map +1 -0
- package/dist/Types.d.mts +172 -0
- package/dist/Types.d.mts.map +1 -0
- package/dist/_virtual/rolldown_runtime.cjs +1 -25
- package/dist/_virtual/rolldown_runtime.mjs +4 -1
- package/dist/index.cjs +37 -75
- package/dist/index.d.cts +13 -12
- package/dist/index.d.mts +13 -12
- package/dist/index.mjs +12 -12
- package/dist/testing/ColdStorageTestSuite.cjs +508 -0
- package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
- package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
- package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
- package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
- package/dist/testing/ColdStorageTestSuite.mjs +508 -0
- package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
- package/dist/testing/FailingStorage.cjs +135 -0
- package/dist/testing/FailingStorage.d.cts +43 -0
- package/dist/testing/FailingStorage.d.cts.map +1 -0
- package/dist/testing/FailingStorage.d.mts +43 -0
- package/dist/testing/FailingStorage.d.mts.map +1 -0
- package/dist/testing/FailingStorage.mjs +136 -0
- package/dist/testing/FailingStorage.mjs.map +1 -0
- package/dist/testing/HotStorageTestSuite.cjs +585 -0
- package/dist/testing/HotStorageTestSuite.d.cts +40 -0
- package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
- package/dist/testing/HotStorageTestSuite.d.mts +40 -0
- package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
- package/dist/testing/HotStorageTestSuite.mjs +585 -0
- package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
- package/dist/testing/StorageIntegrationTestSuite.cjs +349 -0
- package/dist/testing/StorageIntegrationTestSuite.d.cts +35 -0
- package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
- package/dist/testing/StorageIntegrationTestSuite.d.mts +35 -0
- package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
- package/dist/testing/StorageIntegrationTestSuite.mjs +349 -0
- package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
- package/dist/testing/assertions.cjs +114 -0
- package/dist/testing/assertions.mjs +109 -0
- package/dist/testing/assertions.mjs.map +1 -0
- package/dist/testing/index.cjs +14 -0
- package/dist/testing/index.d.cts +6 -0
- package/dist/testing/index.d.mts +6 -0
- package/dist/testing/index.mjs +7 -0
- package/dist/testing/types.cjs +15 -0
- package/dist/testing/types.d.cts +90 -0
- package/dist/testing/types.d.cts.map +1 -0
- package/dist/testing/types.d.mts +90 -0
- package/dist/testing/types.d.mts.map +1 -0
- package/dist/testing/types.mjs +16 -0
- package/dist/testing/types.mjs.map +1 -0
- package/package.json +18 -3
- package/src/ColdStorage.ts +136 -0
- package/src/DocumentManager.ts +550 -190
- package/src/Errors.ts +114 -0
- package/src/HotStorage.ts +239 -0
- package/src/Metrics.ts +187 -0
- package/src/MimicAuthService.ts +126 -64
- package/src/MimicClusterServerEngine.ts +946 -0
- package/src/MimicServer.ts +448 -195
- package/src/MimicServerEngine.ts +276 -0
- package/src/PresenceManager.ts +169 -240
- package/src/Protocol.ts +350 -0
- package/src/Types.ts +231 -0
- package/src/index.ts +57 -23
- package/src/testing/ColdStorageTestSuite.ts +589 -0
- package/src/testing/FailingStorage.ts +286 -0
- package/src/testing/HotStorageTestSuite.ts +762 -0
- package/src/testing/StorageIntegrationTestSuite.ts +504 -0
- package/src/testing/assertions.ts +181 -0
- package/src/testing/index.ts +83 -0
- package/src/testing/types.ts +100 -0
- package/tests/ColdStorage.test.ts +24 -0
- package/tests/DocumentManager.test.ts +158 -287
- package/tests/HotStorage.test.ts +24 -0
- package/tests/MimicAuthService.test.ts +102 -134
- package/tests/MimicClusterServerEngine.test.ts +587 -0
- package/tests/MimicServer.test.ts +90 -226
- package/tests/MimicServerEngine.test.ts +521 -0
- package/tests/PresenceManager.test.ts +22 -63
- package/tests/Protocol.test.ts +190 -0
- package/tests/StorageIntegration.test.ts +259 -0
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +1 -1
- package/dist/DocumentProtocol.cjs +0 -94
- package/dist/DocumentProtocol.d.cts +0 -113
- package/dist/DocumentProtocol.d.cts.map +0 -1
- package/dist/DocumentProtocol.d.mts +0 -113
- package/dist/DocumentProtocol.d.mts.map +0 -1
- package/dist/DocumentProtocol.mjs +0 -89
- package/dist/DocumentProtocol.mjs.map +0 -1
- package/dist/MimicConfig.cjs +0 -60
- package/dist/MimicConfig.d.cts +0 -141
- package/dist/MimicConfig.d.cts.map +0 -1
- package/dist/MimicConfig.d.mts +0 -141
- package/dist/MimicConfig.d.mts.map +0 -1
- package/dist/MimicConfig.mjs +0 -50
- package/dist/MimicConfig.mjs.map +0 -1
- package/dist/MimicDataStorage.cjs +0 -83
- package/dist/MimicDataStorage.d.cts +0 -113
- package/dist/MimicDataStorage.d.cts.map +0 -1
- package/dist/MimicDataStorage.d.mts +0 -113
- package/dist/MimicDataStorage.d.mts.map +0 -1
- package/dist/MimicDataStorage.mjs +0 -74
- package/dist/MimicDataStorage.mjs.map +0 -1
- package/dist/WebSocketHandler.cjs +0 -365
- package/dist/WebSocketHandler.d.cts +0 -34
- package/dist/WebSocketHandler.d.cts.map +0 -1
- package/dist/WebSocketHandler.d.mts +0 -34
- package/dist/WebSocketHandler.d.mts.map +0 -1
- package/dist/WebSocketHandler.mjs +0 -355
- package/dist/WebSocketHandler.mjs.map +0 -1
- package/dist/auth/NoAuth.cjs +0 -43
- package/dist/auth/NoAuth.d.cts +0 -22
- package/dist/auth/NoAuth.d.cts.map +0 -1
- package/dist/auth/NoAuth.d.mts +0 -22
- package/dist/auth/NoAuth.d.mts.map +0 -1
- package/dist/auth/NoAuth.mjs +0 -36
- package/dist/auth/NoAuth.mjs.map +0 -1
- package/dist/errors.cjs +0 -74
- package/dist/errors.d.cts +0 -89
- package/dist/errors.d.cts.map +0 -1
- package/dist/errors.d.mts +0 -89
- package/dist/errors.d.mts.map +0 -1
- package/dist/errors.mjs +0 -67
- package/dist/errors.mjs.map +0 -1
- package/dist/storage/InMemoryDataStorage.cjs +0 -57
- package/dist/storage/InMemoryDataStorage.d.cts +0 -19
- package/dist/storage/InMemoryDataStorage.d.cts.map +0 -1
- package/dist/storage/InMemoryDataStorage.d.mts +0 -19
- package/dist/storage/InMemoryDataStorage.d.mts.map +0 -1
- package/dist/storage/InMemoryDataStorage.mjs +0 -48
- package/dist/storage/InMemoryDataStorage.mjs.map +0 -1
- package/src/DocumentProtocol.ts +0 -112
- package/src/MimicConfig.ts +0 -211
- package/src/MimicDataStorage.ts +0 -157
- package/src/WebSocketHandler.ts +0 -735
- package/src/auth/NoAuth.ts +0 -46
- package/src/errors.ts +0 -113
- package/src/storage/InMemoryDataStorage.ts +0 -66
- package/tests/DocumentProtocol.test.ts +0 -113
- package/tests/InMemoryDataStorage.test.ts +0 -190
- package/tests/MimicConfig.test.ts +0 -290
- package/tests/MimicDataStorage.test.ts +0 -190
- package/tests/NoAuth.test.ts +0 -94
- package/tests/WebSocketHandler.test.ts +0 -321
- package/tests/errors.test.ts +0 -77
package/README.md
CHANGED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# @voidhash/mimic-effect
|
|
2
|
+
|
|
3
|
+
Effect-based server engine for real-time document collaboration using the Mimic CRDT system.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @voidhash/mimic-effect @voidhash/mimic effect @effect/platform
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Effect, Layer } from "effect"
|
|
15
|
+
import { Primitive } from "@voidhash/mimic"
|
|
16
|
+
import {
|
|
17
|
+
MimicServerEngine,
|
|
18
|
+
ColdStorage,
|
|
19
|
+
HotStorage,
|
|
20
|
+
MimicAuthService,
|
|
21
|
+
} from "@voidhash/mimic-effect"
|
|
22
|
+
|
|
23
|
+
// Define your document schema
|
|
24
|
+
const TodoSchema = Primitive.Struct({
|
|
25
|
+
title: Primitive.String().default(""),
|
|
26
|
+
items: Primitive.Array(
|
|
27
|
+
Primitive.Struct({
|
|
28
|
+
text: Primitive.String(),
|
|
29
|
+
completed: Primitive.Boolean().default(false),
|
|
30
|
+
})
|
|
31
|
+
).default([]),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Create the engine
|
|
35
|
+
const Engine = MimicServerEngine.make({
|
|
36
|
+
schema: TodoSchema,
|
|
37
|
+
initial: { title: "My Todo List" },
|
|
38
|
+
basePath: "/mimic",
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Wire up with storage and auth
|
|
42
|
+
const MimicLive = Engine.pipe(
|
|
43
|
+
Layer.provide(ColdStorage.InMemory.make()),
|
|
44
|
+
Layer.provide(HotStorage.InMemory.make()),
|
|
45
|
+
Layer.provide(MimicAuthService.NoAuth.make())
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
// Use in your HTTP server
|
|
49
|
+
const program = Effect.gen(function* () {
|
|
50
|
+
const engine = yield* MimicServerEngine.Tag
|
|
51
|
+
// Mount engine.routes in your HTTP server
|
|
52
|
+
// e.g., with @effect/platform-bun or @effect/platform-node
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
program.pipe(Effect.provide(MimicLive), Effect.runPromise)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Architecture
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
MimicServerEngine
|
|
62
|
+
|
|
|
63
|
+
+-------------------+-------------------+
|
|
64
|
+
| | |
|
|
65
|
+
ColdStorage HotStorage MimicAuthService
|
|
66
|
+
(Snapshots) (WAL) (Auth/Permissions)
|
|
67
|
+
| |
|
|
68
|
+
+-------------------+
|
|
69
|
+
|
|
|
70
|
+
Document Storage
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Components
|
|
74
|
+
|
|
75
|
+
- **MimicServerEngine**: Main entry point that creates HTTP routes for WebSocket connections
|
|
76
|
+
- **ColdStorage**: Snapshot storage interface (for persisting document state)
|
|
77
|
+
- **HotStorage**: Write-ahead log storage (for transaction history)
|
|
78
|
+
- **MimicAuthService**: Authentication and authorization service
|
|
79
|
+
- **DocumentManager**: Internal service managing document lifecycle
|
|
80
|
+
- **PresenceManager**: Internal service managing ephemeral presence state
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const Engine = MimicServerEngine.make({
|
|
86
|
+
// Required: Document schema
|
|
87
|
+
schema: MySchema,
|
|
88
|
+
|
|
89
|
+
// Optional: Initial state for new documents
|
|
90
|
+
initial: { title: "Untitled" },
|
|
91
|
+
// Or a function for dynamic initial state:
|
|
92
|
+
// initial: ({ documentId }) => Effect.succeed({ title: documentId }),
|
|
93
|
+
|
|
94
|
+
// Optional: Presence schema for cursor tracking, etc.
|
|
95
|
+
presence: CursorPresence,
|
|
96
|
+
|
|
97
|
+
// Optional: WebSocket route base path (default: "/mimic")
|
|
98
|
+
basePath: "/mimic",
|
|
99
|
+
|
|
100
|
+
// Optional: Document idle timeout before GC (default: 5 minutes)
|
|
101
|
+
maxIdleTime: "10 minutes",
|
|
102
|
+
|
|
103
|
+
// Optional: Max transaction history for deduplication (default: 1000)
|
|
104
|
+
maxTransactionHistory: 500,
|
|
105
|
+
|
|
106
|
+
// Optional: Snapshot configuration
|
|
107
|
+
snapshot: {
|
|
108
|
+
interval: "5 minutes", // Time-based snapshot interval
|
|
109
|
+
transactionThreshold: 100, // Transaction count threshold
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// Optional: Heartbeat configuration
|
|
113
|
+
heartbeatInterval: "30 seconds", // How often to ping
|
|
114
|
+
heartbeatTimeout: "10 seconds", // How long to wait for pong
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Storage Implementations
|
|
119
|
+
|
|
120
|
+
### In-Memory (Development/Testing)
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { ColdStorage, HotStorage } from "@voidhash/mimic-effect"
|
|
124
|
+
|
|
125
|
+
const StorageLive = Layer.mergeAll(
|
|
126
|
+
ColdStorage.InMemory.make(),
|
|
127
|
+
HotStorage.InMemory.make()
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Custom Storage
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { ColdStorage, HotStorage } from "@voidhash/mimic-effect"
|
|
135
|
+
|
|
136
|
+
// Implement ColdStorage interface
|
|
137
|
+
const PostgresColdStorage = Layer.effect(
|
|
138
|
+
ColdStorage.Tag,
|
|
139
|
+
Effect.gen(function* () {
|
|
140
|
+
const db = yield* DatabaseService
|
|
141
|
+
return {
|
|
142
|
+
load: (documentId) => /* load from DB */,
|
|
143
|
+
save: (documentId, doc) => /* save to DB */,
|
|
144
|
+
delete: (documentId) => /* delete from DB */,
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
// Implement HotStorage interface
|
|
150
|
+
const RedisHotStorage = Layer.effect(
|
|
151
|
+
HotStorage.Tag,
|
|
152
|
+
Effect.gen(function* () {
|
|
153
|
+
const redis = yield* RedisService
|
|
154
|
+
return {
|
|
155
|
+
getEntries: (documentId, sinceVersion) => /* get from Redis */,
|
|
156
|
+
append: (documentId, entry) => /* append to Redis */,
|
|
157
|
+
truncate: (documentId, upToVersion) => /* truncate in Redis */,
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Authentication
|
|
164
|
+
|
|
165
|
+
### No Authentication (Development)
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { MimicAuthService } from "@voidhash/mimic-effect"
|
|
169
|
+
|
|
170
|
+
const AuthLive = MimicAuthService.NoAuth.make()
|
|
171
|
+
// All users get "write" permission as "anonymous"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Static Permissions
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { MimicAuthService } from "@voidhash/mimic-effect"
|
|
178
|
+
|
|
179
|
+
const AuthLive = MimicAuthService.Static.make({
|
|
180
|
+
permissions: {
|
|
181
|
+
"admin-token": "write",
|
|
182
|
+
"viewer-token": "read",
|
|
183
|
+
},
|
|
184
|
+
defaultPermission: "read", // Optional fallback
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Custom Authentication
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { MimicAuthService, AuthenticationError } from "@voidhash/mimic-effect"
|
|
192
|
+
|
|
193
|
+
const CustomAuth = MimicAuthService.make(
|
|
194
|
+
Effect.gen(function* () {
|
|
195
|
+
const jwt = yield* JwtService
|
|
196
|
+
const db = yield* DatabaseService
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
authenticate: (token, documentId) =>
|
|
200
|
+
Effect.gen(function* () {
|
|
201
|
+
// Verify JWT
|
|
202
|
+
const payload = yield* jwt.verify(token).pipe(
|
|
203
|
+
Effect.mapError(() => new AuthenticationError({ reason: "Invalid token" }))
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
// Check document permissions
|
|
207
|
+
const permission = yield* db.getDocumentPermission(
|
|
208
|
+
payload.userId,
|
|
209
|
+
documentId
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
userId: payload.userId,
|
|
214
|
+
permission, // "read" or "write"
|
|
215
|
+
}
|
|
216
|
+
}),
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Permissions
|
|
223
|
+
|
|
224
|
+
- `"read"`: Can subscribe, receive transactions, get snapshots
|
|
225
|
+
- `"write"`: All of the above, plus can submit transactions and set presence
|
|
226
|
+
|
|
227
|
+
## Presence
|
|
228
|
+
|
|
229
|
+
Enable presence tracking for features like cursor position, user status, etc.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { Schema } from "effect"
|
|
233
|
+
import { Presence } from "@voidhash/mimic"
|
|
234
|
+
|
|
235
|
+
// Define presence schema
|
|
236
|
+
const CursorPresence = Presence.make({
|
|
237
|
+
schema: Schema.Struct({
|
|
238
|
+
x: Schema.Number,
|
|
239
|
+
y: Schema.Number,
|
|
240
|
+
color: Schema.String,
|
|
241
|
+
name: Schema.optional(Schema.String),
|
|
242
|
+
}),
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Enable in engine config
|
|
246
|
+
const Engine = MimicServerEngine.make({
|
|
247
|
+
schema: DocSchema,
|
|
248
|
+
presence: CursorPresence,
|
|
249
|
+
})
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## WebSocket Protocol
|
|
253
|
+
|
|
254
|
+
### Client -> Server Messages
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// Authentication
|
|
258
|
+
{ type: "auth", token: "user-token" }
|
|
259
|
+
|
|
260
|
+
// Heartbeat
|
|
261
|
+
{ type: "ping" }
|
|
262
|
+
|
|
263
|
+
// Submit transaction
|
|
264
|
+
{ type: "submit", transaction: EncodedTransaction }
|
|
265
|
+
|
|
266
|
+
// Request snapshot
|
|
267
|
+
{ type: "request_snapshot" }
|
|
268
|
+
|
|
269
|
+
// Set presence
|
|
270
|
+
{ type: "presence_set", data: { x: 100, y: 200 } }
|
|
271
|
+
|
|
272
|
+
// Clear presence
|
|
273
|
+
{ type: "presence_clear" }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Server -> Client Messages
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// Authentication result
|
|
280
|
+
{ type: "auth_result", success: true, userId: "...", permission: "write" }
|
|
281
|
+
{ type: "auth_result", success: false, error: "Invalid token" }
|
|
282
|
+
|
|
283
|
+
// Heartbeat response
|
|
284
|
+
{ type: "pong" }
|
|
285
|
+
|
|
286
|
+
// Document snapshot
|
|
287
|
+
{ type: "snapshot", state: {...}, version: 42 }
|
|
288
|
+
|
|
289
|
+
// Transaction broadcast
|
|
290
|
+
{ type: "transaction", transaction: EncodedTransaction, version: 43 }
|
|
291
|
+
|
|
292
|
+
// Transaction error
|
|
293
|
+
{ type: "error", transactionId: "tx-123", reason: "..." }
|
|
294
|
+
|
|
295
|
+
// Presence snapshot (after auth)
|
|
296
|
+
{ type: "presence_snapshot", selfId: "conn-123", presences: {...} }
|
|
297
|
+
|
|
298
|
+
// Presence update
|
|
299
|
+
{ type: "presence_update", entries: { "conn-456": { data: {...}, userId: "..." } } }
|
|
300
|
+
|
|
301
|
+
// Presence removal
|
|
302
|
+
{ type: "presence_remove", connectionId: "conn-456" }
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Metrics
|
|
306
|
+
|
|
307
|
+
Built-in observability metrics using Effect's Metric API:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { MimicMetrics } from "@voidhash/mimic-effect"
|
|
311
|
+
|
|
312
|
+
// Connection metrics
|
|
313
|
+
MimicMetrics.connectionsActive // Gauge: Current connections
|
|
314
|
+
MimicMetrics.connectionsTotal // Counter: Total connections
|
|
315
|
+
MimicMetrics.connectionsDuration // Histogram: Connection duration (ms)
|
|
316
|
+
MimicMetrics.connectionsErrors // Counter: Connection errors
|
|
317
|
+
|
|
318
|
+
// Document metrics
|
|
319
|
+
MimicMetrics.documentsActive // Gauge: Documents in memory
|
|
320
|
+
MimicMetrics.documentsCreated // Counter: New documents
|
|
321
|
+
MimicMetrics.documentsRestored // Counter: Restored from storage
|
|
322
|
+
MimicMetrics.documentsEvicted // Counter: Evicted (idle GC)
|
|
323
|
+
|
|
324
|
+
// Transaction metrics
|
|
325
|
+
MimicMetrics.transactionsProcessed // Counter: Successful transactions
|
|
326
|
+
MimicMetrics.transactionsRejected // Counter: Rejected transactions
|
|
327
|
+
MimicMetrics.transactionsLatency // Histogram: Processing time (ms)
|
|
328
|
+
|
|
329
|
+
// Storage metrics
|
|
330
|
+
MimicMetrics.storageSnapshots // Counter: Snapshots saved
|
|
331
|
+
MimicMetrics.storageSnapshotLatency // Histogram: Snapshot save time (ms)
|
|
332
|
+
MimicMetrics.storageWalAppends // Counter: WAL entries written
|
|
333
|
+
|
|
334
|
+
// Presence metrics
|
|
335
|
+
MimicMetrics.presenceUpdates // Counter: Presence updates
|
|
336
|
+
MimicMetrics.presenceActive // Gauge: Active presences
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Integration Examples
|
|
340
|
+
|
|
341
|
+
### With @effect/platform-bun
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { BunHttpServer, BunRuntime } from "@effect/platform-bun"
|
|
345
|
+
import { HttpRouter, HttpServer } from "@effect/platform"
|
|
346
|
+
|
|
347
|
+
const server = HttpServer.serve(engine.routes).pipe(
|
|
348
|
+
Layer.provide(MimicLive),
|
|
349
|
+
Layer.provide(BunHttpServer.layer({ port: 3000 }))
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
BunRuntime.runMain(Layer.launch(server))
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### With @effect/platform-node
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
|
|
359
|
+
import { HttpRouter, HttpServer } from "@effect/platform"
|
|
360
|
+
|
|
361
|
+
const server = HttpServer.serve(engine.routes).pipe(
|
|
362
|
+
Layer.provide(MimicLive),
|
|
363
|
+
Layer.provide(NodeHttpServer.layer({ port: 3000 }))
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
NodeRuntime.runMain(Layer.launch(server))
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Error Types
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import {
|
|
373
|
+
ColdStorageError,
|
|
374
|
+
HotStorageError,
|
|
375
|
+
AuthenticationError,
|
|
376
|
+
AuthorizationError,
|
|
377
|
+
MissingDocumentIdError,
|
|
378
|
+
MessageParseError,
|
|
379
|
+
TransactionRejectedError,
|
|
380
|
+
} from "@voidhash/mimic-effect"
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## License
|
|
384
|
+
|
|
385
|
+
MIT
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
let effect = require("effect");
|
|
2
|
+
|
|
3
|
+
//#region src/ColdStorage.ts
|
|
4
|
+
/**
|
|
5
|
+
* @voidhash/mimic-effect - ColdStorage
|
|
6
|
+
*
|
|
7
|
+
* Interface and implementations for document snapshot storage.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Context tag for ColdStorage service
|
|
11
|
+
*/
|
|
12
|
+
var ColdStorageTag = class extends effect.Context.Tag("@voidhash/mimic-effect/ColdStorage")() {};
|
|
13
|
+
/**
|
|
14
|
+
* Create a ColdStorage layer from an Effect that produces a ColdStorage service.
|
|
15
|
+
*
|
|
16
|
+
* This allows you to access other Effect services when implementing custom storage.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const Cold = ColdStorage.make(
|
|
21
|
+
* Effect.gen(function*() {
|
|
22
|
+
* const redis = yield* RedisService
|
|
23
|
+
*
|
|
24
|
+
* return {
|
|
25
|
+
* load: (documentId) => redis.get(`doc:${documentId}`).pipe(
|
|
26
|
+
* Effect.map(data => data ? JSON.parse(data) : undefined)
|
|
27
|
+
* ),
|
|
28
|
+
* save: (documentId, document) =>
|
|
29
|
+
* redis.set(`doc:${documentId}`, JSON.stringify(document)),
|
|
30
|
+
* delete: (documentId) => redis.del(`doc:${documentId}`),
|
|
31
|
+
* }
|
|
32
|
+
* })
|
|
33
|
+
* )
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
const make = (effect$1) => effect.Layer.effect(ColdStorageTag, effect$1);
|
|
37
|
+
let InMemory;
|
|
38
|
+
(function(_InMemory) {
|
|
39
|
+
_InMemory.make = () => effect.Layer.effect(ColdStorageTag, effect.Effect.gen(function* () {
|
|
40
|
+
const store = yield* effect.Ref.make(effect.HashMap.empty());
|
|
41
|
+
return {
|
|
42
|
+
load: (documentId) => effect.Effect.gen(function* () {
|
|
43
|
+
const current = yield* effect.Ref.get(store);
|
|
44
|
+
const result = effect.HashMap.get(current, documentId);
|
|
45
|
+
return result._tag === "Some" ? result.value : void 0;
|
|
46
|
+
}),
|
|
47
|
+
save: (documentId, document) => effect.Ref.update(store, (map) => effect.HashMap.set(map, documentId, document)),
|
|
48
|
+
delete: (documentId) => effect.Ref.update(store, (map) => effect.HashMap.remove(map, documentId))
|
|
49
|
+
};
|
|
50
|
+
}));
|
|
51
|
+
})(InMemory || (InMemory = {}));
|
|
52
|
+
const ColdStorage = {
|
|
53
|
+
Tag: ColdStorageTag,
|
|
54
|
+
make,
|
|
55
|
+
InMemory
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
exports.ColdStorage = ColdStorage;
|
|
60
|
+
exports.ColdStorageTag = ColdStorageTag;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { StoredDocument } from "./Types.cjs";
|
|
2
|
+
import { ColdStorageError } from "./Errors.cjs";
|
|
3
|
+
import { Context, Effect, Layer } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/ColdStorage.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ColdStorage interface for storing document snapshots.
|
|
9
|
+
*
|
|
10
|
+
* This is the "cold" tier of the two-tier storage system.
|
|
11
|
+
* It stores complete document snapshots less frequently (on periodic intervals
|
|
12
|
+
* or after a threshold of transactions).
|
|
13
|
+
*/
|
|
14
|
+
interface ColdStorage {
|
|
15
|
+
/**
|
|
16
|
+
* Load a document snapshot.
|
|
17
|
+
* Returns undefined if the document doesn't exist.
|
|
18
|
+
*/
|
|
19
|
+
readonly load: (documentId: string) => Effect.Effect<StoredDocument | undefined, ColdStorageError>;
|
|
20
|
+
/**
|
|
21
|
+
* Save a document snapshot.
|
|
22
|
+
*/
|
|
23
|
+
readonly save: (documentId: string, document: StoredDocument) => Effect.Effect<void, ColdStorageError>;
|
|
24
|
+
/**
|
|
25
|
+
* Delete a document snapshot.
|
|
26
|
+
*/
|
|
27
|
+
readonly delete: (documentId: string) => Effect.Effect<void, ColdStorageError>;
|
|
28
|
+
}
|
|
29
|
+
declare const ColdStorageTag_base: Context.TagClass<ColdStorageTag, "@voidhash/mimic-effect/ColdStorage", ColdStorage>;
|
|
30
|
+
/**
|
|
31
|
+
* Context tag for ColdStorage service
|
|
32
|
+
*/
|
|
33
|
+
declare class ColdStorageTag extends ColdStorageTag_base {}
|
|
34
|
+
/**
|
|
35
|
+
* In-memory ColdStorage implementation.
|
|
36
|
+
*
|
|
37
|
+
* Useful for testing and development. Not suitable for production
|
|
38
|
+
* as data is lost when the process restarts.
|
|
39
|
+
*/
|
|
40
|
+
declare namespace InMemory {
|
|
41
|
+
/**
|
|
42
|
+
* Create an in-memory ColdStorage layer.
|
|
43
|
+
*/
|
|
44
|
+
const make: () => Layer.Layer<ColdStorageTag>;
|
|
45
|
+
}
|
|
46
|
+
declare const ColdStorage: {
|
|
47
|
+
Tag: typeof ColdStorageTag;
|
|
48
|
+
make: <E, R>(effect: Effect.Effect<ColdStorage, E, R>) => Layer.Layer<ColdStorageTag, E, R>;
|
|
49
|
+
InMemory: typeof InMemory;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { ColdStorage, ColdStorageTag };
|
|
53
|
+
//# sourceMappingURL=ColdStorage.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColdStorage.d.cts","names":[],"sources":["../src/ColdStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AA2CC,UAvBgB,WAAA,CAuBhB;;;;;EASY,SAAA,IAAA,EAAA,CAAA,UAAe,EAAA,MAAQ,EAAA,GAzB7B,MAAA,CAAO,MA4BX,CA5BkB,cA4BlB,GAAA,SAAA,EA5B8C,gBA4B9C,CAAA;EA4Cc;AAgCjB;;EA9CwB,SAAA,IAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAnDV,cAmDU,EAAA,GAlDjB,MAAA,CAAO,MAkDU,CAAA,IAAA,EAlDG,gBAkDH,CAAA;EAAa;;;EACtB,SAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,GA5CR,MAAA,CAAO,MA4CC,CAAA,IAAA,EA5CY,gBA4CZ,CAAA;;cA3Cd,mBA2CiC,kBAAA,eAAA,EAAA,oCAAA,aAAA,CAAA;;;;cAlCrB,cAAA,SAAuB,mBAAA;;;;;;;kBA+CnB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cA4BzB;;uBA9CH,MAAA,CAAO,OAAO,aAAa,GAAG,OACrC,KAAA,CAAM,MAAM,gBAAgB,GAAG"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { StoredDocument } from "./Types.mjs";
|
|
2
|
+
import { ColdStorageError } from "./Errors.mjs";
|
|
3
|
+
import { Context, Effect, Layer } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/ColdStorage.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ColdStorage interface for storing document snapshots.
|
|
9
|
+
*
|
|
10
|
+
* This is the "cold" tier of the two-tier storage system.
|
|
11
|
+
* It stores complete document snapshots less frequently (on periodic intervals
|
|
12
|
+
* or after a threshold of transactions).
|
|
13
|
+
*/
|
|
14
|
+
interface ColdStorage {
|
|
15
|
+
/**
|
|
16
|
+
* Load a document snapshot.
|
|
17
|
+
* Returns undefined if the document doesn't exist.
|
|
18
|
+
*/
|
|
19
|
+
readonly load: (documentId: string) => Effect.Effect<StoredDocument | undefined, ColdStorageError>;
|
|
20
|
+
/**
|
|
21
|
+
* Save a document snapshot.
|
|
22
|
+
*/
|
|
23
|
+
readonly save: (documentId: string, document: StoredDocument) => Effect.Effect<void, ColdStorageError>;
|
|
24
|
+
/**
|
|
25
|
+
* Delete a document snapshot.
|
|
26
|
+
*/
|
|
27
|
+
readonly delete: (documentId: string) => Effect.Effect<void, ColdStorageError>;
|
|
28
|
+
}
|
|
29
|
+
declare const ColdStorageTag_base: Context.TagClass<ColdStorageTag, "@voidhash/mimic-effect/ColdStorage", ColdStorage>;
|
|
30
|
+
/**
|
|
31
|
+
* Context tag for ColdStorage service
|
|
32
|
+
*/
|
|
33
|
+
declare class ColdStorageTag extends ColdStorageTag_base {}
|
|
34
|
+
/**
|
|
35
|
+
* In-memory ColdStorage implementation.
|
|
36
|
+
*
|
|
37
|
+
* Useful for testing and development. Not suitable for production
|
|
38
|
+
* as data is lost when the process restarts.
|
|
39
|
+
*/
|
|
40
|
+
declare namespace InMemory {
|
|
41
|
+
/**
|
|
42
|
+
* Create an in-memory ColdStorage layer.
|
|
43
|
+
*/
|
|
44
|
+
const make: () => Layer.Layer<ColdStorageTag>;
|
|
45
|
+
}
|
|
46
|
+
declare const ColdStorage: {
|
|
47
|
+
Tag: typeof ColdStorageTag;
|
|
48
|
+
make: <E, R>(effect: Effect.Effect<ColdStorage, E, R>) => Layer.Layer<ColdStorageTag, E, R>;
|
|
49
|
+
InMemory: typeof InMemory;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { ColdStorage, ColdStorageTag };
|
|
53
|
+
//# sourceMappingURL=ColdStorage.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColdStorage.d.mts","names":[],"sources":["../src/ColdStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AA2CC,UAvBgB,WAAA,CAuBhB;;;;;EASY,SAAA,IAAA,EAAA,CAAA,UAAe,EAAA,MAAQ,EAAA,GAzB7B,MAAA,CAAO,MA4BX,CA5BkB,cA4BlB,GAAA,SAAA,EA5B8C,gBA4B9C,CAAA;EA4Cc;AAgCjB;;EA9CwB,SAAA,IAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAnDV,cAmDU,EAAA,GAlDjB,MAAA,CAAO,MAkDU,CAAA,IAAA,EAlDG,gBAkDH,CAAA;EAAa;;;EACtB,SAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,GA5CR,MAAA,CAAO,MA4CC,CAAA,IAAA,EA5CY,gBA4CZ,CAAA;;cA3Cd,mBA2CiC,kBAAA,eAAA,EAAA,oCAAA,aAAA,CAAA;;;;cAlCrB,cAAA,SAAuB,mBAAA;;;;;;;kBA+CnB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cA4BzB;;uBA9CH,MAAA,CAAO,OAAO,aAAa,GAAG,OACrC,KAAA,CAAM,MAAM,gBAAgB,GAAG"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Context, Effect, HashMap, Layer, Ref } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/ColdStorage.ts
|
|
4
|
+
/**
|
|
5
|
+
* @voidhash/mimic-effect - ColdStorage
|
|
6
|
+
*
|
|
7
|
+
* Interface and implementations for document snapshot storage.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Context tag for ColdStorage service
|
|
11
|
+
*/
|
|
12
|
+
var ColdStorageTag = class extends Context.Tag("@voidhash/mimic-effect/ColdStorage")() {};
|
|
13
|
+
/**
|
|
14
|
+
* Create a ColdStorage layer from an Effect that produces a ColdStorage service.
|
|
15
|
+
*
|
|
16
|
+
* This allows you to access other Effect services when implementing custom storage.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const Cold = ColdStorage.make(
|
|
21
|
+
* Effect.gen(function*() {
|
|
22
|
+
* const redis = yield* RedisService
|
|
23
|
+
*
|
|
24
|
+
* return {
|
|
25
|
+
* load: (documentId) => redis.get(`doc:${documentId}`).pipe(
|
|
26
|
+
* Effect.map(data => data ? JSON.parse(data) : undefined)
|
|
27
|
+
* ),
|
|
28
|
+
* save: (documentId, document) =>
|
|
29
|
+
* redis.set(`doc:${documentId}`, JSON.stringify(document)),
|
|
30
|
+
* delete: (documentId) => redis.del(`doc:${documentId}`),
|
|
31
|
+
* }
|
|
32
|
+
* })
|
|
33
|
+
* )
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
const make = (effect) => Layer.effect(ColdStorageTag, effect);
|
|
37
|
+
let InMemory;
|
|
38
|
+
(function(_InMemory) {
|
|
39
|
+
_InMemory.make = () => Layer.effect(ColdStorageTag, Effect.gen(function* () {
|
|
40
|
+
const store = yield* Ref.make(HashMap.empty());
|
|
41
|
+
return {
|
|
42
|
+
load: (documentId) => Effect.gen(function* () {
|
|
43
|
+
const current = yield* Ref.get(store);
|
|
44
|
+
const result = HashMap.get(current, documentId);
|
|
45
|
+
return result._tag === "Some" ? result.value : void 0;
|
|
46
|
+
}),
|
|
47
|
+
save: (documentId, document) => Ref.update(store, (map) => HashMap.set(map, documentId, document)),
|
|
48
|
+
delete: (documentId) => Ref.update(store, (map) => HashMap.remove(map, documentId))
|
|
49
|
+
};
|
|
50
|
+
}));
|
|
51
|
+
})(InMemory || (InMemory = {}));
|
|
52
|
+
const ColdStorage = {
|
|
53
|
+
Tag: ColdStorageTag,
|
|
54
|
+
make,
|
|
55
|
+
InMemory
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
export { ColdStorage, ColdStorageTag };
|
|
60
|
+
//# sourceMappingURL=ColdStorage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColdStorage.mjs","names":[],"sources":["../src/ColdStorage.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - ColdStorage\n *\n * Interface and implementations for document snapshot storage.\n */\nimport { Context, Effect, HashMap, Layer, Ref } from \"effect\";\nimport type { StoredDocument } from \"./Types\";\nimport { ColdStorageError } from \"./Errors\";\n\n// =============================================================================\n// ColdStorage Interface\n// =============================================================================\n\n/**\n * ColdStorage interface for storing document snapshots.\n *\n * This is the \"cold\" tier of the two-tier storage system.\n * It stores complete document snapshots less frequently (on periodic intervals\n * or after a threshold of transactions).\n */\nexport interface ColdStorage {\n /**\n * Load a document snapshot.\n * Returns undefined if the document doesn't exist.\n */\n readonly load: (\n documentId: string\n ) => Effect.Effect<StoredDocument | undefined, ColdStorageError>;\n\n /**\n * Save a document snapshot.\n */\n readonly save: (\n documentId: string,\n document: StoredDocument\n ) => Effect.Effect<void, ColdStorageError>;\n\n /**\n * Delete a document snapshot.\n */\n readonly delete: (\n documentId: string\n ) => Effect.Effect<void, ColdStorageError>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for ColdStorage service\n */\nexport class ColdStorageTag extends Context.Tag(\"@voidhash/mimic-effect/ColdStorage\")<\n ColdStorageTag,\n ColdStorage\n>() {}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a ColdStorage layer from an Effect that produces a ColdStorage service.\n *\n * This allows you to access other Effect services when implementing custom storage.\n *\n * @example\n * ```typescript\n * const Cold = ColdStorage.make(\n * Effect.gen(function*() {\n * const redis = yield* RedisService\n *\n * return {\n * load: (documentId) => redis.get(`doc:${documentId}`).pipe(\n * Effect.map(data => data ? JSON.parse(data) : undefined)\n * ),\n * save: (documentId, document) =>\n * redis.set(`doc:${documentId}`, JSON.stringify(document)),\n * delete: (documentId) => redis.del(`doc:${documentId}`),\n * }\n * })\n * )\n * ```\n */\nexport const make = <E, R>(\n effect: Effect.Effect<ColdStorage, E, R>\n): Layer.Layer<ColdStorageTag, E, R> =>\n Layer.effect(ColdStorageTag, effect);\n\n// =============================================================================\n// InMemory Implementation\n// =============================================================================\n\n/**\n * In-memory ColdStorage 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 ColdStorage layer.\n */\n export const make = (): Layer.Layer<ColdStorageTag> =>\n Layer.effect(\n ColdStorageTag,\n Effect.gen(function* () {\n const store = yield* Ref.make(HashMap.empty<string, StoredDocument>());\n\n return {\n load: (documentId) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const result = HashMap.get(current, documentId);\n return result._tag === \"Some\" ? result.value : undefined;\n }),\n\n save: (documentId, document) =>\n Ref.update(store, (map) => HashMap.set(map, documentId, document)),\n\n delete: (documentId) =>\n Ref.update(store, (map) => HashMap.remove(map, documentId)),\n };\n })\n );\n}\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const ColdStorage = {\n Tag: ColdStorageTag,\n make,\n InMemory,\n};\n"],"mappings":";;;;;;;;;;;AAoDA,IAAa,iBAAb,cAAoC,QAAQ,IAAI,qCAAqC,EAGlF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AA6BJ,MAAa,QACX,WAEA,MAAM,OAAO,gBAAgB,OAAO;;;wBAiBlC,MAAM,OACJ,gBACA,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,IAAI,KAAK,QAAQ,OAA+B,CAAC;AAEtE,SAAO;GACL,OAAO,eACL,OAAO,IAAI,aAAa;IACtB,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;IACrC,MAAM,SAAS,QAAQ,IAAI,SAAS,WAAW;AAC/C,WAAO,OAAO,SAAS,SAAS,OAAO,QAAQ;KAC/C;GAEJ,OAAO,YAAY,aACjB,IAAI,OAAO,QAAQ,QAAQ,QAAQ,IAAI,KAAK,YAAY,SAAS,CAAC;GAEpE,SAAS,eACP,IAAI,OAAO,QAAQ,QAAQ,QAAQ,OAAO,KAAK,WAAW,CAAC;GAC9D;GACD,CACH;;AAOL,MAAa,cAAc;CACzB,KAAK;CACL;CACA;CACD"}
|