@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/src/PresenceManager.ts
CHANGED
|
@@ -1,99 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @voidhash/mimic-effect - PresenceManager
|
|
3
|
+
*
|
|
4
|
+
* Internal service for managing presence state per document.
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
import {
|
|
7
|
+
Context,
|
|
8
|
+
Effect,
|
|
9
|
+
HashMap,
|
|
10
|
+
Layer,
|
|
11
|
+
Metric,
|
|
12
|
+
PubSub,
|
|
13
|
+
Ref,
|
|
14
|
+
Scope,
|
|
15
|
+
Stream,
|
|
16
|
+
} from "effect";
|
|
17
|
+
import type {
|
|
18
|
+
PresenceEntry,
|
|
19
|
+
PresenceEvent,
|
|
20
|
+
PresenceSnapshot,
|
|
21
|
+
} from "./Types";
|
|
22
|
+
import * as Metrics from "./Metrics";
|
|
15
23
|
|
|
16
24
|
// =============================================================================
|
|
17
|
-
//
|
|
25
|
+
// PresenceManager Interface
|
|
18
26
|
// =============================================================================
|
|
19
27
|
|
|
20
28
|
/**
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
readonly data: unknown;
|
|
26
|
-
/** Optional user ID from authentication */
|
|
27
|
-
readonly userId?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// =============================================================================
|
|
31
|
-
// Presence Events
|
|
32
|
-
// =============================================================================
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Event emitted when a presence is updated.
|
|
36
|
-
*/
|
|
37
|
-
export interface PresenceUpdateEvent {
|
|
38
|
-
readonly type: "presence_update";
|
|
39
|
-
/** The connection ID of the user who updated */
|
|
40
|
-
readonly id: string;
|
|
41
|
-
/** The presence data */
|
|
42
|
-
readonly data: unknown;
|
|
43
|
-
/** Optional user ID from authentication */
|
|
44
|
-
readonly userId?: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Event emitted when a presence is removed (user disconnected).
|
|
49
|
-
*/
|
|
50
|
-
export interface PresenceRemoveEvent {
|
|
51
|
-
readonly type: "presence_remove";
|
|
52
|
-
/** The connection ID of the user who disconnected */
|
|
53
|
-
readonly id: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Union of all presence events.
|
|
58
|
-
*/
|
|
59
|
-
export type PresenceEvent = PresenceUpdateEvent | PresenceRemoveEvent;
|
|
60
|
-
|
|
61
|
-
// =============================================================================
|
|
62
|
-
// Presence Snapshot
|
|
63
|
-
// =============================================================================
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* A snapshot of all presence entries for a document.
|
|
67
|
-
*/
|
|
68
|
-
export interface PresenceSnapshot {
|
|
69
|
-
/** Map of connectionId to presence entry */
|
|
70
|
-
readonly presences: Record<string, PresenceEntry>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// =============================================================================
|
|
74
|
-
// Document Presence Instance
|
|
75
|
-
// =============================================================================
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Per-document presence state.
|
|
79
|
-
*/
|
|
80
|
-
interface DocumentPresence {
|
|
81
|
-
/** Map of connectionId to presence entry */
|
|
82
|
-
readonly entries: Ref.Ref<HashMap.HashMap<string, PresenceEntry>>;
|
|
83
|
-
/** PubSub for broadcasting presence events */
|
|
84
|
-
readonly pubsub: PubSub.PubSub<PresenceEvent>;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// =============================================================================
|
|
88
|
-
// Presence Manager Service
|
|
89
|
-
// =============================================================================
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Service interface for the PresenceManager.
|
|
29
|
+
* Internal service for managing presence state per document.
|
|
30
|
+
*
|
|
31
|
+
* Presence is ephemeral state associated with connections, not persisted.
|
|
32
|
+
* Each document has its own set of presences, keyed by connectionId.
|
|
93
33
|
*/
|
|
94
34
|
export interface PresenceManager {
|
|
95
35
|
/**
|
|
96
|
-
* Get
|
|
36
|
+
* Get snapshot of all presences for a document.
|
|
97
37
|
*/
|
|
98
38
|
readonly getSnapshot: (
|
|
99
39
|
documentId: string
|
|
@@ -101,7 +41,6 @@ export interface PresenceManager {
|
|
|
101
41
|
|
|
102
42
|
/**
|
|
103
43
|
* Set/update presence for a connection.
|
|
104
|
-
* Broadcasts the update to all subscribers.
|
|
105
44
|
*/
|
|
106
45
|
readonly set: (
|
|
107
46
|
documentId: string,
|
|
@@ -110,8 +49,7 @@ export interface PresenceManager {
|
|
|
110
49
|
) => Effect.Effect<void>;
|
|
111
50
|
|
|
112
51
|
/**
|
|
113
|
-
* Remove presence for a connection (
|
|
114
|
-
* Broadcasts the removal to all subscribers.
|
|
52
|
+
* Remove presence for a connection (on disconnect).
|
|
115
53
|
*/
|
|
116
54
|
readonly remove: (
|
|
117
55
|
documentId: string,
|
|
@@ -120,178 +58,169 @@ export interface PresenceManager {
|
|
|
120
58
|
|
|
121
59
|
/**
|
|
122
60
|
* Subscribe to presence events for a document.
|
|
123
|
-
* Returns a
|
|
61
|
+
* Returns a stream of presence update/remove events.
|
|
124
62
|
*/
|
|
125
63
|
readonly subscribe: (
|
|
126
64
|
documentId: string
|
|
127
|
-
) => Effect.Effect<
|
|
128
|
-
Stream.Stream<PresenceEvent>,
|
|
129
|
-
never,
|
|
130
|
-
Scope.Scope
|
|
131
|
-
>;
|
|
65
|
+
) => Effect.Effect<Stream.Stream<PresenceEvent>, never, Scope.Scope>;
|
|
132
66
|
}
|
|
133
67
|
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// Context Tag
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
134
72
|
/**
|
|
135
|
-
* Context tag for PresenceManager
|
|
73
|
+
* Context tag for PresenceManager service
|
|
136
74
|
*/
|
|
137
75
|
export class PresenceManagerTag extends Context.Tag(
|
|
138
|
-
"@voidhash/mimic-
|
|
76
|
+
"@voidhash/mimic-effect/PresenceManager"
|
|
139
77
|
)<PresenceManagerTag, PresenceManager>() {}
|
|
140
78
|
|
|
141
79
|
// =============================================================================
|
|
142
|
-
//
|
|
80
|
+
// Internal Types
|
|
143
81
|
// =============================================================================
|
|
144
82
|
|
|
145
83
|
/**
|
|
146
|
-
*
|
|
84
|
+
* Per-document presence state
|
|
147
85
|
*/
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
// Get or create a document presence instance
|
|
155
|
-
const getOrCreateDocument = (
|
|
156
|
-
documentId: string
|
|
157
|
-
): Effect.Effect<DocumentPresence> =>
|
|
158
|
-
Effect.gen(function* () {
|
|
159
|
-
const current = yield* Ref.get(documents);
|
|
160
|
-
const existing = HashMap.get(current, documentId);
|
|
161
|
-
|
|
162
|
-
if (existing._tag === "Some") {
|
|
163
|
-
return existing.value;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Create new document presence
|
|
167
|
-
const entries = yield* Ref.make(
|
|
168
|
-
HashMap.empty<string, PresenceEntry>()
|
|
169
|
-
);
|
|
170
|
-
const pubsub = yield* PubSub.unbounded<PresenceEvent>();
|
|
171
|
-
|
|
172
|
-
const docPresence: DocumentPresence = {
|
|
173
|
-
entries,
|
|
174
|
-
pubsub,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Store in map
|
|
178
|
-
yield* Ref.update(documents, (map) =>
|
|
179
|
-
HashMap.set(map, documentId, docPresence)
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
return docPresence;
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Get snapshot of all presences for a document
|
|
186
|
-
const getSnapshot = (documentId: string): Effect.Effect<PresenceSnapshot> =>
|
|
187
|
-
Effect.gen(function* () {
|
|
188
|
-
const docPresence = yield* getOrCreateDocument(documentId);
|
|
189
|
-
const entriesMap = yield* Ref.get(docPresence.entries);
|
|
190
|
-
|
|
191
|
-
// Convert HashMap to Record
|
|
192
|
-
const presences: Record<string, PresenceEntry> = {};
|
|
193
|
-
for (const [id, entry] of entriesMap) {
|
|
194
|
-
presences[id] = entry;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return { presences };
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Set/update presence for a connection
|
|
201
|
-
const set = (
|
|
202
|
-
documentId: string,
|
|
203
|
-
connectionId: string,
|
|
204
|
-
entry: PresenceEntry
|
|
205
|
-
): Effect.Effect<void> =>
|
|
206
|
-
Effect.gen(function* () {
|
|
207
|
-
const docPresence = yield* getOrCreateDocument(documentId);
|
|
208
|
-
|
|
209
|
-
// Update the entry
|
|
210
|
-
yield* Ref.update(docPresence.entries, (map) =>
|
|
211
|
-
HashMap.set(map, connectionId, entry)
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
// Broadcast the update
|
|
215
|
-
yield* PubSub.publish(docPresence.pubsub, {
|
|
216
|
-
type: "presence_update",
|
|
217
|
-
id: connectionId,
|
|
218
|
-
data: entry.data,
|
|
219
|
-
userId: entry.userId,
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// Remove presence for a connection
|
|
224
|
-
const remove = (
|
|
225
|
-
documentId: string,
|
|
226
|
-
connectionId: string
|
|
227
|
-
): Effect.Effect<void> =>
|
|
228
|
-
Effect.gen(function* () {
|
|
229
|
-
const current = yield* Ref.get(documents);
|
|
230
|
-
const existing = HashMap.get(current, documentId);
|
|
231
|
-
|
|
232
|
-
if (existing._tag === "None") {
|
|
233
|
-
return; // Document doesn't exist, nothing to remove
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const docPresence = existing.value;
|
|
237
|
-
|
|
238
|
-
// Check if the connection has a presence
|
|
239
|
-
const entries = yield* Ref.get(docPresence.entries);
|
|
240
|
-
const hasEntry = HashMap.has(entries, connectionId);
|
|
241
|
-
|
|
242
|
-
if (!hasEntry) {
|
|
243
|
-
return; // No presence to remove
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Remove the entry
|
|
247
|
-
yield* Ref.update(docPresence.entries, (map) =>
|
|
248
|
-
HashMap.remove(map, connectionId)
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
// Broadcast the removal
|
|
252
|
-
yield* PubSub.publish(docPresence.pubsub, {
|
|
253
|
-
type: "presence_remove",
|
|
254
|
-
id: connectionId,
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// Subscribe to presence events
|
|
259
|
-
const subscribe = (
|
|
260
|
-
documentId: string
|
|
261
|
-
): Effect.Effect<Stream.Stream<PresenceEvent>, never, Scope.Scope> =>
|
|
262
|
-
Effect.gen(function* () {
|
|
263
|
-
const docPresence = yield* getOrCreateDocument(documentId);
|
|
264
|
-
|
|
265
|
-
// Subscribe to the PubSub
|
|
266
|
-
const queue = yield* PubSub.subscribe(docPresence.pubsub);
|
|
267
|
-
|
|
268
|
-
// Convert queue to stream
|
|
269
|
-
return Stream.fromQueue(queue);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
const manager: PresenceManager = {
|
|
273
|
-
getSnapshot,
|
|
274
|
-
set,
|
|
275
|
-
remove,
|
|
276
|
-
subscribe,
|
|
277
|
-
};
|
|
86
|
+
interface DocumentPresenceState {
|
|
87
|
+
readonly presences: HashMap.HashMap<string, PresenceEntry>;
|
|
88
|
+
readonly pubsub: PubSub.PubSub<PresenceEvent>;
|
|
89
|
+
}
|
|
278
90
|
|
|
279
|
-
|
|
280
|
-
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Layer Implementation
|
|
93
|
+
// =============================================================================
|
|
281
94
|
|
|
282
95
|
/**
|
|
283
|
-
*
|
|
96
|
+
* Create the PresenceManager layer.
|
|
284
97
|
*/
|
|
285
98
|
export const layer: Layer.Layer<PresenceManagerTag> = Layer.effect(
|
|
286
99
|
PresenceManagerTag,
|
|
287
|
-
|
|
100
|
+
Effect.gen(function* () {
|
|
101
|
+
// Store: documentId -> DocumentPresenceState
|
|
102
|
+
const store = yield* Ref.make(
|
|
103
|
+
HashMap.empty<string, DocumentPresenceState>()
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get or create presence state for a document
|
|
108
|
+
*/
|
|
109
|
+
const getOrCreateDocumentState = (
|
|
110
|
+
documentId: string
|
|
111
|
+
): Effect.Effect<DocumentPresenceState> =>
|
|
112
|
+
Effect.gen(function* () {
|
|
113
|
+
const current = yield* Ref.get(store);
|
|
114
|
+
const existing = HashMap.get(current, documentId);
|
|
115
|
+
if (existing._tag === "Some") {
|
|
116
|
+
return existing.value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create new state for this document
|
|
120
|
+
const pubsub = yield* PubSub.unbounded<PresenceEvent>();
|
|
121
|
+
const state: DocumentPresenceState = {
|
|
122
|
+
presences: HashMap.empty(),
|
|
123
|
+
pubsub,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
yield* Ref.update(store, (map) => HashMap.set(map, documentId, state));
|
|
127
|
+
return state;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
getSnapshot: (documentId) =>
|
|
132
|
+
Effect.gen(function* () {
|
|
133
|
+
const current = yield* Ref.get(store);
|
|
134
|
+
const existing = HashMap.get(current, documentId);
|
|
135
|
+
if (existing._tag === "None") {
|
|
136
|
+
return { presences: {} };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Convert HashMap to Record
|
|
140
|
+
const presences: Record<string, PresenceEntry> = {};
|
|
141
|
+
for (const [id, entry] of existing.value.presences) {
|
|
142
|
+
presences[id] = entry;
|
|
143
|
+
}
|
|
144
|
+
return { presences };
|
|
145
|
+
}),
|
|
146
|
+
|
|
147
|
+
set: (documentId, connectionId, entry) =>
|
|
148
|
+
Effect.gen(function* () {
|
|
149
|
+
const state = yield* getOrCreateDocumentState(documentId);
|
|
150
|
+
|
|
151
|
+
// Update presence in store
|
|
152
|
+
yield* Ref.update(store, (map) => {
|
|
153
|
+
const existing = HashMap.get(map, documentId);
|
|
154
|
+
if (existing._tag === "None") return map;
|
|
155
|
+
return HashMap.set(map, documentId, {
|
|
156
|
+
...existing.value,
|
|
157
|
+
presences: HashMap.set(
|
|
158
|
+
existing.value.presences,
|
|
159
|
+
connectionId,
|
|
160
|
+
entry
|
|
161
|
+
),
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Track metrics
|
|
166
|
+
yield* Metric.increment(Metrics.presenceUpdates);
|
|
167
|
+
yield* Metric.incrementBy(Metrics.presenceActive, 1);
|
|
168
|
+
|
|
169
|
+
// Broadcast update event
|
|
170
|
+
const event: PresenceEvent = {
|
|
171
|
+
type: "presence_update",
|
|
172
|
+
id: connectionId,
|
|
173
|
+
data: entry.data,
|
|
174
|
+
userId: entry.userId,
|
|
175
|
+
};
|
|
176
|
+
yield* PubSub.publish(state.pubsub, event);
|
|
177
|
+
}),
|
|
178
|
+
|
|
179
|
+
remove: (documentId, connectionId) =>
|
|
180
|
+
Effect.gen(function* () {
|
|
181
|
+
const current = yield* Ref.get(store);
|
|
182
|
+
const existing = HashMap.get(current, documentId);
|
|
183
|
+
if (existing._tag === "None") return;
|
|
184
|
+
|
|
185
|
+
// Check if presence exists before removing
|
|
186
|
+
const hasPresence = HashMap.has(existing.value.presences, connectionId);
|
|
187
|
+
if (!hasPresence) return;
|
|
188
|
+
|
|
189
|
+
// Remove presence from store
|
|
190
|
+
yield* Ref.update(store, (map) => {
|
|
191
|
+
const docState = HashMap.get(map, documentId);
|
|
192
|
+
if (docState._tag === "None") return map;
|
|
193
|
+
return HashMap.set(map, documentId, {
|
|
194
|
+
...docState.value,
|
|
195
|
+
presences: HashMap.remove(docState.value.presences, connectionId),
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Track metrics
|
|
200
|
+
yield* Metric.incrementBy(Metrics.presenceActive, -1);
|
|
201
|
+
|
|
202
|
+
// Broadcast remove event
|
|
203
|
+
const event: PresenceEvent = {
|
|
204
|
+
type: "presence_remove",
|
|
205
|
+
id: connectionId,
|
|
206
|
+
};
|
|
207
|
+
yield* PubSub.publish(existing.value.pubsub, event);
|
|
208
|
+
}),
|
|
209
|
+
|
|
210
|
+
subscribe: (documentId) =>
|
|
211
|
+
Effect.gen(function* () {
|
|
212
|
+
const state = yield* getOrCreateDocumentState(documentId);
|
|
213
|
+
return Stream.fromPubSub(state.pubsub);
|
|
214
|
+
}),
|
|
215
|
+
};
|
|
216
|
+
})
|
|
288
217
|
);
|
|
289
218
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
*/
|
|
294
|
-
export const layerDefault: Layer.Layer<PresenceManagerTag> = Layer.effectDiscard(
|
|
295
|
-
Effect.succeed(undefined)
|
|
296
|
-
).pipe(Layer.provideMerge(layer));
|
|
219
|
+
// =============================================================================
|
|
220
|
+
// Re-export namespace
|
|
221
|
+
// =============================================================================
|
|
297
222
|
|
|
223
|
+
export const PresenceManager = {
|
|
224
|
+
Tag: PresenceManagerTag,
|
|
225
|
+
layer,
|
|
226
|
+
};
|