@voidhash/mimic-effect 0.0.3 → 0.0.5

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.
@@ -1,15 +1,13 @@
1
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";
2
+ import { MimicServerConfigTag, layer } from "./MimicConfig.mjs";
3
+ import { DocumentManagerTag, layer as layer$1 } from "./DocumentManager.mjs";
4
4
  import { MimicAuthServiceTag } from "./MimicAuthService.mjs";
5
- import { PresenceManagerTag, layer as layer$3 } from "./PresenceManager.mjs";
6
- import { extractDocumentId, handleConnection, makeHandler } from "./WebSocketHandler.mjs";
5
+ import { PresenceManagerTag, layer as layer$2 } from "./PresenceManager.mjs";
6
+ import { extractDocumentId, handleConnection } from "./WebSocketHandler.mjs";
7
7
  import { layerDefault } from "./storage/InMemoryDataStorage.mjs";
8
8
  import { layerDefault as layerDefault$1 } from "./auth/NoAuth.mjs";
9
9
  import * as Effect from "effect/Effect";
10
10
  import * as Layer from "effect/Layer";
11
- import * as Context from "effect/Context";
12
- import { SocketServer } from "@effect/platform/SocketServer";
13
11
  import { HttpLayerRouter, HttpServerRequest, HttpServerResponse } from "@effect/platform";
14
12
 
15
13
  //#region src/MimicServer.ts
@@ -18,119 +16,13 @@ import { HttpLayerRouter, HttpServerRequest, HttpServerResponse } from "@effect/
18
16
  * Mimic server layer composition.
19
17
  */
20
18
  var MimicServer_exports = /* @__PURE__ */ __export({
21
- MimicWebSocketHandler: () => MimicWebSocketHandler,
22
19
  documentManagerLayer: () => documentManagerLayer,
23
- handlerLayer: () => handlerLayer,
24
- layer: () => layer,
25
- layerHttpLayerRouter: () => layerHttpLayerRouter,
26
- run: () => run
20
+ layerHttpLayerRouter: () => layerHttpLayerRouter
27
21
  });
28
22
  /**
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
23
  * Create the document manager layer.
116
24
  */
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
- });
25
+ const documentManagerLayer = (options) => layer$1.pipe(Layer.provide(layer(options)), Layer.provide(layerDefault), Layer.provide(layerDefault$1));
134
26
  /**
135
27
  * Create the HTTP handler effect for WebSocket upgrade.
136
28
  * This handler:
@@ -200,22 +92,26 @@ const makeMimicHandler = Effect.gen(function* () {
200
92
  * );
201
93
  * ```
202
94
  */
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));
95
+ const layerHttpLayerRouter = (optionsEf) => {
96
+ return Layer.unwrapScoped(Effect.gen(function* () {
97
+ var _options$basePath, _options$authLayer, _options$storageLayer;
98
+ const options = yield* optionsEf;
99
+ const wsPath = `${(_options$basePath = options.basePath) !== null && _options$basePath !== void 0 ? _options$basePath : "/mimic"}/doc/*`;
100
+ const configLayer = layer({
101
+ schema: options.schema,
102
+ maxTransactionHistory: options.maxTransactionHistory,
103
+ presence: options.presence,
104
+ initial: options.initial
105
+ });
106
+ const authLayer = (_options$authLayer = options.authLayer) !== null && _options$authLayer !== void 0 ? _options$authLayer : layerDefault$1;
107
+ const storageLayer = (_options$storageLayer = options.storageLayer) !== null && _options$storageLayer !== void 0 ? _options$storageLayer : layerDefault;
108
+ const depsLayer = Layer.mergeAll(configLayer, authLayer, storageLayer);
109
+ return Layer.scopedDiscard(Effect.gen(function* () {
110
+ const router = yield* HttpLayerRouter.HttpRouter;
111
+ const handler = yield* makeMimicHandler;
112
+ yield* router.add("GET", wsPath, handler);
113
+ })).pipe(Layer.provide(layer$1), Layer.provide(layer$2), Layer.provide(depsLayer));
114
+ }));
219
115
  };
220
116
 
221
117
  //#endregion
@@ -1 +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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"}
1
+ {"version":3,"file":"MimicServer.mjs","names":["MimicConfig.layer","InMemoryDataStorage.layerDefault","NoAuth.layerDefault","MimicConfig.MimicServerConfigTag","DocumentManager.DocumentManagerTag","PresenceManager.PresenceManagerTag","WebSocketHandler.extractDocumentId","WebSocketHandler.handleConnection","wsPath: PathInput","DocumentManager.layer","PresenceManager.layer"],"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// Layer Composition Options\n// =============================================================================\n\n/**\n * Options for creating a Mimic server layer.\n */\nexport interface MimicLayerOptions<\n TSchema extends Primitive.AnyPrimitive,\n> {\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 * Initial state for new documents.\n * Can be either:\n * - A plain object with the initial state values\n * - A function that receives context (with documentId) and returns an Effect producing the initial state\n *\n * When using a function that requires Effect services (has R requirements),\n * you must also provide `initialLayer` to supply those dependencies.\n *\n * Type-safe: required fields (without defaults) must be provided,\n * while optional fields and fields with defaults can be omitted.\n *\n * @default undefined (documents start empty or use schema defaults)\n */\n readonly initial?: Primitive.InferSetInput<TSchema> | MimicConfig.InitialFn<TSchema>;\n}\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 * 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 * Options for layerHttpLayerRouter including optional custom layers.\n */\nexport interface MimicLayerRouterOptions<TSchema extends Primitive.AnyPrimitive>\n extends 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/**\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 = <\n TSchema extends Primitive.AnyPrimitive,\n TError,\n TRequirements\n>(\n optionsEf: Effect.Effect<MimicLayerRouterOptions<TSchema>, TError, TRequirements>\n): Layer.Layer<never, TError, TRequirements | HttpLayerRouter.HttpRouter> => {\n return Layer.unwrapScoped(\n Effect.gen(function* () {\n const options = yield* optionsEf;\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 with properly typed initial function\n const configLayer = MimicConfig.layer<TSchema>({\n schema: options.schema,\n maxTransactionHistory: options.maxTransactionHistory,\n presence: options.presence,\n initial: options.initial,\n });\n\n // Use provided layers or defaults\n const authLayer = options.authLayer ?? NoAuth.layerDefault;\n const storageLayer = options.storageLayer ?? InMemoryDataStorage.layerDefault;\n\n // Combine all dependency layers\n const depsLayer = Layer.mergeAll(configLayer, authLayer, storageLayer);\n\n // Create the route registration layer\n const routeLayer = Layer.scopedDiscard(\n Effect.gen(function* () {\n const router = yield* HttpLayerRouter.HttpRouter;\n const handler = yield* makeMimicHandler;\n yield* router.add(\"GET\", wsPath, handler);\n })\n );\n\n // Build the complete layer with all dependencies provided\n return routeLayer.pipe(\n Layer.provide(DocumentManager.layer),\n Layer.provide(PresenceManager.layer),\n Layer.provide(depsLayer),\n );\n })\n );\n};"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwEA,MAAa,wBACX,oBAEsB,KACpB,MAAM,QAAQA,MAAkB,QAAQ,CAAC,EAEzC,MAAM,QAAQC,aAAiC,EAC/C,MAAM,QAAQC,eAAoB,CACnC;;;;;;;;AASH,MAAM,mBAAmB,OAAO,IAAI,aAAa;CAC/C,MAAM,SAAS,OAAOC;CACtB,MAAM,cAAc,OAAO;CAC3B,MAAM,kBAAkB,OAAOC;CAC/B,MAAM,kBAAkB,OAAOC;AAE/B,QAAO,OAAO,IAAI,aAAa;EAC7B,MAAM,UAAU,OAAO,kBAAkB;AAItB,SAAOC,kBAAmC,QAAQ,IAAI;EAGzE,MAAM,SAAS,OAAO,QAAQ;AAG9B,SAAOC,iBAAkC,QAAQ,QAAQ,IAAI,CAAC,KAC5D,OAAO,eAAeJ,sBAAkC,OAAO,EAC/D,OAAO,eAAe,qBAAqB,YAAY,EACvD,OAAO,eAAeC,oBAAoC,gBAAgB,EAC1E,OAAO,eAAeC,oBAAoC,gBAAgB,EAC1E,OAAO,QACP,OAAO,UAAU,UACf,OAAO,SAAS,8BAA8B,MAAM,CACrD,CACF;AAGD,SAAO,mBAAmB,OAAO;GACjC,CAAC,KACD,OAAO,UAAU,UACf,OAAO,IAAI,aAAa;AACtB,SAAO,OAAO,WAAW,4BAA4B,MAAM;AAC3D,SAAO,mBAAmB,KAAK,4BAA4B,EACzD,QAAQ,KACT,CAAC;GACF,CACH,CACF;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DF,MAAa,wBAKX,cAC2E;AAC3E,QAAO,MAAM,aACX,OAAO,IAAI,aAAa;;EACtB,MAAM,UAAU,OAAO;EAKvB,MAAMG,SAAoB,wBADT,QAAQ,yEAAY,SACC;EAGtC,MAAM,cAAcR,MAA2B;GAC7C,QAAQ,QAAQ;GAChB,uBAAuB,QAAQ;GAC/B,UAAU,QAAQ;GAClB,SAAS,QAAQ;GAClB,CAAC;EAGF,MAAM,kCAAY,QAAQ,4EAAaE;EACvC,MAAM,wCAAe,QAAQ,qFAAgBD;EAG7C,MAAM,YAAY,MAAM,SAAS,aAAa,WAAW,aAAa;AAYtE,SATmB,MAAM,cACvB,OAAO,IAAI,aAAa;GACtB,MAAM,SAAS,OAAO,gBAAgB;GACtC,MAAM,UAAU,OAAO;AACvB,UAAO,OAAO,IAAI,OAAO,QAAQ,QAAQ;IACzC,CACH,CAGiB,KAChB,MAAM,QAAQQ,QAAsB,EACpC,MAAM,QAAQC,QAAsB,EACpC,MAAM,QAAQ,UAAU,CACzB;GACD,CACH"}
@@ -3,14 +3,14 @@ let effect_Effect = require("effect/Effect");
3
3
  effect_Effect = require_rolldown_runtime.__toESM(effect_Effect);
4
4
  let effect_Layer = require("effect/Layer");
5
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
6
  let effect_PubSub = require("effect/PubSub");
9
7
  effect_PubSub = require_rolldown_runtime.__toESM(effect_PubSub);
10
8
  let effect_Ref = require("effect/Ref");
11
9
  effect_Ref = require_rolldown_runtime.__toESM(effect_Ref);
12
10
  let effect_HashMap = require("effect/HashMap");
13
11
  effect_HashMap = require_rolldown_runtime.__toESM(effect_HashMap);
12
+ let effect_Context = require("effect/Context");
13
+ effect_Context = require_rolldown_runtime.__toESM(effect_Context);
14
14
  let effect_Stream = require("effect/Stream");
15
15
  effect_Stream = require_rolldown_runtime.__toESM(effect_Stream);
16
16
 
@@ -1,10 +1,10 @@
1
1
  import { __export } from "./_virtual/rolldown_runtime.mjs";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Layer from "effect/Layer";
4
- import * as Context from "effect/Context";
5
4
  import * as PubSub from "effect/PubSub";
6
5
  import * as Ref from "effect/Ref";
7
6
  import * as HashMap from "effect/HashMap";
7
+ import * as Context from "effect/Context";
8
8
  import * as Stream from "effect/Stream";
9
9
 
10
10
  //#region src/PresenceManager.ts
@@ -11,9 +11,9 @@ let effect_Stream = require("effect/Stream");
11
11
  effect_Stream = require_rolldown_runtime.__toESM(effect_Stream);
12
12
  let effect_Duration = require("effect/Duration");
13
13
  effect_Duration = require_rolldown_runtime.__toESM(effect_Duration);
14
+ let _voidhash_mimic = require("@voidhash/mimic");
14
15
  let effect_Fiber = require("effect/Fiber");
15
16
  effect_Fiber = require_rolldown_runtime.__toESM(effect_Fiber);
16
- let _voidhash_mimic = require("@voidhash/mimic");
17
17
 
18
18
  //#region src/WebSocketHandler.ts
19
19
  /**
@@ -362,5 +362,4 @@ Object.defineProperty(exports, 'WebSocketHandler_exports', {
362
362
  }
363
363
  });
364
364
  exports.extractDocumentId = extractDocumentId;
365
- exports.handleConnection = handleConnection;
366
- exports.makeHandler = makeHandler;
365
+ exports.handleConnection = handleConnection;
@@ -4,8 +4,8 @@ import { MimicAuthServiceTag } from "./MimicAuthService.cjs";
4
4
  import { PresenceManagerTag } from "./PresenceManager.cjs";
5
5
  import { MessageParseError, MissingDocumentIdError } from "./errors.cjs";
6
6
  import * as Effect from "effect/Effect";
7
- import * as Socket from "@effect/platform/Socket";
8
7
  import * as Scope from "effect/Scope";
8
+ import * as Socket from "@effect/platform/Socket";
9
9
 
10
10
  //#region src/WebSocketHandler.d.ts
11
11
  declare namespace WebSocketHandler_d_exports {
@@ -4,8 +4,8 @@ import { MimicAuthServiceTag } from "./MimicAuthService.mjs";
4
4
  import { PresenceManagerTag } from "./PresenceManager.mjs";
5
5
  import { MessageParseError, MissingDocumentIdError } from "./errors.mjs";
6
6
  import * as Effect from "effect/Effect";
7
- import * as Socket from "@effect/platform/Socket";
8
7
  import * as Scope from "effect/Scope";
8
+ import * as Socket from "@effect/platform/Socket";
9
9
 
10
10
  //#region src/WebSocketHandler.d.ts
11
11
  declare namespace WebSocketHandler_d_exports {
@@ -8,8 +8,8 @@ import { _objectSpread2 } from "./_virtual/_@oxc-project_runtime@0.103.0/helpers
8
8
  import * as Effect from "effect/Effect";
9
9
  import * as Stream from "effect/Stream";
10
10
  import * as Duration from "effect/Duration";
11
- import * as Fiber from "effect/Fiber";
12
11
  import { Presence, Transaction } from "@voidhash/mimic";
12
+ import * as Fiber from "effect/Fiber";
13
13
 
14
14
  //#region src/WebSocketHandler.ts
15
15
  /**
@@ -351,5 +351,5 @@ const handleConnectionWithDocumentId = (socket, documentId) => Effect.gen(functi
351
351
  });
352
352
 
353
353
  //#endregion
354
- export { WebSocketHandler_exports, extractDocumentId, handleConnection, makeHandler };
354
+ export { WebSocketHandler_exports, extractDocumentId, handleConnection };
355
355
  //# sourceMappingURL=WebSocketHandler.mjs.map
package/dist/errors.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import * as effect_Types0 from "effect/Types";
2
- import * as effect_Cause0 from "effect/Cause";
1
+ import * as effect_Types2 from "effect/Types";
2
+ import * as effect_Cause2 from "effect/Cause";
3
3
 
4
4
  //#region src/errors.d.ts
5
- declare const DocumentTypeNotFoundError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
5
+ declare const DocumentTypeNotFoundError_base: new <A extends Record<string, any> = {}>(args: effect_Types2.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause2.YieldableError & {
6
6
  readonly _tag: "DocumentTypeNotFoundError";
7
7
  } & Readonly<A>;
8
8
  /**
@@ -13,7 +13,7 @@ declare class DocumentTypeNotFoundError extends DocumentTypeNotFoundError_base<{
13
13
  }> {
14
14
  get message(): string;
15
15
  }
16
- declare const DocumentNotFoundError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
16
+ declare const DocumentNotFoundError_base: new <A extends Record<string, any> = {}>(args: effect_Types2.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause2.YieldableError & {
17
17
  readonly _tag: "DocumentNotFoundError";
18
18
  } & Readonly<A>;
19
19
  /**
@@ -24,7 +24,7 @@ declare class DocumentNotFoundError extends DocumentNotFoundError_base<{
24
24
  }> {
25
25
  get message(): string;
26
26
  }
27
- declare const AuthenticationError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
27
+ declare const AuthenticationError_base: new <A extends Record<string, any> = {}>(args: effect_Types2.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause2.YieldableError & {
28
28
  readonly _tag: "AuthenticationError";
29
29
  } & Readonly<A>;
30
30
  /**
@@ -35,7 +35,7 @@ declare class AuthenticationError extends AuthenticationError_base<{
35
35
  }> {
36
36
  get message(): string;
37
37
  }
38
- declare const TransactionRejectedError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
38
+ declare const TransactionRejectedError_base: new <A extends Record<string, any> = {}>(args: effect_Types2.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause2.YieldableError & {
39
39
  readonly _tag: "TransactionRejectedError";
40
40
  } & Readonly<A>;
41
41
  /**
@@ -47,7 +47,7 @@ declare class TransactionRejectedError extends TransactionRejectedError_base<{
47
47
  }> {
48
48
  get message(): string;
49
49
  }
50
- declare const MessageParseError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
50
+ declare const MessageParseError_base: new <A extends Record<string, any> = {}>(args: effect_Types2.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause2.YieldableError & {
51
51
  readonly _tag: "MessageParseError";
52
52
  } & Readonly<A>;
53
53
  /**
@@ -58,7 +58,7 @@ declare class MessageParseError extends MessageParseError_base<{
58
58
  }> {
59
59
  get message(): string;
60
60
  }
61
- declare const InvalidConnectionError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
61
+ declare const InvalidConnectionError_base: new <A extends Record<string, any> = {}>(args: effect_Types2.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause2.YieldableError & {
62
62
  readonly _tag: "InvalidConnectionError";
63
63
  } & Readonly<A>;
64
64
  /**
@@ -69,7 +69,7 @@ declare class InvalidConnectionError extends InvalidConnectionError_base<{
69
69
  }> {
70
70
  get message(): string;
71
71
  }
72
- declare const MissingDocumentIdError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
72
+ declare const MissingDocumentIdError_base: new <A extends Record<string, any> = {}>(args: effect_Types2.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause2.YieldableError & {
73
73
  readonly _tag: "MissingDocumentIdError";
74
74
  } & Readonly<A>;
75
75
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidhash/mimic-effect",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,11 +24,11 @@
24
24
  "typescript": "5.8.3",
25
25
  "vite-tsconfig-paths": "^5.1.4",
26
26
  "vitest": "^3.2.4",
27
- "@voidhash/tsconfig": "0.0.3"
27
+ "@voidhash/tsconfig": "0.0.5"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "effect": "^3.19.12",
31
- "@voidhash/mimic": "0.0.3"
31
+ "@voidhash/mimic": "0.0.5"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsdown",
@@ -113,10 +113,12 @@ const makeDocumentManager = Effect.gen(function* () {
113
113
  () => Effect.succeed(undefined)
114
114
  );
115
115
 
116
- // Transform loaded state with onLoad hook
116
+ // Transform loaded state with onLoad hook, or compute initial state for new docs
117
117
  const initialState = rawState !== undefined
118
118
  ? yield* storage.onLoad(rawState)
119
- : undefined;
119
+ : config.initial !== undefined
120
+ ? yield* config.initial({ documentId })
121
+ : undefined;
120
122
 
121
123
  // Create PubSub for broadcasting
122
124
  const pubsub = yield* PubSub.unbounded<Protocol.ServerBroadcast>();
@@ -124,7 +126,7 @@ const makeDocumentManager = Effect.gen(function* () {
124
126
  // Create ServerDocument with broadcast callback
125
127
  const serverDocument = ServerDocument.make({
126
128
  schema: config.schema,
127
- initialState: initialState as Primitive.InferState<typeof config.schema> | undefined,
129
+ initialState: initialState as Primitive.InferSetInput<typeof config.schema> | undefined,
128
130
  maxTransactionHistory: config.maxTransactionHistory,
129
131
  onBroadcast: (transactionMessage) => {
130
132
  // Get current state and save to storage
@@ -5,8 +5,31 @@
5
5
  import * as Context from "effect/Context";
6
6
  import * as Duration from "effect/Duration";
7
7
  import type { DurationInput } from "effect/Duration";
8
+ import * as Effect from "effect/Effect";
8
9
  import * as Layer from "effect/Layer";
9
- import type { Primitive, Presence } from "@voidhash/mimic";
10
+ import { Primitive, Presence } from "@voidhash/mimic";
11
+
12
+ // =============================================================================
13
+ // Initial State Types
14
+ // =============================================================================
15
+
16
+ /**
17
+ * Context available when computing initial state for a document.
18
+ */
19
+ export interface InitialContext {
20
+ /**
21
+ * The document ID being initialized.
22
+ */
23
+ readonly documentId: string;
24
+ }
25
+
26
+ /**
27
+ * Function that computes initial state for a document.
28
+ * Receives context with the document ID and returns an Effect that produces the initial state.
29
+ */
30
+ export type InitialFn<TSchema extends Primitive.AnyPrimitive> = (
31
+ context: InitialContext
32
+ ) => Effect.Effect<Primitive.InferSetInput<TSchema>>;
10
33
 
11
34
  // =============================================================================
12
35
  // Mimic Server Configuration
@@ -18,7 +41,9 @@ import type { Primitive, Presence } from "@voidhash/mimic";
18
41
  * Note: Authentication and persistence are now handled by injectable services
19
42
  * (MimicAuthService and MimicDataStorage) rather than config options.
20
43
  */
21
- export interface MimicServerConfig<TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> {
44
+ export interface MimicServerConfig<
45
+ TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive,
46
+ > {
22
47
  /**
23
48
  * The schema defining the document structure.
24
49
  */
@@ -54,12 +79,22 @@ export interface MimicServerConfig<TSchema extends Primitive.AnyPrimitive = Prim
54
79
  * @default undefined (presence disabled)
55
80
  */
56
81
  readonly presence: Presence.AnyPresence | undefined;
82
+
83
+ /**
84
+ * Initial state function for new documents.
85
+ * Called when a document is created and no existing state is found in storage.
86
+ * Receives the document ID and returns an Effect that produces the initial state.
87
+ * @default undefined (documents start empty or use schema defaults)
88
+ */
89
+ readonly initial: InitialFn<TSchema> | undefined;
57
90
  }
58
91
 
59
92
  /**
60
93
  * Options for creating a MimicServerConfig.
61
94
  */
62
- export interface MimicServerConfigOptions<TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> {
95
+ export interface MimicServerConfigOptions<
96
+ TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive,
97
+ > {
63
98
  /**
64
99
  * The schema defining the document structure.
65
100
  */
@@ -95,21 +130,66 @@ export interface MimicServerConfigOptions<TSchema extends Primitive.AnyPrimitive
95
130
  * @default undefined (presence disabled)
96
131
  */
97
132
  readonly presence?: Presence.AnyPresence;
133
+
134
+ /**
135
+ * Initial state for new documents.
136
+ * Can be either:
137
+ * - A plain object with the initial state values
138
+ * - A function that receives context (with documentId) and returns an Effect producing the initial state
139
+ *
140
+ * Type-safe: required fields (without defaults) must be provided,
141
+ * while optional fields and fields with defaults can be omitted.
142
+ *
143
+ * @example
144
+ * // Plain object
145
+ * initial: { title: "New Document", count: 0 }
146
+ *
147
+ * @example
148
+ * // Function returning Effect
149
+ * initial: ({ documentId }) => Effect.succeed({ title: `Doc ${documentId}`, count: 0 })
150
+ *
151
+ * @default undefined (documents start empty or use schema defaults)
152
+ */
153
+ readonly initial?: Primitive.InferSetInput<TSchema> | InitialFn<TSchema>;
98
154
  }
99
155
 
156
+ /**
157
+ * Check if a value is an InitialFn (function) rather than a plain object.
158
+ */
159
+ const isInitialFn = <TSchema extends Primitive.AnyPrimitive>(
160
+ value: Primitive.InferSetInput<TSchema> | InitialFn<TSchema> | undefined
161
+ ): value is InitialFn<TSchema> => typeof value === "function";
162
+
100
163
  /**
101
164
  * Create a MimicServerConfig from options.
102
165
  */
103
166
  export const make = <TSchema extends Primitive.AnyPrimitive>(
104
167
  options: MimicServerConfigOptions<TSchema>
105
- ): MimicServerConfig<TSchema> => ({
106
- schema: options.schema,
107
- maxIdleTime: Duration.decode(options.maxIdleTime ?? "5 minutes"),
108
- maxTransactionHistory: options.maxTransactionHistory ?? 1000,
109
- heartbeatInterval: Duration.decode(options.heartbeatInterval ?? "30 seconds"),
110
- heartbeatTimeout: Duration.decode(options.heartbeatTimeout ?? "10 seconds"),
111
- presence: options.presence,
112
- });
168
+ ): MimicServerConfig<TSchema> => {
169
+ const { initial, schema } = options;
170
+
171
+ // Convert initial to a function that applies defaults
172
+ const initialFn: InitialFn<TSchema> | undefined = initial === undefined
173
+ ? undefined
174
+ : isInitialFn<TSchema>(initial)
175
+ ? (context) => Effect.map(
176
+ initial(context),
177
+ (state) => Primitive.applyDefaults(schema, state as Partial<Primitive.InferState<TSchema>>)
178
+ ) as Effect.Effect<Primitive.InferSetInput<TSchema>>
179
+ : () => Effect.succeed(
180
+ Primitive.applyDefaults(schema, initial as Partial<Primitive.InferState<TSchema>>)
181
+ ) as Effect.Effect<Primitive.InferSetInput<TSchema>>;
182
+
183
+ return {
184
+ schema,
185
+ maxIdleTime: Duration.decode(options.maxIdleTime ?? "5 minutes"),
186
+ maxTransactionHistory: options.maxTransactionHistory ?? 1000,
187
+ heartbeatInterval: Duration.decode(options.heartbeatInterval ?? "30 seconds"),
188
+ heartbeatTimeout: Duration.decode(options.heartbeatTimeout ?? "10 seconds"),
189
+ presence: options.presence,
190
+ initial: initialFn,
191
+ };
192
+ };
113
193
 
114
194
  // =============================================================================
115
195
  // Context Tag
@@ -128,4 +208,4 @@ export class MimicServerConfigTag extends Context.Tag(
128
208
  export const layer = <TSchema extends Primitive.AnyPrimitive>(
129
209
  options: MimicServerConfigOptions<TSchema>
130
210
  ): Layer.Layer<MimicServerConfigTag> =>
131
- Layer.succeed(MimicServerConfigTag, make(options));
211
+ Layer.succeed(MimicServerConfigTag, make(options) as unknown as MimicServerConfig);