@voidhash/mimic-effect 0.0.1 → 0.0.3

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 (100) hide show
  1. package/.turbo/turbo-build.log +99 -14
  2. package/dist/DocumentManager.cjs +118 -0
  3. package/dist/DocumentManager.d.cts +45 -0
  4. package/dist/DocumentManager.d.cts.map +1 -0
  5. package/dist/DocumentManager.d.mts +45 -0
  6. package/dist/DocumentManager.d.mts.map +1 -0
  7. package/dist/DocumentManager.mjs +105 -0
  8. package/dist/DocumentManager.mjs.map +1 -0
  9. package/dist/DocumentProtocol.cjs +94 -0
  10. package/dist/DocumentProtocol.d.cts +113 -0
  11. package/dist/DocumentProtocol.d.cts.map +1 -0
  12. package/dist/DocumentProtocol.d.mts +113 -0
  13. package/dist/DocumentProtocol.d.mts.map +1 -0
  14. package/dist/DocumentProtocol.mjs +89 -0
  15. package/dist/DocumentProtocol.mjs.map +1 -0
  16. package/dist/MimicAuthService.cjs +55 -0
  17. package/dist/MimicAuthService.d.cts +65 -0
  18. package/dist/MimicAuthService.d.cts.map +1 -0
  19. package/dist/MimicAuthService.d.mts +65 -0
  20. package/dist/MimicAuthService.d.mts.map +1 -0
  21. package/dist/MimicAuthService.mjs +47 -0
  22. package/dist/MimicAuthService.mjs.map +1 -0
  23. package/dist/MimicConfig.cjs +50 -0
  24. package/dist/MimicConfig.d.cts +99 -0
  25. package/dist/MimicConfig.d.cts.map +1 -0
  26. package/dist/MimicConfig.d.mts +99 -0
  27. package/dist/MimicConfig.d.mts.map +1 -0
  28. package/dist/MimicConfig.mjs +41 -0
  29. package/dist/MimicConfig.mjs.map +1 -0
  30. package/dist/MimicDataStorage.cjs +83 -0
  31. package/dist/MimicDataStorage.d.cts +113 -0
  32. package/dist/MimicDataStorage.d.cts.map +1 -0
  33. package/dist/MimicDataStorage.d.mts +113 -0
  34. package/dist/MimicDataStorage.d.mts.map +1 -0
  35. package/dist/MimicDataStorage.mjs +74 -0
  36. package/dist/MimicDataStorage.mjs.map +1 -0
  37. package/dist/MimicServer.cjs +230 -0
  38. package/dist/MimicServer.d.cts +192 -0
  39. package/dist/MimicServer.d.cts.map +1 -0
  40. package/dist/MimicServer.d.mts +192 -0
  41. package/dist/MimicServer.d.mts.map +1 -0
  42. package/dist/MimicServer.mjs +223 -0
  43. package/dist/MimicServer.mjs.map +1 -0
  44. package/dist/PresenceManager.cjs +108 -0
  45. package/dist/PresenceManager.d.cts +91 -0
  46. package/dist/PresenceManager.d.cts.map +1 -0
  47. package/dist/PresenceManager.d.mts +91 -0
  48. package/dist/PresenceManager.d.mts.map +1 -0
  49. package/dist/PresenceManager.mjs +95 -0
  50. package/dist/PresenceManager.mjs.map +1 -0
  51. package/dist/WebSocketHandler.cjs +366 -0
  52. package/dist/WebSocketHandler.d.cts +34 -0
  53. package/dist/WebSocketHandler.d.cts.map +1 -0
  54. package/dist/WebSocketHandler.d.mts +34 -0
  55. package/dist/WebSocketHandler.d.mts.map +1 -0
  56. package/dist/WebSocketHandler.mjs +355 -0
  57. package/dist/WebSocketHandler.mjs.map +1 -0
  58. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.cjs +14 -0
  59. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.mjs +14 -0
  60. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.cjs +27 -0
  61. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs +27 -0
  62. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.cjs +16 -0
  63. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.mjs +16 -0
  64. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.cjs +11 -0
  65. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.mjs +11 -0
  66. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.cjs +18 -0
  67. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.mjs +12 -0
  68. package/dist/_virtual/rolldown_runtime.cjs +43 -0
  69. package/dist/{chunk-C6wwvPpM.mjs → _virtual/rolldown_runtime.mjs} +1 -1
  70. package/dist/auth/NoAuth.cjs +43 -0
  71. package/dist/auth/NoAuth.d.cts +22 -0
  72. package/dist/auth/NoAuth.d.cts.map +1 -0
  73. package/dist/auth/NoAuth.d.mts +22 -0
  74. package/dist/auth/NoAuth.d.mts.map +1 -0
  75. package/dist/auth/NoAuth.mjs +36 -0
  76. package/dist/auth/NoAuth.mjs.map +1 -0
  77. package/dist/errors.cjs +74 -0
  78. package/dist/errors.d.cts +89 -0
  79. package/dist/errors.d.cts.map +1 -0
  80. package/dist/errors.d.mts +89 -0
  81. package/dist/errors.d.mts.map +1 -0
  82. package/dist/errors.mjs +67 -0
  83. package/dist/errors.mjs.map +1 -0
  84. package/dist/index.cjs +29 -1227
  85. package/dist/index.d.cts +12 -795
  86. package/dist/index.d.mts +12 -795
  87. package/dist/index.mjs +13 -1162
  88. package/dist/storage/InMemoryDataStorage.cjs +57 -0
  89. package/dist/storage/InMemoryDataStorage.d.cts +19 -0
  90. package/dist/storage/InMemoryDataStorage.d.cts.map +1 -0
  91. package/dist/storage/InMemoryDataStorage.d.mts +19 -0
  92. package/dist/storage/InMemoryDataStorage.d.mts.map +1 -0
  93. package/dist/storage/InMemoryDataStorage.mjs +48 -0
  94. package/dist/storage/InMemoryDataStorage.mjs.map +1 -0
  95. package/package.json +3 -3
  96. package/src/MimicServer.ts +1 -1
  97. package/tsdown.config.ts +1 -1
  98. package/dist/index.d.cts.map +0 -1
  99. package/dist/index.d.mts.map +0 -1
  100. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,223 @@
1
+ import { __export } from "./_virtual/rolldown_runtime.mjs";
2
+ import { MimicServerConfigTag, layer as layer$1 } from "./MimicConfig.mjs";
3
+ import { DocumentManagerTag, layer as layer$2 } from "./DocumentManager.mjs";
4
+ import { MimicAuthServiceTag } from "./MimicAuthService.mjs";
5
+ import { PresenceManagerTag, layer as layer$3 } from "./PresenceManager.mjs";
6
+ import { extractDocumentId, handleConnection, makeHandler } from "./WebSocketHandler.mjs";
7
+ import { layerDefault } from "./storage/InMemoryDataStorage.mjs";
8
+ import { layerDefault as layerDefault$1 } from "./auth/NoAuth.mjs";
9
+ import * as Effect from "effect/Effect";
10
+ import * as Layer from "effect/Layer";
11
+ import * as Context from "effect/Context";
12
+ import { SocketServer } from "@effect/platform/SocketServer";
13
+ import { HttpLayerRouter, HttpServerRequest, HttpServerResponse } from "@effect/platform";
14
+
15
+ //#region src/MimicServer.ts
16
+ /**
17
+ * @since 0.0.1
18
+ * Mimic server layer composition.
19
+ */
20
+ var MimicServer_exports = /* @__PURE__ */ __export({
21
+ MimicWebSocketHandler: () => MimicWebSocketHandler,
22
+ documentManagerLayer: () => documentManagerLayer,
23
+ handlerLayer: () => handlerLayer,
24
+ layer: () => layer,
25
+ layerHttpLayerRouter: () => layerHttpLayerRouter,
26
+ run: () => run
27
+ });
28
+ /**
29
+ * Tag for the WebSocket handler function.
30
+ */
31
+ var MimicWebSocketHandler = class extends Context.Tag("@voidhash/mimic-server-effect/MimicWebSocketHandler")() {};
32
+ /**
33
+ * Create a Mimic WebSocket handler layer.
34
+ *
35
+ * This layer provides a handler function that can be used with any WebSocket server
36
+ * implementation. The handler takes a socket and document ID and manages the
37
+ * document synchronization.
38
+ *
39
+ * By default, uses in-memory storage and no authentication.
40
+ * Override these by providing MimicDataStorage and MimicAuthService layers.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * import { MimicServer, MimicAuthService } from "@voidhash/mimic-effect";
45
+ * import { Primitive } from "@voidhash/mimic";
46
+ *
47
+ * const TodoSchema = Primitive.Struct({
48
+ * title: Primitive.String(),
49
+ * completed: Primitive.Boolean(),
50
+ * });
51
+ *
52
+ * // Create the handler layer with defaults
53
+ * const HandlerLayer = MimicServer.layer({
54
+ * basePath: "/mimic/todo",
55
+ * schema: TodoSchema
56
+ * });
57
+ *
58
+ * // Or with custom auth
59
+ * const HandlerLayerWithAuth = MimicServer.layer({
60
+ * basePath: "/mimic/todo",
61
+ * schema: TodoSchema
62
+ * }).pipe(
63
+ * Layer.provideMerge(MimicAuthService.layer({
64
+ * authHandler: (token) => ({ success: true, userId: "user-123" })
65
+ * }))
66
+ * );
67
+ * ```
68
+ */
69
+ const layer = (options) => {
70
+ const configLayer = layer$1({
71
+ schema: options.schema,
72
+ maxTransactionHistory: options.maxTransactionHistory,
73
+ presence: options.presence
74
+ });
75
+ return Layer.merge(Layer.effect(MimicWebSocketHandler, makeHandler).pipe(Layer.provide(layer$2), Layer.provide(layer$3), Layer.provide(configLayer)), layer$2.pipe(Layer.provide(configLayer))).pipe(Layer.provide(layerDefault), Layer.provide(layerDefault$1));
76
+ };
77
+ /**
78
+ * Create the Mimic server handler layer.
79
+ * This layer provides the WebSocket handler that can be used with any WebSocket server.
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * import { MimicServer } from "@voidhash/mimic-server-effect";
84
+ * import { SocketServer } from "@effect/platform/SocketServer";
85
+ * import { Primitive } from "@voidhash/mimic";
86
+ *
87
+ * // Define your document schema
88
+ * const TodoSchema = Primitive.Struct({
89
+ * title: Primitive.String(),
90
+ * completed: Primitive.Boolean(),
91
+ * });
92
+ *
93
+ * // Create the server layer
94
+ * const serverLayer = MimicServer.handlerLayer({
95
+ * schema: TodoSchema,
96
+ * });
97
+ *
98
+ * // Run with your socket server
99
+ * Effect.gen(function* () {
100
+ * const handler = yield* MimicServer.MimicWebSocketHandler;
101
+ * const server = yield* SocketServer;
102
+ *
103
+ * yield* server.run((socket) =>
104
+ * // Extract document ID from request and call handler
105
+ * handler(socket, "my-document-id")
106
+ * );
107
+ * }).pipe(
108
+ * Effect.provide(serverLayer),
109
+ * Effect.provide(YourSocketServerLayer),
110
+ * );
111
+ * ```
112
+ */
113
+ const handlerLayer = (options) => Layer.effect(MimicWebSocketHandler, makeHandler).pipe(Layer.provide(layer$2), Layer.provide(layer$3), Layer.provide(layer$1(options)), Layer.provide(layerDefault), Layer.provide(layerDefault$1));
114
+ /**
115
+ * Create the document manager layer.
116
+ */
117
+ const documentManagerLayer = (options) => layer$2.pipe(Layer.provide(layer$1(options)), Layer.provide(layerDefault), Layer.provide(layerDefault$1));
118
+ /**
119
+ * Run a Mimic WebSocket server with the provided handler.
120
+ *
121
+ * This is a helper that:
122
+ * 1. Gets the WebSocket handler from context
123
+ * 2. Runs the socket server with the handler
124
+ *
125
+ * Note: The document ID extraction from socket is implementation-specific.
126
+ * You may need to customize this based on your socket server.
127
+ */
128
+ const run = (extractDocumentId$1) => Effect.gen(function* () {
129
+ const handler = yield* MimicWebSocketHandler;
130
+ yield* (yield* SocketServer).run((socket) => Effect.gen(function* () {
131
+ yield* handler(socket, yield* extractDocumentId$1(socket));
132
+ }).pipe(Effect.catchAll((error) => Effect.logError("Connection error", error))));
133
+ });
134
+ /**
135
+ * Create the HTTP handler effect for WebSocket upgrade.
136
+ * This handler:
137
+ * 1. Extracts the document ID from the URL path
138
+ * 2. Upgrades the HTTP connection to WebSocket
139
+ * 3. Delegates to the WebSocketHandler for document sync
140
+ */
141
+ const makeMimicHandler = Effect.gen(function* () {
142
+ const config = yield* MimicServerConfigTag;
143
+ const authService = yield* MimicAuthServiceTag;
144
+ const documentManager = yield* DocumentManagerTag;
145
+ const presenceManager = yield* PresenceManagerTag;
146
+ return Effect.gen(function* () {
147
+ const request = yield* HttpServerRequest.HttpServerRequest;
148
+ yield* extractDocumentId(request.url);
149
+ const socket = yield* request.upgrade;
150
+ yield* handleConnection(socket, request.url).pipe(Effect.provideService(MimicServerConfigTag, config), Effect.provideService(MimicAuthServiceTag, authService), Effect.provideService(DocumentManagerTag, documentManager), Effect.provideService(PresenceManagerTag, presenceManager), Effect.scoped, Effect.catchAll((error) => Effect.logError("WebSocket connection error", error)));
151
+ return HttpServerResponse.empty();
152
+ }).pipe(Effect.catchAll((error) => Effect.gen(function* () {
153
+ yield* Effect.logWarning("WebSocket upgrade failed", error);
154
+ return HttpServerResponse.text("WebSocket upgrade failed", { status: 400 });
155
+ })));
156
+ });
157
+ /**
158
+ * Create a Mimic server layer that integrates with HttpLayerRouter.
159
+ *
160
+ * This function creates a layer that:
161
+ * 1. Registers a WebSocket route at the specified base path
162
+ * 2. Handles WebSocket upgrades for document sync
163
+ * 3. Provides all required dependencies (config, auth, storage, document manager)
164
+ *
165
+ * By default, uses in-memory storage and no authentication.
166
+ * To override these defaults, provide custom layers before the defaults:
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * import { MimicServer, MimicAuthService } from "@voidhash/mimic-effect";
171
+ * import { HttpLayerRouter } from "@effect/platform";
172
+ * import { Primitive } from "@voidhash/mimic";
173
+ *
174
+ * const TodoSchema = Primitive.Struct({
175
+ * title: Primitive.String(),
176
+ * completed: Primitive.Boolean(),
177
+ * });
178
+ *
179
+ * // Create the Mimic route layer with defaults
180
+ * const MimicRoute = MimicServer.layerHttpLayerRouter({
181
+ * basePath: "/mimic/todo",
182
+ * schema: TodoSchema
183
+ * });
184
+ *
185
+ * // Or with custom auth - use Layer.provide to inject before defaults
186
+ * const MimicRouteWithAuth = MimicServer.layerHttpLayerRouter({
187
+ * basePath: "/mimic/todo",
188
+ * schema: TodoSchema,
189
+ * authLayer: MimicAuthService.layer({
190
+ * authHandler: (token) => ({ success: true, userId: token })
191
+ * })
192
+ * });
193
+ *
194
+ * // Merge with other routes and serve
195
+ * const AllRoutes = Layer.mergeAll(MimicRoute, OtherRoutes);
196
+ * HttpLayerRouter.serve(AllRoutes).pipe(
197
+ * Layer.provide(BunHttpServer.layer({ port: 3000 })),
198
+ * Layer.launch,
199
+ * BunRuntime.runMain
200
+ * );
201
+ * ```
202
+ */
203
+ const layerHttpLayerRouter = (options) => {
204
+ var _options$basePath, _options$authLayer, _options$storageLayer;
205
+ const wsPath = `${(_options$basePath = options.basePath) !== null && _options$basePath !== void 0 ? _options$basePath : "/mimic"}/doc/*`;
206
+ const configLayer = layer$1({
207
+ schema: options.schema,
208
+ maxTransactionHistory: options.maxTransactionHistory,
209
+ presence: options.presence
210
+ });
211
+ const authLayer = (_options$authLayer = options.authLayer) !== null && _options$authLayer !== void 0 ? _options$authLayer : layerDefault$1;
212
+ const storageLayer = (_options$storageLayer = options.storageLayer) !== null && _options$storageLayer !== void 0 ? _options$storageLayer : layerDefault;
213
+ const registerRoute = Effect.gen(function* () {
214
+ const router = yield* HttpLayerRouter.HttpRouter;
215
+ const handler = yield* makeMimicHandler;
216
+ yield* router.add("GET", wsPath, handler);
217
+ });
218
+ return Layer.scopedDiscard(registerRoute).pipe(Layer.provide(layer$2), Layer.provide(layer$3), Layer.provide(configLayer), Layer.provide(storageLayer), Layer.provide(authLayer));
219
+ };
220
+
221
+ //#endregion
222
+ export { MimicServer_exports };
223
+ //# sourceMappingURL=MimicServer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MimicServer.mjs","names":["MimicConfig.layer","WebSocketHandler.makeHandler","DocumentManager.layer","PresenceManager.layer","InMemoryDataStorage.layerDefault","NoAuth.layerDefault","extractDocumentId","MimicConfig.MimicServerConfigTag","DocumentManager.DocumentManagerTag","PresenceManager.PresenceManagerTag","WebSocketHandler.extractDocumentId","WebSocketHandler.handleConnection","wsPath: PathInput"],"sources":["../src/MimicServer.ts"],"sourcesContent":["/**\n * @since 0.0.1\n * Mimic server layer composition.\n */\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport * as Context from \"effect/Context\";\nimport type * as Socket from \"@effect/platform/Socket\";\nimport { SocketServer } from \"@effect/platform/SocketServer\";\nimport type { Primitive, Presence } from \"@voidhash/mimic\";\n\nimport * as DocumentManager from \"./DocumentManager.js\";\nimport * as WebSocketHandler from \"./WebSocketHandler.js\";\nimport * as MimicConfig from \"./MimicConfig.js\";\nimport { MimicDataStorageTag } from \"./MimicDataStorage.js\";\nimport { MimicAuthServiceTag } from \"./MimicAuthService.js\";\nimport * as PresenceManager from \"./PresenceManager.js\";\nimport * as InMemoryDataStorage from \"./storage/InMemoryDataStorage.js\";\nimport * as NoAuth from \"./auth/NoAuth.js\";\nimport { HttpLayerRouter, HttpServerRequest, HttpServerResponse } from \"@effect/platform\";\nimport { PathInput } from \"@effect/platform/HttpRouter\";\n\n// =============================================================================\n// Handler Tag\n// =============================================================================\n\n/**\n * Tag for the WebSocket handler function.\n */\nexport class MimicWebSocketHandler extends Context.Tag(\n \"@voidhash/mimic-server-effect/MimicWebSocketHandler\"\n)<\n MimicWebSocketHandler,\n (socket: Socket.Socket, documentId: string) => Effect.Effect<void, unknown>\n>() {}\n\n// =============================================================================\n// Layer Composition Options\n// =============================================================================\n\n/**\n * Options for creating a Mimic server layer.\n */\nexport interface MimicLayerOptions<TSchema extends Primitive.AnyPrimitive> {\n /**\n * Base path for document routes (used for path matching).\n * @example \"/mimic/todo\" - documents accessed at \"/mimic/todo/:documentId\"\n */\n readonly basePath?: PathInput;\n /**\n * The schema defining the document structure.\n */\n readonly schema: TSchema;\n /**\n * Maximum number of processed transaction IDs to track for deduplication.\n * @default 1000\n */\n readonly maxTransactionHistory?: number;\n /**\n * Optional presence schema for ephemeral per-user data.\n * When provided, enables presence features on WebSocket connections.\n */\n readonly presence?: Presence.AnyPresence;\n}\n\n// =============================================================================\n// Layer Composition\n// =============================================================================\n\n/**\n * Create a Mimic WebSocket handler layer.\n * \n * This layer provides a handler function that can be used with any WebSocket server\n * implementation. The handler takes a socket and document ID and manages the\n * document synchronization.\n * \n * By default, uses in-memory storage and no authentication.\n * Override these by providing MimicDataStorage and MimicAuthService layers.\n * \n * @example\n * ```typescript\n * import { MimicServer, MimicAuthService } from \"@voidhash/mimic-effect\";\n * import { Primitive } from \"@voidhash/mimic\";\n * \n * const TodoSchema = Primitive.Struct({\n * title: Primitive.String(),\n * completed: Primitive.Boolean(),\n * });\n * \n * // Create the handler layer with defaults\n * const HandlerLayer = MimicServer.layer({\n * basePath: \"/mimic/todo\",\n * schema: TodoSchema\n * });\n * \n * // Or with custom auth\n * const HandlerLayerWithAuth = MimicServer.layer({\n * basePath: \"/mimic/todo\",\n * schema: TodoSchema\n * }).pipe(\n * Layer.provideMerge(MimicAuthService.layer({\n * authHandler: (token) => ({ success: true, userId: \"user-123\" })\n * }))\n * );\n * ```\n */\nexport const layer = <TSchema extends Primitive.AnyPrimitive>(\n options: MimicLayerOptions<TSchema>\n): Layer.Layer<MimicWebSocketHandler | DocumentManager.DocumentManagerTag> => {\n const configLayer = MimicConfig.layer({\n schema: options.schema,\n maxTransactionHistory: options.maxTransactionHistory,\n presence: options.presence,\n });\n\n return Layer.merge(\n // Handler layer\n Layer.effect(MimicWebSocketHandler, WebSocketHandler.makeHandler).pipe(\n Layer.provide(DocumentManager.layer),\n Layer.provide(PresenceManager.layer),\n Layer.provide(configLayer)\n ),\n // Document manager layer\n DocumentManager.layer.pipe(Layer.provide(configLayer))\n ).pipe(\n // Provide defaults if not overridden\n Layer.provide(InMemoryDataStorage.layerDefault),\n Layer.provide(NoAuth.layerDefault)\n );\n};\n\n/**\n * Create the Mimic server handler layer.\n * This layer provides the WebSocket handler that can be used with any WebSocket server.\n *\n * @example\n * ```typescript\n * import { MimicServer } from \"@voidhash/mimic-server-effect\";\n * import { SocketServer } from \"@effect/platform/SocketServer\";\n * import { Primitive } from \"@voidhash/mimic\";\n *\n * // Define your document schema\n * const TodoSchema = Primitive.Struct({\n * title: Primitive.String(),\n * completed: Primitive.Boolean(),\n * });\n *\n * // Create the server layer\n * const serverLayer = MimicServer.handlerLayer({\n * schema: TodoSchema,\n * });\n *\n * // Run with your socket server\n * Effect.gen(function* () {\n * const handler = yield* MimicServer.MimicWebSocketHandler;\n * const server = yield* SocketServer;\n *\n * yield* server.run((socket) =>\n * // Extract document ID from request and call handler\n * handler(socket, \"my-document-id\")\n * );\n * }).pipe(\n * Effect.provide(serverLayer),\n * Effect.provide(YourSocketServerLayer),\n * );\n * ```\n */\nexport const handlerLayer = <TSchema extends Primitive.AnyPrimitive>(\n options: MimicConfig.MimicServerConfigOptions<TSchema>\n): Layer.Layer<MimicWebSocketHandler> =>\n Layer.effect(MimicWebSocketHandler, WebSocketHandler.makeHandler).pipe(\n Layer.provide(DocumentManager.layer),\n Layer.provide(PresenceManager.layer),\n Layer.provide(MimicConfig.layer(options)),\n // Provide defaults\n Layer.provide(InMemoryDataStorage.layerDefault),\n Layer.provide(NoAuth.layerDefault)\n );\n\n/**\n * Create the document manager layer.\n */\nexport const documentManagerLayer = <TSchema extends Primitive.AnyPrimitive>(\n options: MimicConfig.MimicServerConfigOptions<TSchema>\n): Layer.Layer<DocumentManager.DocumentManagerTag> =>\n DocumentManager.layer.pipe(\n Layer.provide(MimicConfig.layer(options)),\n // Provide defaults\n Layer.provide(InMemoryDataStorage.layerDefault),\n Layer.provide(NoAuth.layerDefault)\n );\n\n// =============================================================================\n// Convenience Functions\n// =============================================================================\n\n/**\n * Run a Mimic WebSocket server with the provided handler.\n *\n * This is a helper that:\n * 1. Gets the WebSocket handler from context\n * 2. Runs the socket server with the handler\n *\n * Note: The document ID extraction from socket is implementation-specific.\n * You may need to customize this based on your socket server.\n */\nexport const run = (\n extractDocumentId: (socket: Socket.Socket) => Effect.Effect<string>\n) =>\n Effect.gen(function* () {\n const handler = yield* MimicWebSocketHandler;\n const server = yield* SocketServer;\n\n yield* server.run((socket) =>\n Effect.gen(function* () {\n const documentId = yield* extractDocumentId(socket);\n yield* handler(socket, documentId);\n }).pipe(\n Effect.catchAll((error) =>\n Effect.logError(\"Connection error\", error)\n )\n )\n );\n });\n\n\n/**\n * Create the HTTP handler effect for WebSocket upgrade.\n * This handler:\n * 1. Extracts the document ID from the URL path\n * 2. Upgrades the HTTP connection to WebSocket\n * 3. Delegates to the WebSocketHandler for document sync\n */\nconst makeMimicHandler = Effect.gen(function* () {\n const config = yield* MimicConfig.MimicServerConfigTag;\n const authService = yield* MimicAuthServiceTag;\n const documentManager = yield* DocumentManager.DocumentManagerTag;\n const presenceManager = yield* PresenceManager.PresenceManagerTag;\n\n return Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n\n // Extract document ID from the URL path\n // Expected format: /basePath/doc/{documentId}\n const documentId = yield* WebSocketHandler.extractDocumentId(request.url);\n\n // Upgrade to WebSocket\n const socket = yield* request.upgrade;\n\n // Handle the WebSocket connection\n yield* WebSocketHandler.handleConnection(socket, request.url).pipe(\n Effect.provideService(MimicConfig.MimicServerConfigTag, config),\n Effect.provideService(MimicAuthServiceTag, authService),\n Effect.provideService(DocumentManager.DocumentManagerTag, documentManager),\n Effect.provideService(PresenceManager.PresenceManagerTag, presenceManager),\n Effect.scoped,\n Effect.catchAll((error) =>\n Effect.logError(\"WebSocket connection error\", error)\n )\n );\n\n // Return empty response - the WebSocket upgrade handles the connection\n return HttpServerResponse.empty();\n }).pipe(\n Effect.catchAll((error) =>\n Effect.gen(function* () {\n yield* Effect.logWarning(\"WebSocket upgrade failed\", error);\n return HttpServerResponse.text(\"WebSocket upgrade failed\", {\n status: 400,\n });\n })\n )\n );\n});\n\n\n\n/**\n * Create a Mimic server layer that integrates with HttpLayerRouter.\n *\n * This function creates a layer that:\n * 1. Registers a WebSocket route at the specified base path\n * 2. Handles WebSocket upgrades for document sync\n * 3. Provides all required dependencies (config, auth, storage, document manager)\n *\n * By default, uses in-memory storage and no authentication.\n * To override these defaults, provide custom layers before the defaults:\n *\n * @example\n * ```typescript\n * import { MimicServer, MimicAuthService } from \"@voidhash/mimic-effect\";\n * import { HttpLayerRouter } from \"@effect/platform\";\n * import { Primitive } from \"@voidhash/mimic\";\n *\n * const TodoSchema = Primitive.Struct({\n * title: Primitive.String(),\n * completed: Primitive.Boolean(),\n * });\n *\n * // Create the Mimic route layer with defaults\n * const MimicRoute = MimicServer.layerHttpLayerRouter({\n * basePath: \"/mimic/todo\",\n * schema: TodoSchema\n * });\n *\n * // Or with custom auth - use Layer.provide to inject before defaults\n * const MimicRouteWithAuth = MimicServer.layerHttpLayerRouter({\n * basePath: \"/mimic/todo\",\n * schema: TodoSchema,\n * authLayer: MimicAuthService.layer({\n * authHandler: (token) => ({ success: true, userId: token })\n * })\n * });\n *\n * // Merge with other routes and serve\n * const AllRoutes = Layer.mergeAll(MimicRoute, OtherRoutes);\n * HttpLayerRouter.serve(AllRoutes).pipe(\n * Layer.provide(BunHttpServer.layer({ port: 3000 })),\n * Layer.launch,\n * BunRuntime.runMain\n * );\n * ```\n */\nexport const layerHttpLayerRouter = <TSchema extends Primitive.AnyPrimitive>(\n options: MimicLayerOptions<TSchema> & {\n /** Custom auth layer. Defaults to NoAuth (all connections allowed). */\n readonly authLayer?: Layer.Layer<MimicAuthServiceTag>;\n /** Custom storage layer. Defaults to InMemoryDataStorage. */\n readonly storageLayer?: Layer.Layer<MimicDataStorageTag>;\n }\n) => {\n // Build the base path pattern for WebSocket routes\n // Append /doc/* to match /basePath/doc/{documentId}\n const basePath = options.basePath ?? \"/mimic\";\n const wsPath: PathInput = `${basePath}/doc/*` as PathInput;\n\n // Create the config layer\n const configLayer = MimicConfig.layer({\n schema: options.schema,\n maxTransactionHistory: options.maxTransactionHistory,\n presence: options.presence,\n });\n\n // Use provided layers or defaults\n const authLayer = options.authLayer ?? NoAuth.layerDefault;\n const storageLayer = options.storageLayer ?? InMemoryDataStorage.layerDefault;\n\n // Create the route registration effect\n const registerRoute = Effect.gen(function* () {\n const router = yield* HttpLayerRouter.HttpRouter;\n const handler = yield* makeMimicHandler;\n yield* router.add(\"GET\", wsPath, handler);\n });\n\n // Build the layer with all dependencies\n return Layer.scopedDiscard(registerRoute).pipe(\n Layer.provide(DocumentManager.layer),\n Layer.provide(PresenceManager.layer),\n Layer.provide(configLayer),\n Layer.provide(storageLayer),\n Layer.provide(authLayer)\n );\n};"],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ let effect_Effect = require("effect/Effect");
3
+ effect_Effect = require_rolldown_runtime.__toESM(effect_Effect);
4
+ let effect_Layer = require("effect/Layer");
5
+ effect_Layer = require_rolldown_runtime.__toESM(effect_Layer);
6
+ let effect_Context = require("effect/Context");
7
+ effect_Context = require_rolldown_runtime.__toESM(effect_Context);
8
+ let effect_PubSub = require("effect/PubSub");
9
+ effect_PubSub = require_rolldown_runtime.__toESM(effect_PubSub);
10
+ let effect_Ref = require("effect/Ref");
11
+ effect_Ref = require_rolldown_runtime.__toESM(effect_Ref);
12
+ let effect_HashMap = require("effect/HashMap");
13
+ effect_HashMap = require_rolldown_runtime.__toESM(effect_HashMap);
14
+ let effect_Stream = require("effect/Stream");
15
+ effect_Stream = require_rolldown_runtime.__toESM(effect_Stream);
16
+
17
+ //#region src/PresenceManager.ts
18
+ /**
19
+ * @since 0.0.1
20
+ * Presence manager for ephemeral per-connection state.
21
+ * Handles in-memory storage and broadcasting of presence updates.
22
+ */
23
+ var PresenceManager_exports = /* @__PURE__ */ require_rolldown_runtime.__export({
24
+ PresenceManagerTag: () => PresenceManagerTag,
25
+ layer: () => layer,
26
+ layerDefault: () => layerDefault
27
+ });
28
+ /**
29
+ * Context tag for PresenceManager.
30
+ */
31
+ var PresenceManagerTag = class extends effect_Context.Tag("@voidhash/mimic-server-effect/PresenceManager")() {};
32
+ /**
33
+ * Create the PresenceManager service.
34
+ */
35
+ const makePresenceManager = effect_Effect.gen(function* () {
36
+ const documents = yield* effect_Ref.make(effect_HashMap.empty());
37
+ const getOrCreateDocument = (documentId) => effect_Effect.gen(function* () {
38
+ const current = yield* effect_Ref.get(documents);
39
+ const existing = effect_HashMap.get(current, documentId);
40
+ if (existing._tag === "Some") return existing.value;
41
+ const docPresence = {
42
+ entries: yield* effect_Ref.make(effect_HashMap.empty()),
43
+ pubsub: yield* effect_PubSub.unbounded()
44
+ };
45
+ yield* effect_Ref.update(documents, (map) => effect_HashMap.set(map, documentId, docPresence));
46
+ return docPresence;
47
+ });
48
+ const getSnapshot = (documentId) => effect_Effect.gen(function* () {
49
+ const docPresence = yield* getOrCreateDocument(documentId);
50
+ const entriesMap = yield* effect_Ref.get(docPresence.entries);
51
+ const presences = {};
52
+ for (const [id, entry] of entriesMap) presences[id] = entry;
53
+ return { presences };
54
+ });
55
+ const set = (documentId, connectionId, entry) => effect_Effect.gen(function* () {
56
+ const docPresence = yield* getOrCreateDocument(documentId);
57
+ yield* effect_Ref.update(docPresence.entries, (map) => effect_HashMap.set(map, connectionId, entry));
58
+ yield* effect_PubSub.publish(docPresence.pubsub, {
59
+ type: "presence_update",
60
+ id: connectionId,
61
+ data: entry.data,
62
+ userId: entry.userId
63
+ });
64
+ });
65
+ const remove = (documentId, connectionId) => effect_Effect.gen(function* () {
66
+ const current = yield* effect_Ref.get(documents);
67
+ const existing = effect_HashMap.get(current, documentId);
68
+ if (existing._tag === "None") return;
69
+ const docPresence = existing.value;
70
+ const entries = yield* effect_Ref.get(docPresence.entries);
71
+ if (!effect_HashMap.has(entries, connectionId)) return;
72
+ yield* effect_Ref.update(docPresence.entries, (map) => effect_HashMap.remove(map, connectionId));
73
+ yield* effect_PubSub.publish(docPresence.pubsub, {
74
+ type: "presence_remove",
75
+ id: connectionId
76
+ });
77
+ });
78
+ const subscribe = (documentId) => effect_Effect.gen(function* () {
79
+ const docPresence = yield* getOrCreateDocument(documentId);
80
+ const queue = yield* effect_PubSub.subscribe(docPresence.pubsub);
81
+ return effect_Stream.fromQueue(queue);
82
+ });
83
+ return {
84
+ getSnapshot,
85
+ set,
86
+ remove,
87
+ subscribe
88
+ };
89
+ });
90
+ /**
91
+ * Layer that provides PresenceManager.
92
+ */
93
+ const layer = effect_Layer.effect(PresenceManagerTag, makePresenceManager);
94
+ /**
95
+ * Default layer that provides PresenceManager.
96
+ * Uses the default priority for layer composition.
97
+ */
98
+ const layerDefault = effect_Layer.effectDiscard(effect_Effect.succeed(void 0)).pipe(effect_Layer.provideMerge(layer));
99
+
100
+ //#endregion
101
+ exports.PresenceManagerTag = PresenceManagerTag;
102
+ Object.defineProperty(exports, 'PresenceManager_exports', {
103
+ enumerable: true,
104
+ get: function () {
105
+ return PresenceManager_exports;
106
+ }
107
+ });
108
+ exports.layer = layer;
@@ -0,0 +1,91 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Layer from "effect/Layer";
3
+ import * as Context from "effect/Context";
4
+ import * as Scope from "effect/Scope";
5
+ import * as Stream from "effect/Stream";
6
+
7
+ //#region src/PresenceManager.d.ts
8
+ declare namespace PresenceManager_d_exports {
9
+ export { PresenceEntry, PresenceEvent, PresenceManager, PresenceManagerTag, PresenceRemoveEvent, PresenceSnapshot, PresenceUpdateEvent, layer, layerDefault };
10
+ }
11
+ /**
12
+ * A presence entry stored in the manager.
13
+ */
14
+ interface PresenceEntry {
15
+ /** The presence data */
16
+ readonly data: unknown;
17
+ /** Optional user ID from authentication */
18
+ readonly userId?: string;
19
+ }
20
+ /**
21
+ * Event emitted when a presence is updated.
22
+ */
23
+ interface PresenceUpdateEvent {
24
+ readonly type: "presence_update";
25
+ /** The connection ID of the user who updated */
26
+ readonly id: string;
27
+ /** The presence data */
28
+ readonly data: unknown;
29
+ /** Optional user ID from authentication */
30
+ readonly userId?: string;
31
+ }
32
+ /**
33
+ * Event emitted when a presence is removed (user disconnected).
34
+ */
35
+ interface PresenceRemoveEvent {
36
+ readonly type: "presence_remove";
37
+ /** The connection ID of the user who disconnected */
38
+ readonly id: string;
39
+ }
40
+ /**
41
+ * Union of all presence events.
42
+ */
43
+ type PresenceEvent = PresenceUpdateEvent | PresenceRemoveEvent;
44
+ /**
45
+ * A snapshot of all presence entries for a document.
46
+ */
47
+ interface PresenceSnapshot {
48
+ /** Map of connectionId to presence entry */
49
+ readonly presences: Record<string, PresenceEntry>;
50
+ }
51
+ /**
52
+ * Service interface for the PresenceManager.
53
+ */
54
+ interface PresenceManager {
55
+ /**
56
+ * Get a snapshot of all presences for a document.
57
+ */
58
+ readonly getSnapshot: (documentId: string) => Effect.Effect<PresenceSnapshot>;
59
+ /**
60
+ * Set/update presence for a connection.
61
+ * Broadcasts the update to all subscribers.
62
+ */
63
+ readonly set: (documentId: string, connectionId: string, entry: PresenceEntry) => Effect.Effect<void>;
64
+ /**
65
+ * Remove presence for a connection (e.g., on disconnect).
66
+ * Broadcasts the removal to all subscribers.
67
+ */
68
+ readonly remove: (documentId: string, connectionId: string) => Effect.Effect<void>;
69
+ /**
70
+ * Subscribe to presence events for a document.
71
+ * Returns a Stream of presence events.
72
+ */
73
+ readonly subscribe: (documentId: string) => Effect.Effect<Stream.Stream<PresenceEvent>, never, Scope.Scope>;
74
+ }
75
+ declare const PresenceManagerTag_base: Context.TagClass<PresenceManagerTag, "@voidhash/mimic-server-effect/PresenceManager", PresenceManager>;
76
+ /**
77
+ * Context tag for PresenceManager.
78
+ */
79
+ declare class PresenceManagerTag extends PresenceManagerTag_base {}
80
+ /**
81
+ * Layer that provides PresenceManager.
82
+ */
83
+ declare const layer: Layer.Layer<PresenceManagerTag>;
84
+ /**
85
+ * Default layer that provides PresenceManager.
86
+ * Uses the default priority for layer composition.
87
+ */
88
+ declare const layerDefault: Layer.Layer<PresenceManagerTag>;
89
+ //#endregion
90
+ export { PresenceManagerTag, PresenceManager_d_exports };
91
+ //# sourceMappingURL=PresenceManager.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresenceManager.d.cts","names":[],"sources":["../src/PresenceManager.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;UAsBiB,aAAA;;;;;;AAAjB;AAcA;AAaA;AASY,UAtBK,mBAAA,CAsBW;EASX,SAAA,IAAA,EAAA,iBAEoB;EAwBpB;EAMI,SAAA,EAAA,EAAA,MAAA;EAAd;EASI,SAAA,IAAA,EAAA,OAAA;EACJ;EASA,SAAO,MAAA,CAAA,EAAA,MAAA;;;;;AAQM,UA7EH,mBAAA,CA6EG;EAKnB,SAAA,IAAA,EAAA,iBAAA;;;;;AAKD;AAoJA;AASa,KA3OD,aAAA,GAAgB,mBA2OW,GA3OW,mBA2OZ;;;;UAlOrB,gBAAA;;sBAEK,eAAe;;;;;UAwBpB,eAAA;;;;gDAMV,MAAA,CAAO,OAAO;;;;;kEASV,kBACJ,MAAA,CAAO;;;;;iEASP,MAAA,CAAO;;;;;8CAQP,MAAA,CAAO,OACV,MAAA,CAAO,OAAO,uBAEd,KAAA,CAAM;;cAET;;;;cAKY,kBAAA,SAA2B,uBAAA;;;;cAoJ3B,OAAO,KAAA,CAAM,MAAM;;;;;cASnB,cAAc,KAAA,CAAM,MAAM"}
@@ -0,0 +1,91 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Layer from "effect/Layer";
3
+ import * as Context from "effect/Context";
4
+ import * as Stream from "effect/Stream";
5
+ import * as Scope from "effect/Scope";
6
+
7
+ //#region src/PresenceManager.d.ts
8
+ declare namespace PresenceManager_d_exports {
9
+ export { PresenceEntry, PresenceEvent, PresenceManager, PresenceManagerTag, PresenceRemoveEvent, PresenceSnapshot, PresenceUpdateEvent, layer, layerDefault };
10
+ }
11
+ /**
12
+ * A presence entry stored in the manager.
13
+ */
14
+ interface PresenceEntry {
15
+ /** The presence data */
16
+ readonly data: unknown;
17
+ /** Optional user ID from authentication */
18
+ readonly userId?: string;
19
+ }
20
+ /**
21
+ * Event emitted when a presence is updated.
22
+ */
23
+ interface PresenceUpdateEvent {
24
+ readonly type: "presence_update";
25
+ /** The connection ID of the user who updated */
26
+ readonly id: string;
27
+ /** The presence data */
28
+ readonly data: unknown;
29
+ /** Optional user ID from authentication */
30
+ readonly userId?: string;
31
+ }
32
+ /**
33
+ * Event emitted when a presence is removed (user disconnected).
34
+ */
35
+ interface PresenceRemoveEvent {
36
+ readonly type: "presence_remove";
37
+ /** The connection ID of the user who disconnected */
38
+ readonly id: string;
39
+ }
40
+ /**
41
+ * Union of all presence events.
42
+ */
43
+ type PresenceEvent = PresenceUpdateEvent | PresenceRemoveEvent;
44
+ /**
45
+ * A snapshot of all presence entries for a document.
46
+ */
47
+ interface PresenceSnapshot {
48
+ /** Map of connectionId to presence entry */
49
+ readonly presences: Record<string, PresenceEntry>;
50
+ }
51
+ /**
52
+ * Service interface for the PresenceManager.
53
+ */
54
+ interface PresenceManager {
55
+ /**
56
+ * Get a snapshot of all presences for a document.
57
+ */
58
+ readonly getSnapshot: (documentId: string) => Effect.Effect<PresenceSnapshot>;
59
+ /**
60
+ * Set/update presence for a connection.
61
+ * Broadcasts the update to all subscribers.
62
+ */
63
+ readonly set: (documentId: string, connectionId: string, entry: PresenceEntry) => Effect.Effect<void>;
64
+ /**
65
+ * Remove presence for a connection (e.g., on disconnect).
66
+ * Broadcasts the removal to all subscribers.
67
+ */
68
+ readonly remove: (documentId: string, connectionId: string) => Effect.Effect<void>;
69
+ /**
70
+ * Subscribe to presence events for a document.
71
+ * Returns a Stream of presence events.
72
+ */
73
+ readonly subscribe: (documentId: string) => Effect.Effect<Stream.Stream<PresenceEvent>, never, Scope.Scope>;
74
+ }
75
+ declare const PresenceManagerTag_base: Context.TagClass<PresenceManagerTag, "@voidhash/mimic-server-effect/PresenceManager", PresenceManager>;
76
+ /**
77
+ * Context tag for PresenceManager.
78
+ */
79
+ declare class PresenceManagerTag extends PresenceManagerTag_base {}
80
+ /**
81
+ * Layer that provides PresenceManager.
82
+ */
83
+ declare const layer: Layer.Layer<PresenceManagerTag>;
84
+ /**
85
+ * Default layer that provides PresenceManager.
86
+ * Uses the default priority for layer composition.
87
+ */
88
+ declare const layerDefault: Layer.Layer<PresenceManagerTag>;
89
+ //#endregion
90
+ export { PresenceManagerTag, PresenceManager_d_exports };
91
+ //# sourceMappingURL=PresenceManager.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresenceManager.d.mts","names":[],"sources":["../src/PresenceManager.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;UAsBiB,aAAA;;;;;;AAAjB;AAcA;AAaA;AASY,UAtBK,mBAAA,CAsBW;EASX,SAAA,IAAA,EAAA,iBAEoB;EAwBpB;EAMI,SAAA,EAAA,EAAA,MAAA;EAAd;EASI,SAAA,IAAA,EAAA,OAAA;EACJ;EASA,SAAO,MAAA,CAAA,EAAA,MAAA;;;;;AAQM,UA7EH,mBAAA,CA6EG;EAKnB,SAAA,IAAA,EAAA,iBAAA;;;;;AAKD;AAoJA;AASa,KA3OD,aAAA,GAAgB,mBA2OW,GA3OW,mBA2OZ;;;;UAlOrB,gBAAA;;sBAEK,eAAe;;;;;UAwBpB,eAAA;;;;gDAMV,MAAA,CAAO,OAAO;;;;;kEASV,kBACJ,MAAA,CAAO;;;;;iEASP,MAAA,CAAO;;;;;8CAQP,MAAA,CAAO,OACV,MAAA,CAAO,OAAO,uBAEd,KAAA,CAAM;;cAET;;;;cAKY,kBAAA,SAA2B,uBAAA;;;;cAoJ3B,OAAO,KAAA,CAAM,MAAM;;;;;cASnB,cAAc,KAAA,CAAM,MAAM"}
@@ -0,0 +1,95 @@
1
+ import { __export } from "./_virtual/rolldown_runtime.mjs";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Context from "effect/Context";
5
+ import * as PubSub from "effect/PubSub";
6
+ import * as Ref from "effect/Ref";
7
+ import * as HashMap from "effect/HashMap";
8
+ import * as Stream from "effect/Stream";
9
+
10
+ //#region src/PresenceManager.ts
11
+ /**
12
+ * @since 0.0.1
13
+ * Presence manager for ephemeral per-connection state.
14
+ * Handles in-memory storage and broadcasting of presence updates.
15
+ */
16
+ var PresenceManager_exports = /* @__PURE__ */ __export({
17
+ PresenceManagerTag: () => PresenceManagerTag,
18
+ layer: () => layer,
19
+ layerDefault: () => layerDefault
20
+ });
21
+ /**
22
+ * Context tag for PresenceManager.
23
+ */
24
+ var PresenceManagerTag = class extends Context.Tag("@voidhash/mimic-server-effect/PresenceManager")() {};
25
+ /**
26
+ * Create the PresenceManager service.
27
+ */
28
+ const makePresenceManager = Effect.gen(function* () {
29
+ const documents = yield* Ref.make(HashMap.empty());
30
+ const getOrCreateDocument = (documentId) => Effect.gen(function* () {
31
+ const current = yield* Ref.get(documents);
32
+ const existing = HashMap.get(current, documentId);
33
+ if (existing._tag === "Some") return existing.value;
34
+ const docPresence = {
35
+ entries: yield* Ref.make(HashMap.empty()),
36
+ pubsub: yield* PubSub.unbounded()
37
+ };
38
+ yield* Ref.update(documents, (map) => HashMap.set(map, documentId, docPresence));
39
+ return docPresence;
40
+ });
41
+ const getSnapshot = (documentId) => Effect.gen(function* () {
42
+ const docPresence = yield* getOrCreateDocument(documentId);
43
+ const entriesMap = yield* Ref.get(docPresence.entries);
44
+ const presences = {};
45
+ for (const [id, entry] of entriesMap) presences[id] = entry;
46
+ return { presences };
47
+ });
48
+ const set = (documentId, connectionId, entry) => Effect.gen(function* () {
49
+ const docPresence = yield* getOrCreateDocument(documentId);
50
+ yield* Ref.update(docPresence.entries, (map) => HashMap.set(map, connectionId, entry));
51
+ yield* PubSub.publish(docPresence.pubsub, {
52
+ type: "presence_update",
53
+ id: connectionId,
54
+ data: entry.data,
55
+ userId: entry.userId
56
+ });
57
+ });
58
+ const remove = (documentId, connectionId) => Effect.gen(function* () {
59
+ const current = yield* Ref.get(documents);
60
+ const existing = HashMap.get(current, documentId);
61
+ if (existing._tag === "None") return;
62
+ const docPresence = existing.value;
63
+ const entries = yield* Ref.get(docPresence.entries);
64
+ if (!HashMap.has(entries, connectionId)) return;
65
+ yield* Ref.update(docPresence.entries, (map) => HashMap.remove(map, connectionId));
66
+ yield* PubSub.publish(docPresence.pubsub, {
67
+ type: "presence_remove",
68
+ id: connectionId
69
+ });
70
+ });
71
+ const subscribe = (documentId) => Effect.gen(function* () {
72
+ const docPresence = yield* getOrCreateDocument(documentId);
73
+ const queue = yield* PubSub.subscribe(docPresence.pubsub);
74
+ return Stream.fromQueue(queue);
75
+ });
76
+ return {
77
+ getSnapshot,
78
+ set,
79
+ remove,
80
+ subscribe
81
+ };
82
+ });
83
+ /**
84
+ * Layer that provides PresenceManager.
85
+ */
86
+ const layer = Layer.effect(PresenceManagerTag, makePresenceManager);
87
+ /**
88
+ * Default layer that provides PresenceManager.
89
+ * Uses the default priority for layer composition.
90
+ */
91
+ const layerDefault = Layer.effectDiscard(Effect.succeed(void 0)).pipe(Layer.provideMerge(layer));
92
+
93
+ //#endregion
94
+ export { PresenceManagerTag, PresenceManager_exports, layer };
95
+ //# sourceMappingURL=PresenceManager.mjs.map