@voidhash/mimic-effect 0.0.2 → 0.0.4

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 (105) 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 +52 -0
  24. package/dist/MimicConfig.d.cts +115 -0
  25. package/dist/MimicConfig.d.cts.map +1 -0
  26. package/dist/MimicConfig.d.mts +115 -0
  27. package/dist/MimicConfig.d.mts.map +1 -0
  28. package/dist/MimicConfig.mjs +43 -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 +122 -0
  38. package/dist/MimicServer.d.cts +106 -0
  39. package/dist/MimicServer.d.cts.map +1 -0
  40. package/dist/MimicServer.d.mts +106 -0
  41. package/dist/MimicServer.d.mts.map +1 -0
  42. package/dist/MimicServer.mjs +116 -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 +365 -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/DocumentManager.ts +2 -2
  97. package/src/MimicConfig.ts +22 -1
  98. package/src/MimicServer.ts +11 -161
  99. package/tests/DocumentManager.test.ts +61 -0
  100. package/tests/MimicConfig.test.ts +72 -0
  101. package/tests/MimicServer.test.ts +55 -162
  102. package/tsdown.config.ts +1 -1
  103. package/dist/index.d.cts.map +0 -1
  104. package/dist/index.d.mts.map +0 -1
  105. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,57 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ const require_MimicDataStorage = require('../MimicDataStorage.cjs');
3
+ let effect_Effect = require("effect/Effect");
4
+ effect_Effect = require_rolldown_runtime.__toESM(effect_Effect);
5
+ let effect_Layer = require("effect/Layer");
6
+ effect_Layer = require_rolldown_runtime.__toESM(effect_Layer);
7
+ let effect_Ref = require("effect/Ref");
8
+ effect_Ref = require_rolldown_runtime.__toESM(effect_Ref);
9
+ let effect_HashMap = require("effect/HashMap");
10
+ effect_HashMap = require_rolldown_runtime.__toESM(effect_HashMap);
11
+
12
+ //#region src/storage/InMemoryDataStorage.ts
13
+ /**
14
+ * @since 0.0.1
15
+ * In-memory data storage implementation for Mimic documents.
16
+ * Provides ephemeral storage - data is lost when the server restarts.
17
+ */
18
+ var InMemoryDataStorage_exports = /* @__PURE__ */ require_rolldown_runtime.__export({
19
+ layer: () => layer,
20
+ layerDefault: () => layerDefault
21
+ });
22
+ /**
23
+ * Create an in-memory storage service.
24
+ * Uses a HashMap to store documents in memory.
25
+ */
26
+ const makeInMemoryStorage = effect_Effect.gen(function* () {
27
+ const store = yield* effect_Ref.make(effect_HashMap.empty());
28
+ return {
29
+ load: (documentId) => effect_Effect.gen(function* () {
30
+ const current = yield* effect_Ref.get(store);
31
+ const result = effect_HashMap.get(current, documentId);
32
+ return result._tag === "Some" ? result.value : void 0;
33
+ }),
34
+ save: (documentId, state) => effect_Ref.update(store, (map) => effect_HashMap.set(map, documentId, state)),
35
+ delete: (documentId) => effect_Ref.update(store, (map) => effect_HashMap.remove(map, documentId)),
36
+ onLoad: (state) => effect_Effect.succeed(state),
37
+ onSave: (state) => effect_Effect.succeed(state)
38
+ };
39
+ });
40
+ /**
41
+ * Layer that provides in-memory data storage.
42
+ * This is the default storage implementation - ephemeral and non-persistent.
43
+ */
44
+ const layer = effect_Layer.effect(require_MimicDataStorage.MimicDataStorageTag, makeInMemoryStorage);
45
+ /**
46
+ * Default layer alias for convenience.
47
+ */
48
+ const layerDefault = layer;
49
+
50
+ //#endregion
51
+ Object.defineProperty(exports, 'InMemoryDataStorage_exports', {
52
+ enumerable: true,
53
+ get: function () {
54
+ return InMemoryDataStorage_exports;
55
+ }
56
+ });
57
+ exports.layerDefault = layerDefault;
@@ -0,0 +1,19 @@
1
+ import { MimicDataStorageTag } from "../MimicDataStorage.cjs";
2
+ import * as Layer from "effect/Layer";
3
+
4
+ //#region src/storage/InMemoryDataStorage.d.ts
5
+ declare namespace InMemoryDataStorage_d_exports {
6
+ export { layer, layerDefault };
7
+ }
8
+ /**
9
+ * Layer that provides in-memory data storage.
10
+ * This is the default storage implementation - ephemeral and non-persistent.
11
+ */
12
+ declare const layer: Layer.Layer<MimicDataStorageTag>;
13
+ /**
14
+ * Default layer alias for convenience.
15
+ */
16
+ declare const layerDefault: Layer.Layer<MimicDataStorageTag, never, never>;
17
+ //#endregion
18
+ export { InMemoryDataStorage_d_exports };
19
+ //# sourceMappingURL=InMemoryDataStorage.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InMemoryDataStorage.d.cts","names":[],"sources":["../../src/storage/InMemoryDataStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAyDa,OAAO,KAAA,CAAM,MAAM;;;AAAhC;AAQa,cAAA,YAAY,EAAA,KAAA,CAAA,KAAA,CAAA,mBAAA,EAAA,KAAA,EAAA,KAAA,CAAA"}
@@ -0,0 +1,19 @@
1
+ import { MimicDataStorageTag } from "../MimicDataStorage.mjs";
2
+ import * as Layer from "effect/Layer";
3
+
4
+ //#region src/storage/InMemoryDataStorage.d.ts
5
+ declare namespace InMemoryDataStorage_d_exports {
6
+ export { layer, layerDefault };
7
+ }
8
+ /**
9
+ * Layer that provides in-memory data storage.
10
+ * This is the default storage implementation - ephemeral and non-persistent.
11
+ */
12
+ declare const layer: Layer.Layer<MimicDataStorageTag>;
13
+ /**
14
+ * Default layer alias for convenience.
15
+ */
16
+ declare const layerDefault: Layer.Layer<MimicDataStorageTag, never, never>;
17
+ //#endregion
18
+ export { InMemoryDataStorage_d_exports };
19
+ //# sourceMappingURL=InMemoryDataStorage.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InMemoryDataStorage.d.mts","names":[],"sources":["../../src/storage/InMemoryDataStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAyDa,OAAO,KAAA,CAAM,MAAM;;;AAAhC;AAQa,cAAA,YAAY,EAAA,KAAA,CAAA,KAAA,CAAA,mBAAA,EAAA,KAAA,EAAA,KAAA,CAAA"}
@@ -0,0 +1,48 @@
1
+ import { __export } from "../_virtual/rolldown_runtime.mjs";
2
+ import { MimicDataStorageTag } from "../MimicDataStorage.mjs";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Ref from "effect/Ref";
6
+ import * as HashMap from "effect/HashMap";
7
+
8
+ //#region src/storage/InMemoryDataStorage.ts
9
+ /**
10
+ * @since 0.0.1
11
+ * In-memory data storage implementation for Mimic documents.
12
+ * Provides ephemeral storage - data is lost when the server restarts.
13
+ */
14
+ var InMemoryDataStorage_exports = /* @__PURE__ */ __export({
15
+ layer: () => layer,
16
+ layerDefault: () => layerDefault
17
+ });
18
+ /**
19
+ * Create an in-memory storage service.
20
+ * Uses a HashMap to store documents in memory.
21
+ */
22
+ const makeInMemoryStorage = Effect.gen(function* () {
23
+ const store = yield* Ref.make(HashMap.empty());
24
+ return {
25
+ load: (documentId) => Effect.gen(function* () {
26
+ const current = yield* Ref.get(store);
27
+ const result = HashMap.get(current, documentId);
28
+ return result._tag === "Some" ? result.value : void 0;
29
+ }),
30
+ save: (documentId, state) => Ref.update(store, (map) => HashMap.set(map, documentId, state)),
31
+ delete: (documentId) => Ref.update(store, (map) => HashMap.remove(map, documentId)),
32
+ onLoad: (state) => Effect.succeed(state),
33
+ onSave: (state) => Effect.succeed(state)
34
+ };
35
+ });
36
+ /**
37
+ * Layer that provides in-memory data storage.
38
+ * This is the default storage implementation - ephemeral and non-persistent.
39
+ */
40
+ const layer = Layer.effect(MimicDataStorageTag, makeInMemoryStorage);
41
+ /**
42
+ * Default layer alias for convenience.
43
+ */
44
+ const layerDefault = layer;
45
+
46
+ //#endregion
47
+ export { InMemoryDataStorage_exports, layerDefault };
48
+ //# sourceMappingURL=InMemoryDataStorage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InMemoryDataStorage.mjs","names":["layer: Layer.Layer<MimicDataStorageTag>"],"sources":["../../src/storage/InMemoryDataStorage.ts"],"sourcesContent":["/**\n * @since 0.0.1\n * In-memory data storage implementation for Mimic documents.\n * Provides ephemeral storage - data is lost when the server restarts.\n */\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport * as Ref from \"effect/Ref\";\nimport * as HashMap from \"effect/HashMap\";\n\nimport {\n MimicDataStorageTag,\n type MimicDataStorage,\n} from \"../MimicDataStorage.js\";\n\n// =============================================================================\n// In-Memory Storage Implementation\n// =============================================================================\n\n/**\n * Create an in-memory storage service.\n * Uses a HashMap to store documents in memory.\n */\nconst makeInMemoryStorage = Effect.gen(function* () {\n // Create a mutable reference to a HashMap for storing documents\n const store = yield* Ref.make(HashMap.empty<string, unknown>());\n\n const storage: MimicDataStorage = {\n load: (documentId: string) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const result = HashMap.get(current, documentId);\n return result._tag === \"Some\" ? result.value : undefined;\n }),\n\n save: (documentId: string, state: unknown) =>\n Ref.update(store, (map) => HashMap.set(map, documentId, state)),\n\n delete: (documentId: string) =>\n Ref.update(store, (map) => HashMap.remove(map, documentId)),\n\n onLoad: (state: unknown) => Effect.succeed(state),\n\n onSave: (state: unknown) => Effect.succeed(state),\n };\n\n return storage;\n});\n\n// =============================================================================\n// Layer\n// =============================================================================\n\n/**\n * Layer that provides in-memory data storage.\n * This is the default storage implementation - ephemeral and non-persistent.\n */\nexport const layer: Layer.Layer<MimicDataStorageTag> = Layer.effect(\n MimicDataStorageTag,\n makeInMemoryStorage\n);\n\n/**\n * Default layer alias for convenience.\n */\nexport const layerDefault = layer;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,sBAAsB,OAAO,IAAI,aAAa;CAElD,MAAM,QAAQ,OAAO,IAAI,KAAK,QAAQ,OAAwB,CAAC;AAqB/D,QAnBkC;EAChC,OAAO,eACL,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;GACrC,MAAM,SAAS,QAAQ,IAAI,SAAS,WAAW;AAC/C,UAAO,OAAO,SAAS,SAAS,OAAO,QAAQ;IAC/C;EAEJ,OAAO,YAAoB,UACzB,IAAI,OAAO,QAAQ,QAAQ,QAAQ,IAAI,KAAK,YAAY,MAAM,CAAC;EAEjE,SAAS,eACP,IAAI,OAAO,QAAQ,QAAQ,QAAQ,OAAO,KAAK,WAAW,CAAC;EAE7D,SAAS,UAAmB,OAAO,QAAQ,MAAM;EAEjD,SAAS,UAAmB,OAAO,QAAQ,MAAM;EAClD;EAGD;;;;;AAUF,MAAaA,QAA0C,MAAM,OAC3D,qBACA,oBACD;;;;AAKD,MAAa,eAAe"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidhash/mimic-effect",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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.2"
27
+ "@voidhash/tsconfig": "0.0.4"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "effect": "^3.19.12",
31
- "@voidhash/mimic": "0.0.2"
31
+ "@voidhash/mimic": "0.0.4"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsdown",
@@ -113,10 +113,10 @@ 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 use configured initial state for new docs
117
117
  const initialState = rawState !== undefined
118
118
  ? yield* storage.onLoad(rawState)
119
- : undefined;
119
+ : config.initial;
120
120
 
121
121
  // Create PubSub for broadcasting
122
122
  const pubsub = yield* PubSub.unbounded<Protocol.ServerBroadcast>();
@@ -6,7 +6,7 @@ import * as Context from "effect/Context";
6
6
  import * as Duration from "effect/Duration";
7
7
  import type { DurationInput } from "effect/Duration";
8
8
  import * as Layer from "effect/Layer";
9
- import type { Primitive, Presence } from "@voidhash/mimic";
9
+ import { Primitive, Presence } from "@voidhash/mimic";
10
10
 
11
11
  // =============================================================================
12
12
  // Mimic Server Configuration
@@ -54,6 +54,13 @@ export interface MimicServerConfig<TSchema extends Primitive.AnyPrimitive = Prim
54
54
  * @default undefined (presence disabled)
55
55
  */
56
56
  readonly presence: Presence.AnyPresence | undefined;
57
+
58
+ /**
59
+ * Initial state for new documents.
60
+ * Used when a document is created and no existing state is found in storage.
61
+ * @default undefined (documents start empty)
62
+ */
63
+ readonly initial: Primitive.InferState<TSchema> | undefined;
57
64
  }
58
65
 
59
66
  /**
@@ -95,6 +102,17 @@ export interface MimicServerConfigOptions<TSchema extends Primitive.AnyPrimitive
95
102
  * @default undefined (presence disabled)
96
103
  */
97
104
  readonly presence?: Presence.AnyPresence;
105
+
106
+ /**
107
+ * Initial state for new documents.
108
+ * Used when a document is created and no existing state is found in storage.
109
+ *
110
+ * Type-safe: required fields (without defaults) must be provided,
111
+ * while optional fields and fields with defaults can be omitted.
112
+ *
113
+ * @default undefined (documents start empty or use schema defaults)
114
+ */
115
+ readonly initial?: Primitive.InferSetInput<TSchema>;
98
116
  }
99
117
 
100
118
  /**
@@ -109,6 +127,9 @@ export const make = <TSchema extends Primitive.AnyPrimitive>(
109
127
  heartbeatInterval: Duration.decode(options.heartbeatInterval ?? "30 seconds"),
110
128
  heartbeatTimeout: Duration.decode(options.heartbeatTimeout ?? "10 seconds"),
111
129
  presence: options.presence,
130
+ initial: options.initial !== undefined
131
+ ? Primitive.applyDefaults(options.schema, options.initial as Partial<Primitive.InferState<TSchema>>)
132
+ : undefined,
112
133
  });
113
134
 
114
135
  // =============================================================================
@@ -20,20 +20,6 @@ import * as NoAuth from "./auth/NoAuth.js";
20
20
  import { HttpLayerRouter, HttpServerRequest, HttpServerResponse } from "@effect/platform";
21
21
  import { PathInput } from "@effect/platform/HttpRouter";
22
22
 
23
- // =============================================================================
24
- // Handler Tag
25
- // =============================================================================
26
-
27
- /**
28
- * Tag for the WebSocket handler function.
29
- */
30
- export class MimicWebSocketHandler extends Context.Tag(
31
- "@voidhash/mimic-server-effect/MimicWebSocketHandler"
32
- )<
33
- MimicWebSocketHandler,
34
- (socket: Socket.Socket, documentId: string) => Effect.Effect<void, unknown>
35
- >() {}
36
-
37
23
  // =============================================================================
38
24
  // Layer Composition Options
39
25
  // =============================================================================
@@ -61,121 +47,18 @@ export interface MimicLayerOptions<TSchema extends Primitive.AnyPrimitive> {
61
47
  * When provided, enables presence features on WebSocket connections.
62
48
  */
63
49
  readonly presence?: Presence.AnyPresence;
50
+ /**
51
+ * Initial state for new documents.
52
+ * Used when a document is created and no existing state is found in storage.
53
+ *
54
+ * Type-safe: required fields (without defaults) must be provided,
55
+ * while optional fields and fields with defaults can be omitted.
56
+ *
57
+ * @default undefined (documents start empty or use schema defaults)
58
+ */
59
+ readonly initial?: Primitive.InferSetInput<TSchema>;
64
60
  }
65
61
 
66
- // =============================================================================
67
- // Layer Composition
68
- // =============================================================================
69
-
70
- /**
71
- * Create a Mimic WebSocket handler layer.
72
- *
73
- * This layer provides a handler function that can be used with any WebSocket server
74
- * implementation. The handler takes a socket and document ID and manages the
75
- * document synchronization.
76
- *
77
- * By default, uses in-memory storage and no authentication.
78
- * Override these by providing MimicDataStorage and MimicAuthService layers.
79
- *
80
- * @example
81
- * ```typescript
82
- * import { MimicServer, MimicAuthService } from "@voidhash/mimic-effect";
83
- * import { Primitive } from "@voidhash/mimic";
84
- *
85
- * const TodoSchema = Primitive.Struct({
86
- * title: Primitive.String(),
87
- * completed: Primitive.Boolean(),
88
- * });
89
- *
90
- * // Create the handler layer with defaults
91
- * const HandlerLayer = MimicServer.layer({
92
- * basePath: "/mimic/todo",
93
- * schema: TodoSchema
94
- * });
95
- *
96
- * // Or with custom auth
97
- * const HandlerLayerWithAuth = MimicServer.layer({
98
- * basePath: "/mimic/todo",
99
- * schema: TodoSchema
100
- * }).pipe(
101
- * Layer.provideMerge(MimicAuthService.layer({
102
- * authHandler: (token) => ({ success: true, userId: "user-123" })
103
- * }))
104
- * );
105
- * ```
106
- */
107
- export const layer = <TSchema extends Primitive.AnyPrimitive>(
108
- options: MimicLayerOptions<TSchema>
109
- ): Layer.Layer<MimicWebSocketHandler | DocumentManager.DocumentManagerTag> => {
110
- const configLayer = MimicConfig.layer({
111
- schema: options.schema,
112
- maxTransactionHistory: options.maxTransactionHistory,
113
- presence: options.presence,
114
- });
115
-
116
- return Layer.merge(
117
- // Handler layer
118
- Layer.effect(MimicWebSocketHandler, WebSocketHandler.makeHandler).pipe(
119
- Layer.provide(DocumentManager.layer),
120
- Layer.provide(PresenceManager.layer),
121
- Layer.provide(configLayer)
122
- ),
123
- // Document manager layer
124
- DocumentManager.layer.pipe(Layer.provide(configLayer))
125
- ).pipe(
126
- // Provide defaults if not overridden
127
- Layer.provide(InMemoryDataStorage.layerDefault),
128
- Layer.provide(NoAuth.layerDefault)
129
- );
130
- };
131
-
132
- /**
133
- * Create the Mimic server handler layer.
134
- * This layer provides the WebSocket handler that can be used with any WebSocket server.
135
- *
136
- * @example
137
- * ```typescript
138
- * import { MimicServer } from "@voidhash/mimic-server-effect";
139
- * import { SocketServer } from "@effect/platform/SocketServer";
140
- * import { Primitive } from "@voidhash/mimic";
141
- *
142
- * // Define your document schema
143
- * const TodoSchema = Primitive.Struct({
144
- * title: Primitive.String(),
145
- * completed: Primitive.Boolean(),
146
- * });
147
- *
148
- * // Create the server layer
149
- * const serverLayer = MimicServer.handlerLayer({
150
- * schema: TodoSchema,
151
- * });
152
- *
153
- * // Run with your socket server
154
- * Effect.gen(function* () {
155
- * const handler = yield* MimicServer.MimicWebSocketHandler;
156
- * const server = yield* SocketServer;
157
- *
158
- * yield* server.run((socket) =>
159
- * // Extract document ID from request and call handler
160
- * handler(socket, "my-document-id")
161
- * );
162
- * }).pipe(
163
- * Effect.provide(serverLayer),
164
- * Effect.provide(YourSocketServerLayer),
165
- * );
166
- * ```
167
- */
168
- export const handlerLayer = <TSchema extends Primitive.AnyPrimitive>(
169
- options: MimicConfig.MimicServerConfigOptions<TSchema>
170
- ): Layer.Layer<MimicWebSocketHandler> =>
171
- Layer.effect(MimicWebSocketHandler, WebSocketHandler.makeHandler).pipe(
172
- Layer.provide(DocumentManager.layer),
173
- Layer.provide(PresenceManager.layer),
174
- Layer.provide(MimicConfig.layer(options)),
175
- // Provide defaults
176
- Layer.provide(InMemoryDataStorage.layerDefault),
177
- Layer.provide(NoAuth.layerDefault)
178
- );
179
62
 
180
63
  /**
181
64
  * Create the document manager layer.
@@ -190,40 +73,6 @@ export const documentManagerLayer = <TSchema extends Primitive.AnyPrimitive>(
190
73
  Layer.provide(NoAuth.layerDefault)
191
74
  );
192
75
 
193
- // =============================================================================
194
- // Convenience Functions
195
- // =============================================================================
196
-
197
- /**
198
- * Run a Mimic WebSocket server with the provided handler.
199
- *
200
- * This is a helper that:
201
- * 1. Gets the WebSocket handler from context
202
- * 2. Runs the socket server with the handler
203
- *
204
- * Note: The document ID extraction from socket is implementation-specific.
205
- * You may need to customize this based on your socket server.
206
- */
207
- export const run = (
208
- extractDocumentId: (socket: Socket.Socket) => Effect.Effect<string>
209
- ) =>
210
- Effect.gen(function* () {
211
- const handler = yield* MimicWebSocketHandler;
212
- const server = yield* SocketServer;
213
-
214
- yield* server.run((socket) =>
215
- Effect.gen(function* () {
216
- const documentId = yield* extractDocumentId(socket);
217
- yield* handler(socket, documentId);
218
- }).pipe(
219
- Effect.catchAll((error) =>
220
- Effect.logError("Connection error", error)
221
- )
222
- )
223
- );
224
- });
225
-
226
-
227
76
  /**
228
77
  * Create the HTTP handler effect for WebSocket upgrade.
229
78
  * This handler:
@@ -339,6 +188,7 @@ export const layerHttpLayerRouter = <TSchema extends Primitive.AnyPrimitive>(
339
188
  schema: options.schema,
340
189
  maxTransactionHistory: options.maxTransactionHistory,
341
190
  presence: options.presence,
191
+ initial: options.initial,
342
192
  });
343
193
 
344
194
  // Use provided layers or defaults
@@ -337,4 +337,65 @@ describe("DocumentManager", () => {
337
337
  expect(result).toBe(true);
338
338
  });
339
339
  });
340
+
341
+ describe("initial state", () => {
342
+ const makeTestLayerWithInitial = (initial: { title?: string; count?: number }) => {
343
+ const configLayer = MimicConfig.layer({
344
+ schema: TestSchema,
345
+ maxTransactionHistory: 100,
346
+ initial,
347
+ });
348
+
349
+ return DocumentManager.layer.pipe(
350
+ Layer.provide(configLayer),
351
+ Layer.provide(InMemoryDataStorage.layer)
352
+ );
353
+ };
354
+
355
+ it("should use initial state for new documents", async () => {
356
+ const result = await Effect.runPromise(
357
+ Effect.gen(function* () {
358
+ const manager = yield* DocumentManager.DocumentManagerTag;
359
+ return yield* manager.getSnapshot("new-doc");
360
+ }).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Initial Title", count: 42 })))
361
+ );
362
+
363
+ expect(result.type).toBe("snapshot");
364
+ expect(result.version).toBe(0);
365
+ expect(result.state).toEqual({ title: "Initial Title", count: 42 });
366
+ });
367
+
368
+ it("should apply defaults for omitted fields in initial state", async () => {
369
+ const result = await Effect.runPromise(
370
+ Effect.gen(function* () {
371
+ const manager = yield* DocumentManager.DocumentManagerTag;
372
+ return yield* manager.getSnapshot("new-doc");
373
+ }).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Only Title" })))
374
+ );
375
+
376
+ expect(result.type).toBe("snapshot");
377
+ // count should be 0 (default)
378
+ expect(result.state).toEqual({ title: "Only Title", count: 0 });
379
+ });
380
+
381
+ it("should prefer stored state over initial state", async () => {
382
+ const result = await Effect.runPromise(
383
+ Effect.gen(function* () {
384
+ const manager = yield* DocumentManager.DocumentManagerTag;
385
+
386
+ // Apply a transaction to modify the document
387
+ const tx = createValidTransaction("tx-1", "Modified Title");
388
+ yield* manager.submit("doc-1", tx);
389
+
390
+ // Get snapshot - should show modified state, not initial
391
+ return yield* manager.getSnapshot("doc-1");
392
+ }).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Initial Title", count: 42 })))
393
+ );
394
+
395
+ expect(result.type).toBe("snapshot");
396
+ expect((result.state as any).title).toBe("Modified Title");
397
+ // count should still be 42 since we only modified title
398
+ expect((result.state as any).count).toBe(42);
399
+ });
400
+ });
340
401
  });
@@ -172,4 +172,76 @@ describe("MimicConfig", () => {
172
172
  expect(config.presence).toBe(CursorPresence);
173
173
  });
174
174
  });
175
+
176
+ describe("initial state configuration", () => {
177
+ it("should have undefined initial state by default", () => {
178
+ const config = MimicConfig.make({
179
+ schema: TestSchema,
180
+ });
181
+
182
+ expect(config.initial).toBeUndefined();
183
+ });
184
+
185
+ it("should accept initial state option", () => {
186
+ const config = MimicConfig.make({
187
+ schema: TestSchema,
188
+ initial: { title: "My Document", count: 42 },
189
+ });
190
+
191
+ expect(config.initial).toEqual({ title: "My Document", count: 42 });
192
+ });
193
+
194
+ it("should apply defaults for omitted fields in initial state", () => {
195
+ const config = MimicConfig.make({
196
+ schema: TestSchema,
197
+ initial: { title: "My Document" }, // count has default of 0
198
+ });
199
+
200
+ expect(config.initial).toEqual({ title: "My Document", count: 0 });
201
+ });
202
+
203
+ it("should provide initial state through layer", async () => {
204
+ const testLayer = MimicConfig.layer({
205
+ schema: TestSchema,
206
+ initial: { title: "From Layer", count: 100 },
207
+ });
208
+
209
+ const result = await Effect.runPromise(
210
+ Effect.gen(function* () {
211
+ const config = yield* MimicConfig.MimicServerConfigTag;
212
+ return config.initial;
213
+ }).pipe(Effect.provide(testLayer))
214
+ );
215
+
216
+ expect(result).toEqual({ title: "From Layer", count: 100 });
217
+ });
218
+
219
+ it("should work with schema that has required fields without defaults", () => {
220
+ const SchemaWithRequired = Primitive.Struct({
221
+ name: Primitive.String().required(),
222
+ optional: Primitive.String().default("default"),
223
+ });
224
+
225
+ const config = MimicConfig.make({
226
+ schema: SchemaWithRequired,
227
+ initial: { name: "Required Name" },
228
+ });
229
+
230
+ expect(config.initial).toEqual({ name: "Required Name", optional: "default" });
231
+ });
232
+
233
+ it("should work with all options including initial", () => {
234
+ const config = MimicConfig.make({
235
+ schema: TestSchema,
236
+ maxIdleTime: "10 minutes",
237
+ maxTransactionHistory: 500,
238
+ initial: { title: "Full Options", count: 999 },
239
+ });
240
+
241
+ expect(config.schema).toBe(TestSchema);
242
+ expect(Duration.toMillis(config.maxIdleTime)).toBe(10 * 60 * 1000);
243
+ expect(config.maxTransactionHistory).toBe(500);
244
+ expect(config.initial).toEqual({ title: "Full Options", count: 999 });
245
+ });
246
+ });
175
247
  });