@voidhash/mimic-effect 0.0.8 → 1.0.0-beta.1

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 (176) hide show
  1. package/.turbo/turbo-build.log +93 -89
  2. package/README.md +385 -0
  3. package/dist/ColdStorage.cjs +60 -0
  4. package/dist/ColdStorage.d.cts +53 -0
  5. package/dist/ColdStorage.d.cts.map +1 -0
  6. package/dist/ColdStorage.d.mts +53 -0
  7. package/dist/ColdStorage.d.mts.map +1 -0
  8. package/dist/ColdStorage.mjs +60 -0
  9. package/dist/ColdStorage.mjs.map +1 -0
  10. package/dist/DocumentManager.cjs +193 -82
  11. package/dist/DocumentManager.d.cts +33 -19
  12. package/dist/DocumentManager.d.cts.map +1 -1
  13. package/dist/DocumentManager.d.mts +33 -19
  14. package/dist/DocumentManager.d.mts.map +1 -1
  15. package/dist/DocumentManager.mjs +189 -67
  16. package/dist/DocumentManager.mjs.map +1 -1
  17. package/dist/Errors.cjs +45 -0
  18. package/dist/Errors.d.cts +81 -0
  19. package/dist/Errors.d.cts.map +1 -0
  20. package/dist/Errors.d.mts +81 -0
  21. package/dist/Errors.d.mts.map +1 -0
  22. package/dist/Errors.mjs +40 -0
  23. package/dist/Errors.mjs.map +1 -0
  24. package/dist/HotStorage.cjs +77 -0
  25. package/dist/HotStorage.d.cts +54 -0
  26. package/dist/HotStorage.d.cts.map +1 -0
  27. package/dist/HotStorage.d.mts +54 -0
  28. package/dist/HotStorage.d.mts.map +1 -0
  29. package/dist/HotStorage.mjs +77 -0
  30. package/dist/HotStorage.mjs.map +1 -0
  31. package/dist/Metrics.cjs +121 -0
  32. package/dist/Metrics.d.cts +27 -0
  33. package/dist/Metrics.d.cts.map +1 -0
  34. package/dist/Metrics.d.mts +27 -0
  35. package/dist/Metrics.d.mts.map +1 -0
  36. package/dist/Metrics.mjs +106 -0
  37. package/dist/Metrics.mjs.map +1 -0
  38. package/dist/MimicAuthService.cjs +61 -45
  39. package/dist/MimicAuthService.d.cts +61 -48
  40. package/dist/MimicAuthService.d.cts.map +1 -1
  41. package/dist/MimicAuthService.d.mts +61 -48
  42. package/dist/MimicAuthService.d.mts.map +1 -1
  43. package/dist/MimicAuthService.mjs +60 -36
  44. package/dist/MimicAuthService.mjs.map +1 -1
  45. package/dist/MimicClusterServerEngine.cjs +443 -0
  46. package/dist/MimicClusterServerEngine.d.cts +17 -0
  47. package/dist/MimicClusterServerEngine.d.cts.map +1 -0
  48. package/dist/MimicClusterServerEngine.d.mts +17 -0
  49. package/dist/MimicClusterServerEngine.d.mts.map +1 -0
  50. package/dist/MimicClusterServerEngine.mjs +445 -0
  51. package/dist/MimicClusterServerEngine.mjs.map +1 -0
  52. package/dist/MimicServer.cjs +205 -96
  53. package/dist/MimicServer.d.cts +9 -110
  54. package/dist/MimicServer.d.cts.map +1 -1
  55. package/dist/MimicServer.d.mts +9 -110
  56. package/dist/MimicServer.d.mts.map +1 -1
  57. package/dist/MimicServer.mjs +206 -90
  58. package/dist/MimicServer.mjs.map +1 -1
  59. package/dist/MimicServerEngine.cjs +97 -0
  60. package/dist/MimicServerEngine.d.cts +75 -0
  61. package/dist/MimicServerEngine.d.cts.map +1 -0
  62. package/dist/MimicServerEngine.d.mts +75 -0
  63. package/dist/MimicServerEngine.d.mts.map +1 -0
  64. package/dist/MimicServerEngine.mjs +97 -0
  65. package/dist/MimicServerEngine.mjs.map +1 -0
  66. package/dist/PresenceManager.cjs +75 -91
  67. package/dist/PresenceManager.d.cts +17 -66
  68. package/dist/PresenceManager.d.cts.map +1 -1
  69. package/dist/PresenceManager.d.mts +17 -66
  70. package/dist/PresenceManager.d.mts.map +1 -1
  71. package/dist/PresenceManager.mjs +74 -78
  72. package/dist/PresenceManager.mjs.map +1 -1
  73. package/dist/Protocol.cjs +146 -0
  74. package/dist/Protocol.d.cts +203 -0
  75. package/dist/Protocol.d.cts.map +1 -0
  76. package/dist/Protocol.d.mts +203 -0
  77. package/dist/Protocol.d.mts.map +1 -0
  78. package/dist/Protocol.mjs +132 -0
  79. package/dist/Protocol.mjs.map +1 -0
  80. package/dist/Types.d.cts +172 -0
  81. package/dist/Types.d.cts.map +1 -0
  82. package/dist/Types.d.mts +172 -0
  83. package/dist/Types.d.mts.map +1 -0
  84. package/dist/_virtual/rolldown_runtime.cjs +1 -25
  85. package/dist/_virtual/rolldown_runtime.mjs +4 -1
  86. package/dist/index.cjs +37 -75
  87. package/dist/index.d.cts +13 -12
  88. package/dist/index.d.mts +13 -12
  89. package/dist/index.mjs +12 -12
  90. package/package.json +14 -6
  91. package/src/ColdStorage.ts +136 -0
  92. package/src/DocumentManager.ts +445 -193
  93. package/src/Errors.ts +100 -0
  94. package/src/HotStorage.ts +165 -0
  95. package/src/Metrics.ts +163 -0
  96. package/src/MimicAuthService.ts +126 -64
  97. package/src/MimicClusterServerEngine.ts +824 -0
  98. package/src/MimicServer.ts +448 -195
  99. package/src/MimicServerEngine.ts +272 -0
  100. package/src/PresenceManager.ts +169 -240
  101. package/src/Protocol.ts +350 -0
  102. package/src/Types.ts +231 -0
  103. package/src/index.ts +57 -23
  104. package/tests/ColdStorage.test.ts +136 -0
  105. package/tests/DocumentManager.test.ts +158 -287
  106. package/tests/HotStorage.test.ts +143 -0
  107. package/tests/MimicAuthService.test.ts +102 -134
  108. package/tests/MimicClusterServerEngine.test.ts +587 -0
  109. package/tests/MimicServer.test.ts +90 -226
  110. package/tests/MimicServerEngine.test.ts +521 -0
  111. package/tests/PresenceManager.test.ts +22 -63
  112. package/tests/Protocol.test.ts +190 -0
  113. package/tsconfig.json +1 -1
  114. package/dist/DocumentProtocol.cjs +0 -94
  115. package/dist/DocumentProtocol.d.cts +0 -113
  116. package/dist/DocumentProtocol.d.cts.map +0 -1
  117. package/dist/DocumentProtocol.d.mts +0 -113
  118. package/dist/DocumentProtocol.d.mts.map +0 -1
  119. package/dist/DocumentProtocol.mjs +0 -89
  120. package/dist/DocumentProtocol.mjs.map +0 -1
  121. package/dist/MimicConfig.cjs +0 -60
  122. package/dist/MimicConfig.d.cts +0 -141
  123. package/dist/MimicConfig.d.cts.map +0 -1
  124. package/dist/MimicConfig.d.mts +0 -141
  125. package/dist/MimicConfig.d.mts.map +0 -1
  126. package/dist/MimicConfig.mjs +0 -50
  127. package/dist/MimicConfig.mjs.map +0 -1
  128. package/dist/MimicDataStorage.cjs +0 -83
  129. package/dist/MimicDataStorage.d.cts +0 -113
  130. package/dist/MimicDataStorage.d.cts.map +0 -1
  131. package/dist/MimicDataStorage.d.mts +0 -113
  132. package/dist/MimicDataStorage.d.mts.map +0 -1
  133. package/dist/MimicDataStorage.mjs +0 -74
  134. package/dist/MimicDataStorage.mjs.map +0 -1
  135. package/dist/WebSocketHandler.cjs +0 -365
  136. package/dist/WebSocketHandler.d.cts +0 -34
  137. package/dist/WebSocketHandler.d.cts.map +0 -1
  138. package/dist/WebSocketHandler.d.mts +0 -34
  139. package/dist/WebSocketHandler.d.mts.map +0 -1
  140. package/dist/WebSocketHandler.mjs +0 -355
  141. package/dist/WebSocketHandler.mjs.map +0 -1
  142. package/dist/auth/NoAuth.cjs +0 -43
  143. package/dist/auth/NoAuth.d.cts +0 -22
  144. package/dist/auth/NoAuth.d.cts.map +0 -1
  145. package/dist/auth/NoAuth.d.mts +0 -22
  146. package/dist/auth/NoAuth.d.mts.map +0 -1
  147. package/dist/auth/NoAuth.mjs +0 -36
  148. package/dist/auth/NoAuth.mjs.map +0 -1
  149. package/dist/errors.cjs +0 -74
  150. package/dist/errors.d.cts +0 -89
  151. package/dist/errors.d.cts.map +0 -1
  152. package/dist/errors.d.mts +0 -89
  153. package/dist/errors.d.mts.map +0 -1
  154. package/dist/errors.mjs +0 -67
  155. package/dist/errors.mjs.map +0 -1
  156. package/dist/storage/InMemoryDataStorage.cjs +0 -57
  157. package/dist/storage/InMemoryDataStorage.d.cts +0 -19
  158. package/dist/storage/InMemoryDataStorage.d.cts.map +0 -1
  159. package/dist/storage/InMemoryDataStorage.d.mts +0 -19
  160. package/dist/storage/InMemoryDataStorage.d.mts.map +0 -1
  161. package/dist/storage/InMemoryDataStorage.mjs +0 -48
  162. package/dist/storage/InMemoryDataStorage.mjs.map +0 -1
  163. package/src/DocumentProtocol.ts +0 -112
  164. package/src/MimicConfig.ts +0 -211
  165. package/src/MimicDataStorage.ts +0 -157
  166. package/src/WebSocketHandler.ts +0 -735
  167. package/src/auth/NoAuth.ts +0 -46
  168. package/src/errors.ts +0 -113
  169. package/src/storage/InMemoryDataStorage.ts +0 -66
  170. package/tests/DocumentProtocol.test.ts +0 -113
  171. package/tests/InMemoryDataStorage.test.ts +0 -190
  172. package/tests/MimicConfig.test.ts +0 -290
  173. package/tests/MimicDataStorage.test.ts +0 -190
  174. package/tests/NoAuth.test.ts +0 -94
  175. package/tests/WebSocketHandler.test.ts +0 -321
  176. package/tests/errors.test.ts +0 -77
@@ -1 +1 @@
1
- {"version":3,"file":"MimicAuthService.mjs","names":[],"sources":["../src/MimicAuthService.ts"],"sourcesContent":["/**\n * @since 0.0.1\n * Authentication service interface for Mimic connections.\n * Provides pluggable authentication adapters.\n */\nimport * as Effect from \"effect/Effect\";\nimport * as Context from \"effect/Context\";\nimport * as Layer from \"effect/Layer\";\n\n// =============================================================================\n// Authentication Types\n// =============================================================================\n\n/**\n * Result of an authentication attempt.\n */\nexport type AuthResult =\n | { readonly success: true; readonly userId?: string }\n | { readonly success: false; readonly error: string };\n\n/**\n * Authentication handler function type.\n * Can be synchronous or return a Promise.\n */\nexport type AuthHandler = (token: string) => Promise<AuthResult> | AuthResult;\n\n// =============================================================================\n// Auth Service Interface\n// =============================================================================\n\n/**\n * Authentication service interface.\n * Implementations can authenticate connections using various methods (JWT, API keys, etc.)\n */\nexport interface MimicAuthService {\n /**\n * Authenticate a connection using the provided token.\n * @param token - The authentication token provided by the client\n * @returns The authentication result\n */\n readonly authenticate: (token: string) => Effect.Effect<AuthResult>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for MimicAuthService service.\n */\nexport class MimicAuthServiceTag extends Context.Tag(\n \"@voidhash/mimic-server-effect/MimicAuthService\"\n)<MimicAuthServiceTag, MimicAuthService>() {}\n\n// =============================================================================\n// Layer Constructors\n// =============================================================================\n\n/**\n * Create a MimicAuthService layer from an auth handler function.\n */\nexport const layer = (options: {\n readonly authHandler: AuthHandler;\n}): Layer.Layer<MimicAuthServiceTag> =>\n Layer.succeed(MimicAuthServiceTag, {\n authenticate: (token: string) =>\n Effect.promise(() => Promise.resolve(options.authHandler(token))),\n });\n\n/**\n * Create a MimicAuthService layer from an auth service implementation.\n */\nexport const layerService = (service: MimicAuthService): Layer.Layer<MimicAuthServiceTag> =>\n Layer.succeed(MimicAuthServiceTag, service);\n\n/**\n * Create a MimicAuthService layer from an Effect that produces an auth service.\n */\nexport const layerEffect = <E, R>(\n effect: Effect.Effect<MimicAuthService, E, R>\n): Layer.Layer<MimicAuthServiceTag, E, R> =>\n Layer.effect(MimicAuthServiceTag, effect);\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Create an auth service from an auth handler function.\n */\nexport const make = (authHandler: AuthHandler): MimicAuthService => ({\n authenticate: (token: string) =>\n Effect.promise(() => Promise.resolve(authHandler(token))),\n});\n\n/**\n * Create an auth service from an Effect-based authenticate function.\n */\nexport const makeEffect = (\n authenticate: (token: string) => Effect.Effect<AuthResult>\n): MimicAuthService => ({\n authenticate,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,sBAAb,cAAyC,QAAQ,IAC/C,iDACD,EAAyC,CAAC;;;;AAS3C,MAAa,SAAS,YAGpB,MAAM,QAAQ,qBAAqB,EACjC,eAAe,UACb,OAAO,cAAc,QAAQ,QAAQ,QAAQ,YAAY,MAAM,CAAC,CAAC,EACpE,CAAC;;;;AAKJ,MAAa,gBAAgB,YAC3B,MAAM,QAAQ,qBAAqB,QAAQ;;;;AAK7C,MAAa,eACX,WAEA,MAAM,OAAO,qBAAqB,OAAO;;;;AAS3C,MAAa,QAAQ,iBAAgD,EACnE,eAAe,UACb,OAAO,cAAc,QAAQ,QAAQ,YAAY,MAAM,CAAC,CAAC,EAC5D;;;;AAKD,MAAa,cACX,kBACsB,EACtB,cACD"}
1
+ {"version":3,"file":"MimicAuthService.mjs","names":[],"sources":["../src/MimicAuthService.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - MimicAuthService\n *\n * Authentication and authorization service interface and implementations.\n */\nimport { Context, Effect, Layer } from \"effect\";\nimport type { AuthContext, Permission } from \"./Types\";\nimport { AuthenticationError } from \"./Errors\";\n\n// =============================================================================\n// MimicAuthService Interface\n// =============================================================================\n\n/**\n * MimicAuthService interface for authentication and authorization.\n *\n * The `authenticate` method receives the token from the client's auth message\n * and the document ID being accessed. It should return an AuthContext on success\n * or fail with AuthenticationError on failure.\n *\n * The permission in AuthContext determines what the user can do:\n * - \"read\": Can subscribe, receive transactions, get snapshots\n * - \"write\": All of the above, plus can submit transactions and set presence\n */\nexport interface MimicAuthService {\n /**\n * Authenticate a connection and return authorization context.\n *\n * @param token - The token provided by the client\n * @param documentId - The document ID being accessed\n * @returns AuthContext with userId and permission level\n */\n readonly authenticate: (\n token: string,\n documentId: string\n ) => Effect.Effect<AuthContext, AuthenticationError>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for MimicAuthService\n */\nexport class MimicAuthServiceTag extends Context.Tag(\n \"@voidhash/mimic-effect/MimicAuthService\"\n)<MimicAuthServiceTag, MimicAuthService>() {}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a MimicAuthService layer from an Effect that produces the service.\n *\n * This allows you to access other Effect services when implementing authentication.\n *\n * @example\n * ```typescript\n * const Auth = MimicAuthService.make(\n * Effect.gen(function*() {\n * const db = yield* DatabaseService\n * const jwt = yield* JwtService\n *\n * return {\n * authenticate: (token, documentId) =>\n * Effect.gen(function*() {\n * const payload = yield* jwt.verify(token).pipe(\n * Effect.mapError(() => new AuthenticationError({ reason: \"Invalid token\" }))\n * )\n *\n * const permission = yield* db.getDocumentPermission(payload.userId, documentId)\n *\n * return { userId: payload.userId, permission }\n * })\n * }\n * })\n * )\n * ```\n */\nexport const make = <E, R>(\n effect: Effect.Effect<MimicAuthService, E, R>\n): Layer.Layer<MimicAuthServiceTag, E, R> =>\n Layer.effect(MimicAuthServiceTag, effect);\n\n// =============================================================================\n// NoAuth Implementation\n// =============================================================================\n\n/**\n * No-authentication implementation.\n *\n * Everyone gets write access with userId \"anonymous\".\n * ONLY USE FOR DEVELOPMENT/TESTING.\n */\nexport namespace NoAuth {\n /**\n * Create a NoAuth layer.\n * All connections are authenticated with write permission.\n */\n export const make = (): Layer.Layer<MimicAuthServiceTag> =>\n Layer.succeed(MimicAuthServiceTag, {\n authenticate: (_token, _documentId) =>\n Effect.succeed({\n userId: \"anonymous\",\n permission: \"write\" as const,\n }),\n });\n}\n\n// =============================================================================\n// Static Implementation\n// =============================================================================\n\n/**\n * Static permissions implementation.\n *\n * Permissions are defined at configuration time.\n * The token is treated as the userId.\n */\nexport namespace Static {\n export interface Options {\n /**\n * Map of userId (token) to permission level\n */\n readonly permissions: Record<string, Permission>;\n /**\n * Default permission for users not in the permissions map.\n * If undefined, unknown users will fail authentication.\n */\n readonly defaultPermission?: Permission;\n }\n\n /**\n * Create a Static auth layer.\n * The token is treated as the userId, and permissions are looked up from the config.\n */\n export const make = (options: Options): Layer.Layer<MimicAuthServiceTag> =>\n Layer.succeed(MimicAuthServiceTag, {\n authenticate: (token, _documentId) => {\n const permission = options.permissions[token] ?? options.defaultPermission;\n if (permission === undefined) {\n return Effect.fail(\n new AuthenticationError({ reason: \"Unknown user\" })\n );\n }\n return Effect.succeed({\n userId: token,\n permission,\n });\n },\n });\n}\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const MimicAuthService = {\n Tag: MimicAuthServiceTag,\n make,\n NoAuth,\n Static,\n};\n"],"mappings":";;;;;;;;;;;;AA6CA,IAAa,sBAAb,cAAyC,QAAQ,IAC/C,0CACD,EAAyC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkC3C,MAAa,QACX,WAEA,MAAM,OAAO,qBAAqB,OAAO;;;sBAkBvC,MAAM,QAAQ,qBAAqB,EACjC,eAAe,QAAQ,gBACrB,OAAO,QAAQ;EACb,QAAQ;EACR,YAAY;EACb,CAAC,EACL,CAAC;;;;iBA8BiB,YACnB,MAAM,QAAQ,qBAAqB,EACjC,eAAe,OAAO,gBAAgB;;EACpC,MAAM,sCAAa,QAAQ,YAAY,+EAAU,QAAQ;AACzD,MAAI,eAAe,OACjB,QAAO,OAAO,KACZ,IAAI,oBAAoB,EAAE,QAAQ,gBAAgB,CAAC,CACpD;AAEH,SAAO,OAAO,QAAQ;GACpB,QAAQ;GACR;GACD,CAAC;IAEL,CAAC;;AAON,MAAa,mBAAmB;CAC9B,KAAK;CACL;CACA;CACA;CACD"}
@@ -0,0 +1,443 @@
1
+ const require_ColdStorage = require('./ColdStorage.cjs');
2
+ const require_HotStorage = require('./HotStorage.cjs');
3
+ const require_Metrics = require('./Metrics.cjs');
4
+ const require_objectSpread2 = require('./_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.cjs');
5
+ const require_MimicServerEngine = require('./MimicServerEngine.cjs');
6
+ let effect = require("effect");
7
+ let _voidhash_mimic_server = require("@voidhash/mimic/server");
8
+ let _effect_cluster = require("@effect/cluster");
9
+ let _effect_rpc = require("@effect/rpc");
10
+
11
+ //#region src/MimicClusterServerEngine.ts
12
+ /**
13
+ * @voidhash/mimic-effect - MimicClusterServerEngine
14
+ *
15
+ * Clustered document management service using Effect Cluster for horizontal scaling.
16
+ * Each document becomes a cluster Entity with automatic sharding, failover, and location-transparent routing.
17
+ *
18
+ * This is an alternative to MimicServerEngine for distributed deployments.
19
+ */
20
+ const DEFAULT_MAX_IDLE_TIME = effect.Duration.minutes(5);
21
+ const DEFAULT_MAX_TRANSACTION_HISTORY = 1e3;
22
+ const DEFAULT_SNAPSHOT_INTERVAL = effect.Duration.minutes(5);
23
+ const DEFAULT_SNAPSHOT_THRESHOLD = 100;
24
+ const DEFAULT_SHARD_GROUP = "mimic-documents";
25
+ /**
26
+ * Schema for encoded transaction (wire format)
27
+ */
28
+ const EncodedTransactionSchema = effect.Schema.Struct({
29
+ id: effect.Schema.String,
30
+ ops: effect.Schema.Array(effect.Schema.Unknown)
31
+ });
32
+ /**
33
+ * Schema for submit result
34
+ */
35
+ const SubmitResultSchema = effect.Schema.Union(effect.Schema.Struct({
36
+ success: effect.Schema.Literal(true),
37
+ version: effect.Schema.Number
38
+ }), effect.Schema.Struct({
39
+ success: effect.Schema.Literal(false),
40
+ reason: effect.Schema.String
41
+ }));
42
+ /**
43
+ * Schema for snapshot response
44
+ */
45
+ const SnapshotResponseSchema = effect.Schema.Struct({
46
+ state: effect.Schema.Unknown,
47
+ version: effect.Schema.Number
48
+ });
49
+ /**
50
+ * Schema for presence entry
51
+ */
52
+ const PresenceEntrySchema = effect.Schema.Struct({
53
+ data: effect.Schema.Unknown,
54
+ userId: effect.Schema.optional(effect.Schema.String)
55
+ });
56
+ /**
57
+ * Schema for presence snapshot response
58
+ */
59
+ const PresenceSnapshotResponseSchema = effect.Schema.Struct({ presences: effect.Schema.Record({
60
+ key: effect.Schema.String,
61
+ value: PresenceEntrySchema
62
+ }) });
63
+ effect.Schema.Union(effect.Schema.Struct({
64
+ type: effect.Schema.Literal("presence_update"),
65
+ id: effect.Schema.String,
66
+ data: effect.Schema.Unknown,
67
+ userId: effect.Schema.optional(effect.Schema.String)
68
+ }), effect.Schema.Struct({
69
+ type: effect.Schema.Literal("presence_remove"),
70
+ id: effect.Schema.String
71
+ }));
72
+ effect.Schema.Unknown;
73
+ /**
74
+ * Define the Mimic Document Entity with its RPC protocol.
75
+ * This entity handles document operations for a single documentId.
76
+ */
77
+ const MimicDocumentEntity = _effect_cluster.Entity.make("MimicDocument", [
78
+ _effect_rpc.Rpc.make("Submit", {
79
+ payload: { transaction: EncodedTransactionSchema },
80
+ success: SubmitResultSchema
81
+ }),
82
+ _effect_rpc.Rpc.make("GetSnapshot", { success: SnapshotResponseSchema }),
83
+ _effect_rpc.Rpc.make("Touch", { success: effect.Schema.Void }),
84
+ _effect_rpc.Rpc.make("SetPresence", {
85
+ payload: {
86
+ connectionId: effect.Schema.String,
87
+ entry: PresenceEntrySchema
88
+ },
89
+ success: effect.Schema.Void
90
+ }),
91
+ _effect_rpc.Rpc.make("RemovePresence", {
92
+ payload: { connectionId: effect.Schema.String },
93
+ success: effect.Schema.Void
94
+ }),
95
+ _effect_rpc.Rpc.make("GetPresenceSnapshot", { success: PresenceSnapshotResponseSchema })
96
+ ]);
97
+ /**
98
+ * Context tag for cluster engine configuration
99
+ */
100
+ var MimicClusterConfigTag = class extends effect.Context.Tag("@voidhash/mimic-effect/MimicClusterConfig")() {};
101
+ const resolveClusterConfig = (config) => {
102
+ var _config$maxTransactio, _config$snapshot, _config$snapshot$tran, _config$snapshot2, _config$shardGroup;
103
+ return {
104
+ schema: config.schema,
105
+ initial: config.initial,
106
+ presence: config.presence,
107
+ maxIdleTime: config.maxIdleTime ? effect.Duration.decode(config.maxIdleTime) : DEFAULT_MAX_IDLE_TIME,
108
+ maxTransactionHistory: (_config$maxTransactio = config.maxTransactionHistory) !== null && _config$maxTransactio !== void 0 ? _config$maxTransactio : DEFAULT_MAX_TRANSACTION_HISTORY,
109
+ snapshot: {
110
+ interval: ((_config$snapshot = config.snapshot) === null || _config$snapshot === void 0 ? void 0 : _config$snapshot.interval) ? effect.Duration.decode(config.snapshot.interval) : DEFAULT_SNAPSHOT_INTERVAL,
111
+ transactionThreshold: (_config$snapshot$tran = (_config$snapshot2 = config.snapshot) === null || _config$snapshot2 === void 0 ? void 0 : _config$snapshot2.transactionThreshold) !== null && _config$snapshot$tran !== void 0 ? _config$snapshot$tran : DEFAULT_SNAPSHOT_THRESHOLD
112
+ },
113
+ shardGroup: (_config$shardGroup = config.shardGroup) !== null && _config$shardGroup !== void 0 ? _config$shardGroup : DEFAULT_SHARD_GROUP
114
+ };
115
+ };
116
+ /**
117
+ * Decode an encoded transaction to a Transaction object
118
+ */
119
+ const decodeTransaction = (encoded) => {
120
+ const { Transaction } = require("@voidhash/mimic");
121
+ return Transaction.decode(encoded);
122
+ };
123
+ /**
124
+ * Encode a Transaction to wire format
125
+ */
126
+ const encodeTransaction = (tx) => {
127
+ const { Transaction } = require("@voidhash/mimic");
128
+ return Transaction.encode(tx);
129
+ };
130
+ /**
131
+ * Create the entity handler for MimicDocument
132
+ */
133
+ const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.gen(function* () {
134
+ const documentId = (yield* _effect_cluster.Entity.CurrentAddress).entityId;
135
+ const SCHEMA_VERSION = 1;
136
+ const computeInitialState = () => {
137
+ if (config.initial === void 0) return effect.Effect.succeed(void 0);
138
+ if (typeof config.initial === "function") return config.initial({ documentId });
139
+ return effect.Effect.succeed(config.initial);
140
+ };
141
+ const storedDoc = yield* effect.Effect.catchAll(coldStorage.load(documentId), () => effect.Effect.succeed(void 0));
142
+ let initialState;
143
+ let initialVersion = 0;
144
+ if (storedDoc) {
145
+ initialState = storedDoc.state;
146
+ initialVersion = storedDoc.version;
147
+ } else initialState = yield* computeInitialState();
148
+ const broadcastPubSub = yield* effect.PubSub.unbounded();
149
+ const presencePubSub = yield* effect.PubSub.unbounded();
150
+ const stateRef = yield* effect.Ref.make({
151
+ document: void 0,
152
+ broadcastPubSub,
153
+ presences: effect.HashMap.empty(),
154
+ presencePubSub,
155
+ lastSnapshotVersion: initialVersion,
156
+ lastSnapshotTime: Date.now(),
157
+ transactionsSinceSnapshot: 0
158
+ });
159
+ const document = _voidhash_mimic_server.ServerDocument.make({
160
+ schema: config.schema,
161
+ initialState,
162
+ initialVersion,
163
+ maxTransactionHistory: config.maxTransactionHistory,
164
+ onBroadcast: (message) => {
165
+ effect.Effect.runSync(effect.PubSub.publish(broadcastPubSub, {
166
+ type: "transaction",
167
+ transaction: message.transaction,
168
+ version: message.version
169
+ }));
170
+ },
171
+ onRejection: (transactionId, reason) => {
172
+ effect.Effect.runSync(effect.PubSub.publish(broadcastPubSub, {
173
+ type: "error",
174
+ transactionId,
175
+ reason
176
+ }));
177
+ }
178
+ });
179
+ yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, { document }));
180
+ const walEntries = yield* effect.Effect.catchAll(hotStorage.getEntries(documentId, initialVersion), () => effect.Effect.succeed([]));
181
+ for (const entry of walEntries) {
182
+ const result = document.submit(entry.transaction);
183
+ if (!result.success) yield* effect.Effect.logWarning("Skipping corrupted WAL entry", {
184
+ documentId,
185
+ version: entry.version,
186
+ reason: result.reason
187
+ });
188
+ }
189
+ if (storedDoc) yield* effect.Metric.increment(require_Metrics.documentsRestored);
190
+ else yield* effect.Metric.increment(require_Metrics.documentsCreated);
191
+ yield* effect.Metric.incrementBy(require_Metrics.documentsActive, 1);
192
+ /**
193
+ * Save snapshot to ColdStorage
194
+ */
195
+ const saveSnapshot = effect.Effect.gen(function* () {
196
+ const state = yield* effect.Ref.get(stateRef);
197
+ const docState = state.document.get();
198
+ const version = state.document.getVersion();
199
+ if (docState === void 0) return;
200
+ const storedDocument = {
201
+ state: docState,
202
+ version,
203
+ schemaVersion: SCHEMA_VERSION,
204
+ savedAt: Date.now()
205
+ };
206
+ const snapshotStartTime = Date.now();
207
+ yield* effect.Effect.catchAll(coldStorage.save(documentId, storedDocument), (e) => effect.Effect.logError("Failed to save snapshot", {
208
+ documentId,
209
+ error: e
210
+ }));
211
+ const snapshotDuration = Date.now() - snapshotStartTime;
212
+ yield* effect.Metric.increment(require_Metrics.storageSnapshots);
213
+ yield* effect.Metric.update(require_Metrics.storageSnapshotLatency, snapshotDuration);
214
+ yield* effect.Effect.catchAll(hotStorage.truncate(documentId, version), (e) => effect.Effect.logError("Failed to truncate WAL", {
215
+ documentId,
216
+ error: e
217
+ }));
218
+ yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, {
219
+ lastSnapshotVersion: version,
220
+ lastSnapshotTime: Date.now(),
221
+ transactionsSinceSnapshot: 0
222
+ }));
223
+ });
224
+ /**
225
+ * Check if snapshot should be triggered
226
+ */
227
+ const checkSnapshotTriggers = effect.Effect.gen(function* () {
228
+ const state = yield* effect.Ref.get(stateRef);
229
+ const now = Date.now();
230
+ const intervalMs = effect.Duration.toMillis(config.snapshot.interval);
231
+ const threshold = config.snapshot.transactionThreshold;
232
+ if (state.transactionsSinceSnapshot >= threshold) {
233
+ yield* saveSnapshot;
234
+ return;
235
+ }
236
+ if (now - state.lastSnapshotTime >= intervalMs) {
237
+ yield* saveSnapshot;
238
+ return;
239
+ }
240
+ });
241
+ yield* effect.Effect.addFinalizer(() => effect.Effect.gen(function* () {
242
+ yield* saveSnapshot;
243
+ yield* effect.Metric.incrementBy(require_Metrics.documentsActive, -1);
244
+ yield* effect.Metric.increment(require_Metrics.documentsEvicted);
245
+ yield* effect.Effect.logDebug("Entity finalized", { documentId });
246
+ }));
247
+ return {
248
+ Submit: effect.Effect.fnUntraced(function* ({ payload }) {
249
+ const submitStartTime = Date.now();
250
+ const state = yield* effect.Ref.get(stateRef);
251
+ const transaction = decodeTransaction(payload.transaction);
252
+ const result = state.document.submit(transaction);
253
+ const latency = Date.now() - submitStartTime;
254
+ yield* effect.Metric.update(require_Metrics.transactionsLatency, latency);
255
+ if (result.success) {
256
+ yield* effect.Metric.increment(require_Metrics.transactionsProcessed);
257
+ const walEntry = {
258
+ transaction,
259
+ version: result.version,
260
+ timestamp: Date.now()
261
+ };
262
+ yield* effect.Effect.catchAll(hotStorage.append(documentId, walEntry), (e) => effect.Effect.logError("Failed to append to WAL", {
263
+ documentId,
264
+ error: e
265
+ }));
266
+ yield* effect.Metric.increment(require_Metrics.storageWalAppends);
267
+ yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, { transactionsSinceSnapshot: s.transactionsSinceSnapshot + 1 }));
268
+ yield* checkSnapshotTriggers;
269
+ } else yield* effect.Metric.increment(require_Metrics.transactionsRejected);
270
+ return result;
271
+ }),
272
+ GetSnapshot: effect.Effect.fnUntraced(function* () {
273
+ return (yield* effect.Ref.get(stateRef)).document.getSnapshot();
274
+ }),
275
+ Touch: effect.Effect.fnUntraced(function* () {}),
276
+ SetPresence: effect.Effect.fnUntraced(function* ({ payload }) {
277
+ const { connectionId, entry } = payload;
278
+ yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, { presences: effect.HashMap.set(s.presences, connectionId, entry) }));
279
+ yield* effect.Metric.increment(require_Metrics.presenceUpdates);
280
+ yield* effect.Metric.incrementBy(require_Metrics.presenceActive, 1);
281
+ const state = yield* effect.Ref.get(stateRef);
282
+ const event = {
283
+ type: "presence_update",
284
+ id: connectionId,
285
+ data: entry.data,
286
+ userId: entry.userId
287
+ };
288
+ yield* effect.PubSub.publish(state.presencePubSub, event);
289
+ }),
290
+ RemovePresence: effect.Effect.fnUntraced(function* ({ payload }) {
291
+ const { connectionId } = payload;
292
+ const state = yield* effect.Ref.get(stateRef);
293
+ if (!effect.HashMap.has(state.presences, connectionId)) return;
294
+ yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, { presences: effect.HashMap.remove(s.presences, connectionId) }));
295
+ yield* effect.Metric.incrementBy(require_Metrics.presenceActive, -1);
296
+ const event = {
297
+ type: "presence_remove",
298
+ id: connectionId
299
+ };
300
+ yield* effect.PubSub.publish(state.presencePubSub, event);
301
+ }),
302
+ GetPresenceSnapshot: effect.Effect.fnUntraced(function* () {
303
+ const state = yield* effect.Ref.get(stateRef);
304
+ const presences = {};
305
+ for (const [id, entry] of state.presences) presences[id] = entry;
306
+ return { presences };
307
+ })
308
+ };
309
+ });
310
+ var SubscriptionStoreTag = class extends effect.Context.Tag("@voidhash/mimic-effect/SubscriptionStore")() {};
311
+ const subscriptionStoreLayer = effect.Layer.effect(SubscriptionStoreTag, effect.Effect.gen(function* () {
312
+ const documentPubSubs = yield* effect.Ref.make(effect.HashMap.empty());
313
+ const presencePubSubs = yield* effect.Ref.make(effect.HashMap.empty());
314
+ return {
315
+ getOrCreatePubSub: (documentId) => effect.Effect.gen(function* () {
316
+ const current = yield* effect.Ref.get(documentPubSubs);
317
+ const existing = effect.HashMap.get(current, documentId);
318
+ if (existing._tag === "Some") return existing.value;
319
+ const pubsub = yield* effect.PubSub.unbounded();
320
+ yield* effect.Ref.update(documentPubSubs, (map) => effect.HashMap.set(map, documentId, pubsub));
321
+ return pubsub;
322
+ }),
323
+ getOrCreatePresencePubSub: (documentId) => effect.Effect.gen(function* () {
324
+ const current = yield* effect.Ref.get(presencePubSubs);
325
+ const existing = effect.HashMap.get(current, documentId);
326
+ if (existing._tag === "Some") return existing.value;
327
+ const pubsub = yield* effect.PubSub.unbounded();
328
+ yield* effect.Ref.update(presencePubSubs, (map) => effect.HashMap.set(map, documentId, pubsub));
329
+ return pubsub;
330
+ })
331
+ };
332
+ }));
333
+ /**
334
+ * Create a MimicClusterServerEngine layer.
335
+ *
336
+ * This creates a clustered document management service using Effect Cluster.
337
+ * Each document becomes a cluster Entity with automatic sharding and failover.
338
+ *
339
+ * @example
340
+ * ```typescript
341
+ * // 1. Create the engine
342
+ * const Engine = MimicClusterServerEngine.make({
343
+ * schema: DocSchema,
344
+ * initial: { title: "Untitled" },
345
+ * presence: CursorPresence,
346
+ * maxIdleTime: "5 minutes",
347
+ * snapshot: { interval: "5 minutes", transactionThreshold: 100 },
348
+ * shardGroup: "documents",
349
+ * })
350
+ *
351
+ * // 2. Create the WebSocket route
352
+ * const MimicRoute = MimicServer.layerHttpLayerRouter({
353
+ * path: "/mimic",
354
+ * })
355
+ *
356
+ * // 3. Wire together with cluster infrastructure
357
+ * const MimicLive = MimicRoute.pipe(
358
+ * Layer.provide(Engine),
359
+ * Layer.provide(ColdStorage.S3.make(...)),
360
+ * Layer.provide(HotStorage.Redis.make(...)),
361
+ * Layer.provide(MimicAuthService.make(...)),
362
+ * Layer.provide(ClusterInfrastructure),
363
+ * )
364
+ * ```
365
+ */
366
+ const make = (config) => {
367
+ const resolvedConfig = resolveClusterConfig(config);
368
+ const configLayer = effect.Layer.succeed(MimicClusterConfigTag, resolvedConfig);
369
+ const entityLayer = MimicDocumentEntity.toLayer(effect.Effect.gen(function* () {
370
+ return yield* createEntityHandler(yield* MimicClusterConfigTag, yield* require_ColdStorage.ColdStorageTag, yield* require_HotStorage.HotStorageTag);
371
+ }), {
372
+ maxIdleTime: resolvedConfig.maxIdleTime,
373
+ concurrency: 1,
374
+ mailboxCapacity: 4096
375
+ });
376
+ const engineLayer = effect.Layer.scoped(require_MimicServerEngine.MimicServerEngineTag, effect.Effect.gen(function* () {
377
+ const makeClient = yield* MimicDocumentEntity.client;
378
+ const subscriptionStore = yield* SubscriptionStoreTag;
379
+ return {
380
+ submit: (documentId, transaction) => effect.Effect.gen(function* () {
381
+ const client = makeClient(documentId);
382
+ const encodedTx = encodeTransaction(transaction);
383
+ const result = yield* client.Submit({ transaction: encodedTx }).pipe(effect.Effect.catchAll((error) => effect.Effect.succeed({
384
+ success: false,
385
+ reason: `Cluster error: ${String(error)}`
386
+ })));
387
+ if (result.success) {
388
+ const pubsub = yield* subscriptionStore.getOrCreatePubSub(documentId);
389
+ yield* effect.PubSub.publish(pubsub, {
390
+ type: "transaction",
391
+ transaction,
392
+ version: result.version
393
+ });
394
+ }
395
+ return result;
396
+ }),
397
+ getSnapshot: (documentId) => effect.Effect.gen(function* () {
398
+ return yield* makeClient(documentId).GetSnapshot(void 0).pipe(effect.Effect.orDie);
399
+ }),
400
+ subscribe: (documentId) => effect.Effect.gen(function* () {
401
+ const pubsub = yield* subscriptionStore.getOrCreatePubSub(documentId);
402
+ return effect.Stream.fromPubSub(pubsub);
403
+ }),
404
+ touch: (documentId) => effect.Effect.gen(function* () {
405
+ yield* makeClient(documentId).Touch(void 0).pipe(effect.Effect.orDie);
406
+ }),
407
+ getPresenceSnapshot: (documentId) => effect.Effect.gen(function* () {
408
+ return yield* makeClient(documentId).GetPresenceSnapshot(void 0).pipe(effect.Effect.orDie);
409
+ }),
410
+ setPresence: (documentId, connectionId, entry) => effect.Effect.gen(function* () {
411
+ yield* makeClient(documentId).SetPresence({
412
+ connectionId,
413
+ entry
414
+ }).pipe(effect.Effect.orDie);
415
+ const pubsub = yield* subscriptionStore.getOrCreatePresencePubSub(documentId);
416
+ yield* effect.PubSub.publish(pubsub, {
417
+ type: "presence_update",
418
+ id: connectionId,
419
+ data: entry.data,
420
+ userId: entry.userId
421
+ });
422
+ }),
423
+ removePresence: (documentId, connectionId) => effect.Effect.gen(function* () {
424
+ yield* makeClient(documentId).RemovePresence({ connectionId }).pipe(effect.Effect.orDie);
425
+ const pubsub = yield* subscriptionStore.getOrCreatePresencePubSub(documentId);
426
+ yield* effect.PubSub.publish(pubsub, {
427
+ type: "presence_remove",
428
+ id: connectionId
429
+ });
430
+ }),
431
+ subscribePresence: (documentId) => effect.Effect.gen(function* () {
432
+ const pubsub = yield* subscriptionStore.getOrCreatePresencePubSub(documentId);
433
+ return effect.Stream.fromPubSub(pubsub);
434
+ }),
435
+ config: resolvedConfig
436
+ };
437
+ }));
438
+ return effect.Layer.mergeAll(entityLayer, engineLayer).pipe(effect.Layer.provideMerge(subscriptionStoreLayer), effect.Layer.provideMerge(configLayer));
439
+ };
440
+ const MimicClusterServerEngine = { make };
441
+
442
+ //#endregion
443
+ exports.MimicClusterServerEngine = MimicClusterServerEngine;
@@ -0,0 +1,17 @@
1
+ import { MimicClusterServerEngineConfig } from "./Types.cjs";
2
+ import { ColdStorageTag } from "./ColdStorage.cjs";
3
+ import { HotStorageTag } from "./HotStorage.cjs";
4
+ import { MimicAuthServiceTag } from "./MimicAuthService.cjs";
5
+ import { MimicServerEngineTag } from "./MimicServerEngine.cjs";
6
+ import { Layer } from "effect";
7
+ import { Primitive } from "@voidhash/mimic";
8
+ import { Sharding } from "@effect/cluster";
9
+
10
+ //#region src/MimicClusterServerEngine.d.ts
11
+
12
+ declare const MimicClusterServerEngine: {
13
+ make: <TSchema extends Primitive.AnyPrimitive>(config: MimicClusterServerEngineConfig<TSchema>) => Layer.Layer<MimicServerEngineTag, never, ColdStorageTag | HotStorageTag | MimicAuthServiceTag | Sharding.Sharding>;
14
+ };
15
+ //#endregion
16
+ export { MimicClusterServerEngine };
17
+ //# sourceMappingURL=MimicClusterServerEngine.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MimicClusterServerEngine.d.cts","names":[],"sources":["../src/MimicClusterServerEngine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAqzBa;yBA3JwB,SAAA,CAAU,sBACrC,+BAA+B,aACtC,KAAA,CAAM,MACP,6BAEA,iBAAiB,gBAAgB,sBAAsB,QAAA,CAAS"}
@@ -0,0 +1,17 @@
1
+ import { MimicClusterServerEngineConfig } from "./Types.mjs";
2
+ import { ColdStorageTag } from "./ColdStorage.mjs";
3
+ import { HotStorageTag } from "./HotStorage.mjs";
4
+ import { MimicAuthServiceTag } from "./MimicAuthService.mjs";
5
+ import { MimicServerEngineTag } from "./MimicServerEngine.mjs";
6
+ import { Layer } from "effect";
7
+ import { Sharding } from "@effect/cluster";
8
+ import { Primitive } from "@voidhash/mimic";
9
+
10
+ //#region src/MimicClusterServerEngine.d.ts
11
+
12
+ declare const MimicClusterServerEngine: {
13
+ make: <TSchema extends Primitive.AnyPrimitive>(config: MimicClusterServerEngineConfig<TSchema>) => Layer.Layer<MimicServerEngineTag, never, ColdStorageTag | HotStorageTag | MimicAuthServiceTag | Sharding.Sharding>;
14
+ };
15
+ //#endregion
16
+ export { MimicClusterServerEngine };
17
+ //# sourceMappingURL=MimicClusterServerEngine.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MimicClusterServerEngine.d.mts","names":[],"sources":["../src/MimicClusterServerEngine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAqzBa;yBA3JwB,SAAA,CAAU,sBACrC,+BAA+B,aACtC,KAAA,CAAM,MACP,6BAEA,iBAAiB,gBAAgB,sBAAsB,QAAA,CAAS"}