@voidhash/mimic-effect 1.0.0-beta.6 → 1.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +90 -90
- package/dist/ColdStorage.cjs +9 -5
- package/dist/ColdStorage.d.cts.map +1 -1
- package/dist/ColdStorage.d.mts.map +1 -1
- package/dist/ColdStorage.mjs +9 -5
- package/dist/ColdStorage.mjs.map +1 -1
- package/dist/DocumentManager.cjs +14 -14
- package/dist/DocumentManager.d.cts.map +1 -1
- package/dist/DocumentManager.d.mts.map +1 -1
- package/dist/DocumentManager.mjs +14 -14
- package/dist/DocumentManager.mjs.map +1 -1
- package/dist/HotStorage.cjs +17 -13
- package/dist/HotStorage.d.cts.map +1 -1
- package/dist/HotStorage.d.mts.map +1 -1
- package/dist/HotStorage.mjs +17 -13
- package/dist/HotStorage.mjs.map +1 -1
- package/dist/MimicClusterServerEngine.cjs +17 -17
- package/dist/MimicClusterServerEngine.d.cts.map +1 -1
- package/dist/MimicClusterServerEngine.d.mts.map +1 -1
- package/dist/MimicClusterServerEngine.mjs +17 -17
- package/dist/MimicClusterServerEngine.mjs.map +1 -1
- package/dist/MimicServer.cjs +19 -19
- package/dist/MimicServer.d.cts.map +1 -1
- package/dist/MimicServer.d.mts.map +1 -1
- package/dist/MimicServer.mjs +19 -19
- package/dist/MimicServer.mjs.map +1 -1
- package/dist/MimicServerEngine.cjs +2 -2
- package/dist/MimicServerEngine.mjs +2 -2
- package/dist/MimicServerEngine.mjs.map +1 -1
- package/dist/PresenceManager.cjs +5 -5
- package/dist/PresenceManager.d.cts.map +1 -1
- package/dist/PresenceManager.d.mts.map +1 -1
- package/dist/PresenceManager.mjs +5 -5
- package/dist/PresenceManager.mjs.map +1 -1
- package/dist/testing/types.d.cts +3 -3
- package/package.json +3 -3
- package/src/ColdStorage.ts +21 -12
- package/src/DocumentManager.ts +49 -47
- package/src/HotStorage.ts +75 -58
- package/src/MimicClusterServerEngine.ts +61 -49
- package/src/MimicServer.ts +83 -75
- package/src/MimicServerEngine.ts +2 -2
- package/src/PresenceManager.ts +44 -34
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MimicServerEngine.mjs","names":["documentManagerLayer","presenceManagerLayer"],"sources":["../src/MimicServerEngine.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - MimicServerEngine\n *\n * Core document management service for Mimic real-time collaboration.\n * Handles document lifecycle, storage, presence, and transaction processing.\n *\n * This is the engine layer - for WebSocket routes, use MimicServer.layerHttpLayerRouter().\n */\nimport {\n Context,\n Duration,\n Effect,\n Layer,\n Scope,\n Stream,\n} from \"effect\";\nimport type { Presence, Primitive, Transaction } from \"@voidhash/mimic\";\nimport type {\n MimicServerEngineConfig,\n PresenceEntry,\n PresenceEvent,\n PresenceSnapshot,\n ResolvedConfig,\n} from \"./Types\";\nimport type * as Protocol from \"./Protocol\";\nimport { ColdStorageTag } from \"./ColdStorage\";\nimport { HotStorageTag } from \"./HotStorage\";\nimport { MimicAuthServiceTag } from \"./MimicAuthService\";\nimport {\n DocumentManagerTag,\n DocumentManagerConfigTag,\n layer as documentManagerLayer,\n type SubmitResult,\n type DocumentManagerError,\n} from \"./DocumentManager\";\nimport {\n PresenceManagerTag,\n layer as presenceManagerLayer,\n} from \"./PresenceManager\";\n\n// =============================================================================\n// MimicServerEngine Interface\n// =============================================================================\n\n/**\n * MimicServerEngine service interface.\n *\n * Provides document management operations for Mimic collaboration.\n * Use MimicServer.layerHttpLayerRouter() to create WebSocket routes.\n */\nexport interface MimicServerEngine {\n /**\n * Submit a transaction to a document.\n * Authorization is checked against the auth service.\n * May fail with DocumentManagerError if storage is unavailable.\n */\n readonly submit: (\n documentId: string,\n transaction: Transaction.Transaction\n ) => Effect.Effect<SubmitResult, DocumentManagerError>;\n\n /**\n * Get document snapshot (current state and version).\n * May fail with DocumentManagerError if storage is unavailable.\n */\n readonly getSnapshot: (\n documentId: string\n ) => Effect.Effect<{ state: unknown; version: number }, DocumentManagerError>;\n\n /**\n * Subscribe to document broadcasts (transactions).\n * Returns a stream of server messages.\n * Requires a Scope for cleanup when the subscription ends.\n * May fail with DocumentManagerError if storage is unavailable.\n */\n readonly subscribe: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<Protocol.ServerMessage, never, never>, DocumentManagerError, Scope.Scope>;\n\n /**\n * Touch document to prevent idle garbage collection.\n */\n readonly touch: (documentId: string) => Effect.Effect<void, never>;\n\n /**\n * Get presence snapshot for a document.\n */\n readonly getPresenceSnapshot: (\n documentId: string\n ) => Effect.Effect<PresenceSnapshot, never>;\n\n /**\n * Set presence for a connection.\n */\n readonly setPresence: (\n documentId: string,\n connectionId: string,\n entry: PresenceEntry\n ) => Effect.Effect<void, never>;\n\n /**\n * Remove presence for a connection.\n */\n readonly removePresence: (\n documentId: string,\n connectionId: string\n ) => Effect.Effect<void, never>;\n\n /**\n * Subscribe to presence events for a document.\n * Requires a Scope for cleanup when the subscription ends.\n */\n readonly subscribePresence: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<PresenceEvent, never, never>, never, Scope.Scope>;\n\n /**\n * Resolved engine configuration.\n * Used by route layer to access schema, presence config, etc.\n */\n readonly config: ResolvedConfig<Primitive.AnyPrimitive>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for MimicServerEngine\n */\nexport class MimicServerEngineTag extends Context.Tag(\n \"@voidhash/mimic-effect/MimicServerEngine\"\n)<MimicServerEngineTag, MimicServerEngine>() {}\n\n// =============================================================================\n// Default Configuration\n// =============================================================================\n\nconst DEFAULT_MAX_IDLE_TIME = Duration.minutes(5);\nconst DEFAULT_MAX_TRANSACTION_HISTORY = 1000;\nconst DEFAULT_SNAPSHOT_INTERVAL = Duration.minutes(5);\nconst DEFAULT_SNAPSHOT_THRESHOLD = 100;\n\n/**\n * Resolve configuration with defaults\n */\nconst resolveConfig = <TSchema extends Primitive.AnyPrimitive>(\n config: MimicServerEngineConfig<TSchema>\n): ResolvedConfig<TSchema> => ({\n schema: config.schema,\n initial: config.initial,\n presence: config.presence,\n maxIdleTime: config.maxIdleTime\n ? Duration.decode(config.maxIdleTime)\n : DEFAULT_MAX_IDLE_TIME,\n maxTransactionHistory:\n config.maxTransactionHistory ?? DEFAULT_MAX_TRANSACTION_HISTORY,\n snapshot: {\n interval: config.snapshot?.interval\n ? Duration.decode(config.snapshot.interval)\n : DEFAULT_SNAPSHOT_INTERVAL,\n transactionThreshold:\n config.snapshot?.transactionThreshold ?? DEFAULT_SNAPSHOT_THRESHOLD,\n },\n});\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a MimicServerEngine layer.\n *\n * This creates the core document management service. To expose it via WebSocket,\n * use MimicServer.layerHttpLayerRouter().\n *\n * @example\n * ```typescript\n * // 1. Create the engine\n * const Engine = MimicServerEngine.make({\n * schema: DocSchema,\n * initial: { title: \"Untitled\" },\n * presence: CursorPresence,\n * maxIdleTime: \"5 minutes\",\n * snapshot: { interval: \"5 minutes\", transactionThreshold: 100 },\n * })\n *\n * // 2. Create the WebSocket route\n * const MimicRoute = MimicServer.layerHttpLayerRouter({\n * path: \"/mimic\",\n * })\n *\n * // 3. Wire together\n * const MimicLive = MimicRoute.pipe(\n * Layer.provide(Engine),\n * Layer.provide(ColdStorage.InMemory.make()),\n * Layer.provide(HotStorage.InMemory.make()),\n * Layer.provide(MimicAuthService.NoAuth.make()),\n * )\n * ```\n */\nexport const make = <TSchema extends Primitive.AnyPrimitive>(\n config: MimicServerEngineConfig<TSchema>\n): Layer.Layer<\n MimicServerEngineTag,\n never,\n ColdStorageTag | HotStorageTag | MimicAuthServiceTag\n> => {\n const resolvedConfig = resolveConfig(config);\n\n // Create config layer for DocumentManager\n const configLayer = Layer.succeed(\n DocumentManagerConfigTag,\n resolvedConfig as ResolvedConfig<Primitive.AnyPrimitive>\n );\n\n // Create internal layers\n const internalLayers = Layer.mergeAll(\n documentManagerLayer.pipe(Layer.provide(configLayer)),\n presenceManagerLayer\n );\n\n return Layer.scoped(\n MimicServerEngineTag,\n Effect.gen(function* () {\n const documentManager = yield* DocumentManagerTag;\n const presenceManager = yield* PresenceManagerTag;\n\n const engine: MimicServerEngine = {\n submit: (documentId, transaction) =>\n documentManager.submit(documentId, transaction),\n\n getSnapshot: (documentId) => documentManager.getSnapshot(documentId),\n\n subscribe: (documentId) =>\n documentManager.subscribe(documentId) as Effect.Effect<\n Stream.Stream<Protocol.ServerMessage, never, never>,\n never\n >,\n\n touch: (documentId) => documentManager.touch(documentId),\n\n getPresenceSnapshot: (documentId) =>\n presenceManager.getSnapshot(documentId),\n\n setPresence: (documentId, connectionId, entry) =>\n presenceManager.set(documentId, connectionId, entry),\n\n removePresence: (documentId, connectionId) =>\n presenceManager.remove(documentId, connectionId),\n\n subscribePresence: (documentId) =>\n presenceManager.subscribe(documentId),\n\n config: resolvedConfig as ResolvedConfig<Primitive.AnyPrimitive>,\n };\n\n return engine;\n })\n ).pipe(Layer.provide(internalLayers));\n};\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const MimicServerEngine = {\n Tag: MimicServerEngineTag,\n make,\n};\n\n// =============================================================================\n// Re-export SubmitResult type\n// =============================================================================\n\nexport type { SubmitResult };\n"],"mappings":";;;;;;;;;;;;;;;;AAkIA,IAAa,uBAAb,cAA0C,QAAQ,IAChD,2CACD,EAA2C,CAAC;AAM7C,MAAM,wBAAwB,SAAS,QAAQ,EAAE;AACjD,MAAM,kCAAkC;AACxC,MAAM,4BAA4B,SAAS,QAAQ,EAAE;AACrD,MAAM,6BAA6B;;;;AAKnC,MAAM,iBACJ,WAC4B;;QAAC;EAC7B,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,UAAU,OAAO;EACjB,aAAa,OAAO,cAChB,SAAS,OAAO,OAAO,YAAY,GACnC;EACJ,gDACE,OAAO,8FAAyB;EAClC,UAAU;GACR,+BAAU,OAAO,8EAAU,YACvB,SAAS,OAAO,OAAO,SAAS,SAAS,GACzC;GACJ,oEACE,OAAO,gFAAU,6FAAwB;GAC5C;EACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCD,MAAa,QACX,WAKG;CACH,MAAM,iBAAiB,cAAc,OAAO;CAG5C,MAAM,cAAc,MAAM,QACxB,0BACA,eACD;CAGD,MAAM,iBAAiB,MAAM,SAC3BA,MAAqB,KAAK,MAAM,QAAQ,YAAY,CAAC,EACrDC,QACD;AAED,QAAO,MAAM,OACX,sBACA,OAAO,IAAI,aAAa;EACtB,MAAM,kBAAkB,OAAO;EAC/B,MAAM,kBAAkB,OAAO;AA+B/B,SA7BkC;GAChC,SAAS,YAAY,gBACnB,gBAAgB,OAAO,YAAY,YAAY;GAEjD,cAAc,eAAe,gBAAgB,YAAY,WAAW;GAEpE,YAAY,eACV,gBAAgB,UAAU,WAAW;GAKvC,QAAQ,eAAe,gBAAgB,MAAM,WAAW;GAExD,sBAAsB,eACpB,gBAAgB,YAAY,WAAW;GAEzC,cAAc,YAAY,cAAc,UACtC,gBAAgB,IAAI,YAAY,cAAc,MAAM;GAEtD,iBAAiB,YAAY,iBAC3B,gBAAgB,OAAO,YAAY,aAAa;GAElD,oBAAoB,eAClB,gBAAgB,UAAU,WAAW;GAEvC,QAAQ;GACT;GAGD,CACH,CAAC,KAAK,MAAM,QAAQ,eAAe,CAAC;;AAOvC,MAAa,oBAAoB;CAC/B,KAAK;CACL;CACD"}
|
|
1
|
+
{"version":3,"file":"MimicServerEngine.mjs","names":["documentManagerLayer","presenceManagerLayer"],"sources":["../src/MimicServerEngine.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - MimicServerEngine\n *\n * Core document management service for Mimic real-time collaboration.\n * Handles document lifecycle, storage, presence, and transaction processing.\n *\n * This is the engine layer - for WebSocket routes, use MimicServer.layerHttpLayerRouter().\n */\nimport {\n Context,\n Duration,\n Effect,\n Layer,\n Scope,\n Stream,\n} from \"effect\";\nimport type { Presence, Primitive, Transaction } from \"@voidhash/mimic\";\nimport type {\n MimicServerEngineConfig,\n PresenceEntry,\n PresenceEvent,\n PresenceSnapshot,\n ResolvedConfig,\n} from \"./Types\";\nimport type * as Protocol from \"./Protocol\";\nimport { ColdStorageTag } from \"./ColdStorage\";\nimport { HotStorageTag } from \"./HotStorage\";\nimport { MimicAuthServiceTag } from \"./MimicAuthService\";\nimport {\n DocumentManagerTag,\n DocumentManagerConfigTag,\n layer as documentManagerLayer,\n type SubmitResult,\n type DocumentManagerError,\n} from \"./DocumentManager\";\nimport {\n PresenceManagerTag,\n layer as presenceManagerLayer,\n} from \"./PresenceManager\";\n\n// =============================================================================\n// MimicServerEngine Interface\n// =============================================================================\n\n/**\n * MimicServerEngine service interface.\n *\n * Provides document management operations for Mimic collaboration.\n * Use MimicServer.layerHttpLayerRouter() to create WebSocket routes.\n */\nexport interface MimicServerEngine {\n /**\n * Submit a transaction to a document.\n * Authorization is checked against the auth service.\n * May fail with DocumentManagerError if storage is unavailable.\n */\n readonly submit: (\n documentId: string,\n transaction: Transaction.Transaction\n ) => Effect.Effect<SubmitResult, DocumentManagerError>;\n\n /**\n * Get document snapshot (current state and version).\n * May fail with DocumentManagerError if storage is unavailable.\n */\n readonly getSnapshot: (\n documentId: string\n ) => Effect.Effect<{ state: unknown; version: number }, DocumentManagerError>;\n\n /**\n * Subscribe to document broadcasts (transactions).\n * Returns a stream of server messages.\n * Requires a Scope for cleanup when the subscription ends.\n * May fail with DocumentManagerError if storage is unavailable.\n */\n readonly subscribe: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<Protocol.ServerMessage, never, never>, DocumentManagerError, Scope.Scope>;\n\n /**\n * Touch document to prevent idle garbage collection.\n */\n readonly touch: (documentId: string) => Effect.Effect<void, never>;\n\n /**\n * Get presence snapshot for a document.\n */\n readonly getPresenceSnapshot: (\n documentId: string\n ) => Effect.Effect<PresenceSnapshot, never>;\n\n /**\n * Set presence for a connection.\n */\n readonly setPresence: (\n documentId: string,\n connectionId: string,\n entry: PresenceEntry\n ) => Effect.Effect<void, never>;\n\n /**\n * Remove presence for a connection.\n */\n readonly removePresence: (\n documentId: string,\n connectionId: string\n ) => Effect.Effect<void, never>;\n\n /**\n * Subscribe to presence events for a document.\n * Requires a Scope for cleanup when the subscription ends.\n */\n readonly subscribePresence: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<PresenceEvent, never, never>, never, Scope.Scope>;\n\n /**\n * Resolved engine configuration.\n * Used by route layer to access schema, presence config, etc.\n */\n readonly config: ResolvedConfig<Primitive.AnyPrimitive>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for MimicServerEngine\n */\nexport class MimicServerEngineTag extends Context.Tag(\n \"@voidhash/mimic-effect/MimicServerEngine\"\n)<MimicServerEngineTag, MimicServerEngine>() {}\n\n// =============================================================================\n// Default Configuration\n// =============================================================================\n\nconst DEFAULT_MAX_IDLE_TIME = Duration.minutes(5);\nconst DEFAULT_MAX_TRANSACTION_HISTORY = 1000;\nconst DEFAULT_SNAPSHOT_INTERVAL = Duration.minutes(5);\nconst DEFAULT_SNAPSHOT_THRESHOLD = 100;\n\n/**\n * Resolve configuration with defaults\n */\nconst resolveConfig = <TSchema extends Primitive.AnyPrimitive>(\n config: MimicServerEngineConfig<TSchema>\n): ResolvedConfig<TSchema> => ({\n schema: config.schema,\n initial: config.initial,\n presence: config.presence,\n maxIdleTime: config.maxIdleTime\n ? Duration.decode(config.maxIdleTime)\n : DEFAULT_MAX_IDLE_TIME,\n maxTransactionHistory:\n config.maxTransactionHistory ?? DEFAULT_MAX_TRANSACTION_HISTORY,\n snapshot: {\n interval: config.snapshot?.interval\n ? Duration.decode(config.snapshot.interval)\n : DEFAULT_SNAPSHOT_INTERVAL,\n transactionThreshold:\n config.snapshot?.transactionThreshold ?? DEFAULT_SNAPSHOT_THRESHOLD,\n },\n});\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a MimicServerEngine layer.\n *\n * This creates the core document management service. To expose it via WebSocket,\n * use MimicServer.layerHttpLayerRouter().\n *\n * @example\n * ```typescript\n * // 1. Create the engine\n * const Engine = MimicServerEngine.make({\n * schema: DocSchema,\n * initial: { title: \"Untitled\" },\n * presence: CursorPresence,\n * maxIdleTime: \"5 minutes\",\n * snapshot: { interval: \"5 minutes\", transactionThreshold: 100 },\n * })\n *\n * // 2. Create the WebSocket route\n * const MimicRoute = MimicServer.layerHttpLayerRouter({\n * path: \"/mimic\",\n * })\n *\n * // 3. Wire together\n * const MimicLive = MimicRoute.pipe(\n * Layer.provide(Engine),\n * Layer.provide(ColdStorage.InMemory.make()),\n * Layer.provide(HotStorage.InMemory.make()),\n * Layer.provide(MimicAuthService.NoAuth.make()),\n * )\n * ```\n */\nexport const make = <TSchema extends Primitive.AnyPrimitive>(\n config: MimicServerEngineConfig<TSchema>\n): Layer.Layer<\n MimicServerEngineTag,\n never,\n ColdStorageTag | HotStorageTag | MimicAuthServiceTag\n> => {\n const resolvedConfig = resolveConfig(config);\n\n // Create config layer for DocumentManager\n const configLayer = Layer.succeed(\n DocumentManagerConfigTag,\n resolvedConfig as ResolvedConfig<Primitive.AnyPrimitive>\n );\n\n // Create internal layers\n const internalLayers = Layer.mergeAll(\n documentManagerLayer.pipe(Layer.provide(configLayer)),\n presenceManagerLayer\n );\n\n return Layer.scoped(\n MimicServerEngineTag,\n Effect.fn(\"mimic-server-engine.make\")(function* () {\n const documentManager = yield* DocumentManagerTag;\n const presenceManager = yield* PresenceManagerTag;\n\n const engine: MimicServerEngine = {\n submit: (documentId, transaction) =>\n documentManager.submit(documentId, transaction),\n\n getSnapshot: (documentId) => documentManager.getSnapshot(documentId),\n\n subscribe: (documentId) =>\n documentManager.subscribe(documentId) as Effect.Effect<\n Stream.Stream<Protocol.ServerMessage, never, never>,\n never\n >,\n\n touch: (documentId) => documentManager.touch(documentId),\n\n getPresenceSnapshot: (documentId) =>\n presenceManager.getSnapshot(documentId),\n\n setPresence: (documentId, connectionId, entry) =>\n presenceManager.set(documentId, connectionId, entry),\n\n removePresence: (documentId, connectionId) =>\n presenceManager.remove(documentId, connectionId),\n\n subscribePresence: (documentId) =>\n presenceManager.subscribe(documentId),\n\n config: resolvedConfig as ResolvedConfig<Primitive.AnyPrimitive>,\n };\n\n return engine;\n })()\n ).pipe(Layer.provide(internalLayers));\n};\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const MimicServerEngine = {\n Tag: MimicServerEngineTag,\n make,\n};\n\n// =============================================================================\n// Re-export SubmitResult type\n// =============================================================================\n\nexport type { SubmitResult };\n"],"mappings":";;;;;;;;;;;;;;;;AAkIA,IAAa,uBAAb,cAA0C,QAAQ,IAChD,2CACD,EAA2C,CAAC;AAM7C,MAAM,wBAAwB,SAAS,QAAQ,EAAE;AACjD,MAAM,kCAAkC;AACxC,MAAM,4BAA4B,SAAS,QAAQ,EAAE;AACrD,MAAM,6BAA6B;;;;AAKnC,MAAM,iBACJ,WAC4B;;QAAC;EAC7B,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,UAAU,OAAO;EACjB,aAAa,OAAO,cAChB,SAAS,OAAO,OAAO,YAAY,GACnC;EACJ,gDACE,OAAO,8FAAyB;EAClC,UAAU;GACR,+BAAU,OAAO,8EAAU,YACvB,SAAS,OAAO,OAAO,SAAS,SAAS,GACzC;GACJ,oEACE,OAAO,gFAAU,6FAAwB;GAC5C;EACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCD,MAAa,QACX,WAKG;CACH,MAAM,iBAAiB,cAAc,OAAO;CAG5C,MAAM,cAAc,MAAM,QACxB,0BACA,eACD;CAGD,MAAM,iBAAiB,MAAM,SAC3BA,MAAqB,KAAK,MAAM,QAAQ,YAAY,CAAC,EACrDC,QACD;AAED,QAAO,MAAM,OACX,sBACA,OAAO,GAAG,2BAA2B,CAAC,aAAa;EACjD,MAAM,kBAAkB,OAAO;EAC/B,MAAM,kBAAkB,OAAO;AA+B/B,SA7BkC;GAChC,SAAS,YAAY,gBACnB,gBAAgB,OAAO,YAAY,YAAY;GAEjD,cAAc,eAAe,gBAAgB,YAAY,WAAW;GAEpE,YAAY,eACV,gBAAgB,UAAU,WAAW;GAKvC,QAAQ,eAAe,gBAAgB,MAAM,WAAW;GAExD,sBAAsB,eACpB,gBAAgB,YAAY,WAAW;GAEzC,cAAc,YAAY,cAAc,UACtC,gBAAgB,IAAI,YAAY,cAAc,MAAM;GAEtD,iBAAiB,YAAY,iBAC3B,gBAAgB,OAAO,YAAY,aAAa;GAElD,oBAAoB,eAClB,gBAAgB,UAAU,WAAW;GAEvC,QAAQ;GACT;GAGD,EAAE,CACL,CAAC,KAAK,MAAM,QAAQ,eAAe,CAAC;;AAOvC,MAAa,oBAAoB;CAC/B,KAAK;CACL;CACD"}
|
package/dist/PresenceManager.cjs
CHANGED
|
@@ -20,7 +20,7 @@ const layer = effect.Layer.effect(PresenceManagerTag, effect.Effect.gen(function
|
|
|
20
20
|
/**
|
|
21
21
|
* Get or create presence state for a document
|
|
22
22
|
*/
|
|
23
|
-
const getOrCreateDocumentState =
|
|
23
|
+
const getOrCreateDocumentState = effect.Effect.fn("presence.document-state.get-or-create")(function* (documentId) {
|
|
24
24
|
const current = yield* effect.Ref.get(store);
|
|
25
25
|
const existing = effect.HashMap.get(current, documentId);
|
|
26
26
|
if (existing._tag === "Some") return existing.value;
|
|
@@ -33,7 +33,7 @@ const layer = effect.Layer.effect(PresenceManagerTag, effect.Effect.gen(function
|
|
|
33
33
|
return state;
|
|
34
34
|
});
|
|
35
35
|
return {
|
|
36
|
-
getSnapshot:
|
|
36
|
+
getSnapshot: effect.Effect.fn("presence.snapshot.get")(function* (documentId) {
|
|
37
37
|
const current = yield* effect.Ref.get(store);
|
|
38
38
|
const existing = effect.HashMap.get(current, documentId);
|
|
39
39
|
if (existing._tag === "None") return { presences: {} };
|
|
@@ -41,7 +41,7 @@ const layer = effect.Layer.effect(PresenceManagerTag, effect.Effect.gen(function
|
|
|
41
41
|
for (const [id, entry] of existing.value.presences) presences[id] = entry;
|
|
42
42
|
return { presences };
|
|
43
43
|
}),
|
|
44
|
-
set:
|
|
44
|
+
set: effect.Effect.fn("presence.set")(function* (documentId, connectionId, entry) {
|
|
45
45
|
const state = yield* getOrCreateDocumentState(documentId);
|
|
46
46
|
yield* effect.Ref.update(store, (map) => {
|
|
47
47
|
const existing = effect.HashMap.get(map, documentId);
|
|
@@ -58,7 +58,7 @@ const layer = effect.Layer.effect(PresenceManagerTag, effect.Effect.gen(function
|
|
|
58
58
|
};
|
|
59
59
|
yield* effect.PubSub.publish(state.pubsub, event);
|
|
60
60
|
}),
|
|
61
|
-
remove:
|
|
61
|
+
remove: effect.Effect.fn("presence.remove")(function* (documentId, connectionId) {
|
|
62
62
|
const current = yield* effect.Ref.get(store);
|
|
63
63
|
const existing = effect.HashMap.get(current, documentId);
|
|
64
64
|
if (existing._tag === "None") return;
|
|
@@ -75,7 +75,7 @@ const layer = effect.Layer.effect(PresenceManagerTag, effect.Effect.gen(function
|
|
|
75
75
|
};
|
|
76
76
|
yield* effect.PubSub.publish(existing.value.pubsub, event);
|
|
77
77
|
}),
|
|
78
|
-
subscribe:
|
|
78
|
+
subscribe: effect.Effect.fn("presence.subscribe")(function* (documentId) {
|
|
79
79
|
const state = yield* getOrCreateDocumentState(documentId);
|
|
80
80
|
return effect.Stream.fromPubSub(state.pubsub);
|
|
81
81
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PresenceManager.d.cts","names":[],"sources":["../src/PresenceManager.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAgEO,UA/BU,eAAA,CA+BH;EAAM;AACnB;;gDA1BM,MAAA,CAAO,OAAO;;;AAmCrB;
|
|
1
|
+
{"version":3,"file":"PresenceManager.d.cts","names":[],"sources":["../src/PresenceManager.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAgEO,UA/BU,eAAA,CA+BH;EAAM;AACnB;;gDA1BM,MAAA,CAAO,OAAO;;;AAmCrB;EA8Ja,SAAA,GAAA,EAAA,CAAA,UAGZ,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,KAAA,EA5LU,aA4LV,EAAA,GA3LM,MAAA,CAAO,MA2Lb,CAAA,IAAA,CAAA;;;;iEAnLM,MAAA,CAAO;;;;;8CAQP,MAAA,CAAO,OAAO,MAAA,CAAO,OAAO,uBAAuB,KAAA,CAAM;;cAC/D;;;;cASY,kBAAA,SAA2B,uBAAA;cA8J3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PresenceManager.d.mts","names":[],"sources":["../src/PresenceManager.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAgEO,UA/BU,eAAA,CA+BH;EAAM;AACnB;;gDA1BM,MAAA,CAAO,OAAO;;;AAmCrB;
|
|
1
|
+
{"version":3,"file":"PresenceManager.d.mts","names":[],"sources":["../src/PresenceManager.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAgEO,UA/BU,eAAA,CA+BH;EAAM;AACnB;;gDA1BM,MAAA,CAAO,OAAO;;;AAmCrB;EA8Ja,SAAA,GAAA,EAAA,CAAA,UAGZ,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,KAAA,EA5LU,aA4LV,EAAA,GA3LM,MAAA,CAAO,MA2Lb,CAAA,IAAA,CAAA;;;;iEAnLM,MAAA,CAAO;;;;;8CAQP,MAAA,CAAO,OAAO,MAAA,CAAO,OAAO,uBAAuB,KAAA,CAAM;;cAC/D;;;;cASY,kBAAA,SAA2B,uBAAA;cA8J3B"}
|
package/dist/PresenceManager.mjs
CHANGED
|
@@ -20,7 +20,7 @@ const layer = Layer.effect(PresenceManagerTag, Effect.gen(function* () {
|
|
|
20
20
|
/**
|
|
21
21
|
* Get or create presence state for a document
|
|
22
22
|
*/
|
|
23
|
-
const getOrCreateDocumentState =
|
|
23
|
+
const getOrCreateDocumentState = Effect.fn("presence.document-state.get-or-create")(function* (documentId) {
|
|
24
24
|
const current = yield* Ref.get(store);
|
|
25
25
|
const existing = HashMap.get(current, documentId);
|
|
26
26
|
if (existing._tag === "Some") return existing.value;
|
|
@@ -33,7 +33,7 @@ const layer = Layer.effect(PresenceManagerTag, Effect.gen(function* () {
|
|
|
33
33
|
return state;
|
|
34
34
|
});
|
|
35
35
|
return {
|
|
36
|
-
getSnapshot:
|
|
36
|
+
getSnapshot: Effect.fn("presence.snapshot.get")(function* (documentId) {
|
|
37
37
|
const current = yield* Ref.get(store);
|
|
38
38
|
const existing = HashMap.get(current, documentId);
|
|
39
39
|
if (existing._tag === "None") return { presences: {} };
|
|
@@ -41,7 +41,7 @@ const layer = Layer.effect(PresenceManagerTag, Effect.gen(function* () {
|
|
|
41
41
|
for (const [id, entry] of existing.value.presences) presences[id] = entry;
|
|
42
42
|
return { presences };
|
|
43
43
|
}),
|
|
44
|
-
set: (documentId, connectionId, entry)
|
|
44
|
+
set: Effect.fn("presence.set")(function* (documentId, connectionId, entry) {
|
|
45
45
|
const state = yield* getOrCreateDocumentState(documentId);
|
|
46
46
|
yield* Ref.update(store, (map) => {
|
|
47
47
|
const existing = HashMap.get(map, documentId);
|
|
@@ -58,7 +58,7 @@ const layer = Layer.effect(PresenceManagerTag, Effect.gen(function* () {
|
|
|
58
58
|
};
|
|
59
59
|
yield* PubSub.publish(state.pubsub, event);
|
|
60
60
|
}),
|
|
61
|
-
remove:
|
|
61
|
+
remove: Effect.fn("presence.remove")(function* (documentId, connectionId) {
|
|
62
62
|
const current = yield* Ref.get(store);
|
|
63
63
|
const existing = HashMap.get(current, documentId);
|
|
64
64
|
if (existing._tag === "None") return;
|
|
@@ -75,7 +75,7 @@ const layer = Layer.effect(PresenceManagerTag, Effect.gen(function* () {
|
|
|
75
75
|
};
|
|
76
76
|
yield* PubSub.publish(existing.value.pubsub, event);
|
|
77
77
|
}),
|
|
78
|
-
subscribe:
|
|
78
|
+
subscribe: Effect.fn("presence.subscribe")(function* (documentId) {
|
|
79
79
|
const state = yield* getOrCreateDocumentState(documentId);
|
|
80
80
|
return Stream.fromPubSub(state.pubsub);
|
|
81
81
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PresenceManager.mjs","names":["layer: Layer.Layer<PresenceManagerTag>","state: DocumentPresenceState","presences: Record<string, PresenceEntry>","Metrics.presenceUpdates","Metrics.presenceActive","event: PresenceEvent"],"sources":["../src/PresenceManager.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - PresenceManager\n *\n * Internal service for managing presence state per document.\n */\nimport {\n Context,\n Effect,\n HashMap,\n Layer,\n Metric,\n PubSub,\n Ref,\n Scope,\n Stream,\n} from \"effect\";\nimport type {\n PresenceEntry,\n PresenceEvent,\n PresenceSnapshot,\n} from \"./Types\";\nimport * as Metrics from \"./Metrics\";\n\n// =============================================================================\n// PresenceManager Interface\n// =============================================================================\n\n/**\n * Internal service for managing presence state per document.\n *\n * Presence is ephemeral state associated with connections, not persisted.\n * Each document has its own set of presences, keyed by connectionId.\n */\nexport interface PresenceManager {\n /**\n * Get snapshot of all presences for a document.\n */\n readonly getSnapshot: (\n documentId: string\n ) => Effect.Effect<PresenceSnapshot>;\n\n /**\n * Set/update presence for a connection.\n */\n readonly set: (\n documentId: string,\n connectionId: string,\n entry: PresenceEntry\n ) => Effect.Effect<void>;\n\n /**\n * Remove presence for a connection (on disconnect).\n */\n readonly remove: (\n documentId: string,\n connectionId: string\n ) => Effect.Effect<void>;\n\n /**\n * Subscribe to presence events for a document.\n * Returns a stream of presence update/remove events.\n */\n readonly subscribe: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<PresenceEvent>, never, Scope.Scope>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for PresenceManager service\n */\nexport class PresenceManagerTag extends Context.Tag(\n \"@voidhash/mimic-effect/PresenceManager\"\n)<PresenceManagerTag, PresenceManager>() {}\n\n// =============================================================================\n// Internal Types\n// =============================================================================\n\n/**\n * Per-document presence state\n */\ninterface DocumentPresenceState {\n readonly presences: HashMap.HashMap<string, PresenceEntry>;\n readonly pubsub: PubSub.PubSub<PresenceEvent>;\n}\n\n// =============================================================================\n// Layer Implementation\n// =============================================================================\n\n/**\n * Create the PresenceManager layer.\n */\nexport const layer: Layer.Layer<PresenceManagerTag> = Layer.effect(\n PresenceManagerTag,\n Effect.gen(function* () {\n // Store: documentId -> DocumentPresenceState\n const store = yield* Ref.make(\n HashMap.empty<string, DocumentPresenceState>()\n );\n\n /**\n * Get or create presence state for a document\n */\n const getOrCreateDocumentState = (\n
|
|
1
|
+
{"version":3,"file":"PresenceManager.mjs","names":["layer: Layer.Layer<PresenceManagerTag>","state: DocumentPresenceState","presences: Record<string, PresenceEntry>","Metrics.presenceUpdates","Metrics.presenceActive","event: PresenceEvent"],"sources":["../src/PresenceManager.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - PresenceManager\n *\n * Internal service for managing presence state per document.\n */\nimport {\n Context,\n Effect,\n HashMap,\n Layer,\n Metric,\n PubSub,\n Ref,\n Scope,\n Stream,\n} from \"effect\";\nimport type {\n PresenceEntry,\n PresenceEvent,\n PresenceSnapshot,\n} from \"./Types\";\nimport * as Metrics from \"./Metrics\";\n\n// =============================================================================\n// PresenceManager Interface\n// =============================================================================\n\n/**\n * Internal service for managing presence state per document.\n *\n * Presence is ephemeral state associated with connections, not persisted.\n * Each document has its own set of presences, keyed by connectionId.\n */\nexport interface PresenceManager {\n /**\n * Get snapshot of all presences for a document.\n */\n readonly getSnapshot: (\n documentId: string\n ) => Effect.Effect<PresenceSnapshot>;\n\n /**\n * Set/update presence for a connection.\n */\n readonly set: (\n documentId: string,\n connectionId: string,\n entry: PresenceEntry\n ) => Effect.Effect<void>;\n\n /**\n * Remove presence for a connection (on disconnect).\n */\n readonly remove: (\n documentId: string,\n connectionId: string\n ) => Effect.Effect<void>;\n\n /**\n * Subscribe to presence events for a document.\n * Returns a stream of presence update/remove events.\n */\n readonly subscribe: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<PresenceEvent>, never, Scope.Scope>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for PresenceManager service\n */\nexport class PresenceManagerTag extends Context.Tag(\n \"@voidhash/mimic-effect/PresenceManager\"\n)<PresenceManagerTag, PresenceManager>() {}\n\n// =============================================================================\n// Internal Types\n// =============================================================================\n\n/**\n * Per-document presence state\n */\ninterface DocumentPresenceState {\n readonly presences: HashMap.HashMap<string, PresenceEntry>;\n readonly pubsub: PubSub.PubSub<PresenceEvent>;\n}\n\n// =============================================================================\n// Layer Implementation\n// =============================================================================\n\n/**\n * Create the PresenceManager layer.\n */\nexport const layer: Layer.Layer<PresenceManagerTag> = Layer.effect(\n PresenceManagerTag,\n Effect.gen(function* () {\n // Store: documentId -> DocumentPresenceState\n const store = yield* Ref.make(\n HashMap.empty<string, DocumentPresenceState>()\n );\n\n /**\n * Get or create presence state for a document\n */\n const getOrCreateDocumentState = Effect.fn(\n \"presence.document-state.get-or-create\"\n )(function* (documentId: string) {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n if (existing._tag === \"Some\") {\n return existing.value;\n }\n\n // Create new state for this document\n const pubsub = yield* PubSub.unbounded<PresenceEvent>();\n const state: DocumentPresenceState = {\n presences: HashMap.empty(),\n pubsub,\n };\n\n yield* Ref.update(store, (map) => HashMap.set(map, documentId, state));\n return state;\n });\n\n return {\n getSnapshot: Effect.fn(\"presence.snapshot.get\")(\n function* (documentId: string) {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n if (existing._tag === \"None\") {\n return { presences: {} };\n }\n\n // Convert HashMap to Record\n const presences: Record<string, PresenceEntry> = {};\n for (const [id, entry] of existing.value.presences) {\n presences[id] = entry;\n }\n return { presences };\n }\n ),\n\n set: Effect.fn(\"presence.set\")(\n function* (\n documentId: string,\n connectionId: string,\n entry: PresenceEntry\n ) {\n const state = yield* getOrCreateDocumentState(documentId);\n\n // Update presence in store\n yield* Ref.update(store, (map) => {\n const existing = HashMap.get(map, documentId);\n if (existing._tag === \"None\") return map;\n return HashMap.set(map, documentId, {\n ...existing.value,\n presences: HashMap.set(\n existing.value.presences,\n connectionId,\n entry\n ),\n });\n });\n\n // Track metrics\n yield* Metric.increment(Metrics.presenceUpdates);\n yield* Metric.incrementBy(Metrics.presenceActive, 1);\n\n // Broadcast update event\n const event: PresenceEvent = {\n type: \"presence_update\",\n id: connectionId,\n data: entry.data,\n userId: entry.userId,\n };\n yield* PubSub.publish(state.pubsub, event);\n }\n ),\n\n remove: Effect.fn(\"presence.remove\")(\n function* (documentId: string, connectionId: string) {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n if (existing._tag === \"None\") return;\n\n // Check if presence exists before removing\n const hasPresence = HashMap.has(\n existing.value.presences,\n connectionId\n );\n if (!hasPresence) return;\n\n // Remove presence from store\n yield* Ref.update(store, (map) => {\n const docState = HashMap.get(map, documentId);\n if (docState._tag === \"None\") return map;\n return HashMap.set(map, documentId, {\n ...docState.value,\n presences: HashMap.remove(docState.value.presences, connectionId),\n });\n });\n\n // Track metrics\n yield* Metric.incrementBy(Metrics.presenceActive, -1);\n\n // Broadcast remove event\n const event: PresenceEvent = {\n type: \"presence_remove\",\n id: connectionId,\n };\n yield* PubSub.publish(existing.value.pubsub, event);\n }\n ),\n\n subscribe: Effect.fn(\"presence.subscribe\")(\n function* (documentId: string) {\n const state = yield* getOrCreateDocumentState(documentId);\n return Stream.fromPubSub(state.pubsub);\n }\n ),\n };\n })\n);\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const PresenceManager = {\n Tag: PresenceManagerTag,\n layer,\n};\n"],"mappings":";;;;;;;;;;;;;AA0EA,IAAa,qBAAb,cAAwC,QAAQ,IAC9C,yCACD,EAAuC,CAAC;;;;AAqBzC,MAAaA,QAAyC,MAAM,OAC1D,oBACA,OAAO,IAAI,aAAa;CAEtB,MAAM,QAAQ,OAAO,IAAI,KACvB,QAAQ,OAAsC,CAC/C;;;;CAKD,MAAM,2BAA2B,OAAO,GACtC,wCACD,CAAC,WAAW,YAAoB;EAC/B,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;EACrC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AACjD,MAAI,SAAS,SAAS,OACpB,QAAO,SAAS;EAIlB,MAAM,SAAS,OAAO,OAAO,WAA0B;EACvD,MAAMC,QAA+B;GACnC,WAAW,QAAQ,OAAO;GAC1B;GACD;AAED,SAAO,IAAI,OAAO,QAAQ,QAAQ,QAAQ,IAAI,KAAK,YAAY,MAAM,CAAC;AACtE,SAAO;GACP;AAEF,QAAO;EACL,aAAa,OAAO,GAAG,wBAAwB,CAC7C,WAAW,YAAoB;GAC7B,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;GACrC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AACjD,OAAI,SAAS,SAAS,OACpB,QAAO,EAAE,WAAW,EAAE,EAAE;GAI1B,MAAMC,YAA2C,EAAE;AACnD,QAAK,MAAM,CAAC,IAAI,UAAU,SAAS,MAAM,UACvC,WAAU,MAAM;AAElB,UAAO,EAAE,WAAW;IAEvB;EAED,KAAK,OAAO,GAAG,eAAe,CAC5B,WACE,YACA,cACA,OACA;GACA,MAAM,QAAQ,OAAO,yBAAyB,WAAW;AAGzD,UAAO,IAAI,OAAO,QAAQ,QAAQ;IAChC,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;AAC7C,QAAI,SAAS,SAAS,OAAQ,QAAO;AACrC,WAAO,QAAQ,IAAI,KAAK,8CACnB,SAAS,cACZ,WAAW,QAAQ,IACjB,SAAS,MAAM,WACf,cACA,MACD,IACD;KACF;AAGF,UAAO,OAAO,UAAUC,gBAAwB;AAChD,UAAO,OAAO,YAAYC,gBAAwB,EAAE;GAGpD,MAAMC,QAAuB;IAC3B,MAAM;IACN,IAAI;IACJ,MAAM,MAAM;IACZ,QAAQ,MAAM;IACf;AACD,UAAO,OAAO,QAAQ,MAAM,QAAQ,MAAM;IAE7C;EAED,QAAQ,OAAO,GAAG,kBAAkB,CAClC,WAAW,YAAoB,cAAsB;GACnD,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;GACrC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AACjD,OAAI,SAAS,SAAS,OAAQ;AAO9B,OAAI,CAJgB,QAAQ,IAC1B,SAAS,MAAM,WACf,aACD,CACiB;AAGlB,UAAO,IAAI,OAAO,QAAQ,QAAQ;IAChC,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;AAC7C,QAAI,SAAS,SAAS,OAAQ,QAAO;AACrC,WAAO,QAAQ,IAAI,KAAK,8CACnB,SAAS,cACZ,WAAW,QAAQ,OAAO,SAAS,MAAM,WAAW,aAAa,IACjE;KACF;AAGF,UAAO,OAAO,YAAYD,gBAAwB,GAAG;GAGrD,MAAMC,QAAuB;IAC3B,MAAM;IACN,IAAI;IACL;AACD,UAAO,OAAO,QAAQ,SAAS,MAAM,QAAQ,MAAM;IAEtD;EAED,WAAW,OAAO,GAAG,qBAAqB,CACxC,WAAW,YAAoB;GAC7B,MAAM,QAAQ,OAAO,yBAAyB,WAAW;AACzD,UAAO,OAAO,WAAW,MAAM,OAAO;IAEzC;EACF;EACD,CACH;AAMD,MAAa,kBAAkB;CAC7B,KAAK;CACL;CACD"}
|
package/dist/testing/types.d.cts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as effect_Types7 from "effect/Types";
|
|
3
|
+
import * as effect_Cause7 from "effect/Cause";
|
|
4
4
|
|
|
5
5
|
//#region src/testing/types.d.ts
|
|
6
6
|
|
|
7
|
-
declare const TestError_base: new <A extends Record<string, any> = {}>(args:
|
|
7
|
+
declare const TestError_base: new <A extends Record<string, any> = {}>(args: effect_Types7.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause7.YieldableError & {
|
|
8
8
|
readonly _tag: "TestError";
|
|
9
9
|
} & Readonly<A>;
|
|
10
10
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidhash/mimic-effect",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"typescript": "5.8.3",
|
|
27
27
|
"vite-tsconfig-paths": "^5.1.4",
|
|
28
28
|
"vitest": "^3.2.4",
|
|
29
|
-
"@voidhash/tsconfig": "1.0.0-beta.
|
|
29
|
+
"@voidhash/tsconfig": "1.0.0-beta.7"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@effect/platform": "^0.93.8",
|
|
33
33
|
"@effect/cluster": "^0.55.0",
|
|
34
34
|
"@effect/rpc": "^0.72.2",
|
|
35
35
|
"effect": "^3.19.12",
|
|
36
|
-
"@voidhash/mimic": "1.0.0-beta.
|
|
36
|
+
"@voidhash/mimic": "1.0.0-beta.7"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@effect/cluster": {
|
package/src/ColdStorage.ts
CHANGED
|
@@ -104,24 +104,33 @@ export namespace InMemory {
|
|
|
104
104
|
export const make = (): Layer.Layer<ColdStorageTag> =>
|
|
105
105
|
Layer.effect(
|
|
106
106
|
ColdStorageTag,
|
|
107
|
-
Effect.
|
|
107
|
+
Effect.fn("cold-storage.in-memory.create")(function* () {
|
|
108
108
|
const store = yield* Ref.make(HashMap.empty<string, StoredDocument>());
|
|
109
109
|
|
|
110
110
|
return {
|
|
111
|
-
load: (documentId)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}),
|
|
111
|
+
load: Effect.fn("cold-storage.load")(function* (documentId: string) {
|
|
112
|
+
const current = yield* Ref.get(store);
|
|
113
|
+
const result = HashMap.get(current, documentId);
|
|
114
|
+
return result._tag === "Some" ? result.value : undefined;
|
|
115
|
+
}),
|
|
117
116
|
|
|
118
|
-
save: (
|
|
119
|
-
|
|
117
|
+
save: Effect.fn("cold-storage.save")(
|
|
118
|
+
function* (documentId: string, document: StoredDocument) {
|
|
119
|
+
yield* Ref.update(store, (map) =>
|
|
120
|
+
HashMap.set(map, documentId, document)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
),
|
|
120
124
|
|
|
121
|
-
delete: (
|
|
122
|
-
|
|
125
|
+
delete: Effect.fn("cold-storage.delete")(
|
|
126
|
+
function* (documentId: string) {
|
|
127
|
+
yield* Ref.update(store, (map) =>
|
|
128
|
+
HashMap.remove(map, documentId)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
),
|
|
123
132
|
};
|
|
124
|
-
})
|
|
133
|
+
})()
|
|
125
134
|
);
|
|
126
135
|
}
|
|
127
136
|
|
package/src/DocumentManager.ts
CHANGED
|
@@ -176,10 +176,8 @@ export const layer = Layer.scoped(
|
|
|
176
176
|
/**
|
|
177
177
|
* Restore a document from storage
|
|
178
178
|
*/
|
|
179
|
-
const restoreDocument = (
|
|
180
|
-
documentId: string
|
|
181
|
-
): Effect.Effect<DocumentInstance<typeof config.schema>, ColdStorageError | HotStorageError> =>
|
|
182
|
-
Effect.gen(function* () {
|
|
179
|
+
const restoreDocument = Effect.fn("document.restore")(
|
|
180
|
+
function* (documentId: string) {
|
|
183
181
|
// 1. Load snapshot from ColdStorage (errors propagate - do not silently fallback)
|
|
184
182
|
const storedDoc = yield* coldStorage.load(documentId);
|
|
185
183
|
|
|
@@ -294,15 +292,14 @@ export const layer = Layer.scoped(
|
|
|
294
292
|
yield* Metric.incrementBy(Metrics.documentsActive, 1);
|
|
295
293
|
|
|
296
294
|
return instance;
|
|
297
|
-
}
|
|
295
|
+
}
|
|
296
|
+
);
|
|
298
297
|
|
|
299
298
|
/**
|
|
300
299
|
* Get or create a document instance
|
|
301
300
|
*/
|
|
302
|
-
const getOrCreateDocument = (
|
|
303
|
-
documentId: string
|
|
304
|
-
): Effect.Effect<DocumentInstance<typeof config.schema>, ColdStorageError | HotStorageError> =>
|
|
305
|
-
Effect.gen(function* () {
|
|
301
|
+
const getOrCreateDocument = Effect.fn("document.get-or-create")(
|
|
302
|
+
function* (documentId: string) {
|
|
306
303
|
const current = yield* Ref.get(store);
|
|
307
304
|
const existing = HashMap.get(current, documentId);
|
|
308
305
|
|
|
@@ -321,7 +318,8 @@ export const layer = Layer.scoped(
|
|
|
321
318
|
);
|
|
322
319
|
|
|
323
320
|
return instance;
|
|
324
|
-
}
|
|
321
|
+
}
|
|
322
|
+
);
|
|
325
323
|
|
|
326
324
|
/**
|
|
327
325
|
* Save a snapshot to ColdStorage derived from WAL entries.
|
|
@@ -329,12 +327,12 @@ export const layer = Layer.scoped(
|
|
|
329
327
|
* Idempotent: skips save if already snapshotted at target version.
|
|
330
328
|
* Truncate failures are non-fatal and will be retried on next snapshot.
|
|
331
329
|
*/
|
|
332
|
-
const saveSnapshot = (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
330
|
+
const saveSnapshot = Effect.fn("document.snapshot.save")(
|
|
331
|
+
function* (
|
|
332
|
+
documentId: string,
|
|
333
|
+
instance: DocumentInstance<typeof config.schema>,
|
|
334
|
+
targetVersion: number
|
|
335
|
+
) {
|
|
338
336
|
const lastSnapshotVersion = yield* Ref.get(instance.lastSnapshotVersion);
|
|
339
337
|
|
|
340
338
|
// Idempotency check: skip if already snapshotted at this version
|
|
@@ -410,16 +408,17 @@ export const layer = Layer.scoped(
|
|
|
410
408
|
error: e,
|
|
411
409
|
})
|
|
412
410
|
);
|
|
413
|
-
}
|
|
411
|
+
}
|
|
412
|
+
);
|
|
414
413
|
|
|
415
414
|
/**
|
|
416
415
|
* Check if snapshot should be triggered
|
|
417
416
|
*/
|
|
418
|
-
const checkSnapshotTriggers = (
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
417
|
+
const checkSnapshotTriggers = Effect.fn("document.snapshot.check-triggers")(
|
|
418
|
+
function* (
|
|
419
|
+
documentId: string,
|
|
420
|
+
instance: DocumentInstance<typeof config.schema>
|
|
421
|
+
) {
|
|
423
422
|
const txCount = yield* Ref.get(instance.transactionsSinceSnapshot);
|
|
424
423
|
const lastTime = yield* Ref.get(instance.lastSnapshotTime);
|
|
425
424
|
const now = Date.now();
|
|
@@ -439,13 +438,14 @@ export const layer = Layer.scoped(
|
|
|
439
438
|
yield* saveSnapshot(documentId, instance, currentVersion);
|
|
440
439
|
return;
|
|
441
440
|
}
|
|
442
|
-
}
|
|
441
|
+
}
|
|
442
|
+
);
|
|
443
443
|
|
|
444
444
|
/**
|
|
445
445
|
* Start background GC fiber
|
|
446
446
|
*/
|
|
447
|
-
const startGCFiber = Effect.
|
|
448
|
-
const gcLoop = Effect.
|
|
447
|
+
const startGCFiber = Effect.fn("document.gc.start")(function* () {
|
|
448
|
+
const gcLoop = Effect.fn("document.gc.loop")(function* () {
|
|
449
449
|
const current = yield* Ref.get(store);
|
|
450
450
|
const now = Date.now();
|
|
451
451
|
const maxIdleMs = Duration.toMillis(config.maxIdleTime);
|
|
@@ -477,18 +477,18 @@ export const layer = Layer.scoped(
|
|
|
477
477
|
});
|
|
478
478
|
|
|
479
479
|
// Run GC every minute
|
|
480
|
-
yield* gcLoop.pipe(
|
|
480
|
+
yield* gcLoop().pipe(
|
|
481
481
|
Effect.repeat(Schedule.spaced("1 minute")),
|
|
482
482
|
Effect.fork
|
|
483
483
|
);
|
|
484
484
|
});
|
|
485
485
|
|
|
486
486
|
// Start GC fiber
|
|
487
|
-
yield* startGCFiber;
|
|
487
|
+
yield* startGCFiber();
|
|
488
488
|
|
|
489
489
|
// Cleanup on shutdown
|
|
490
490
|
yield* Effect.addFinalizer(() =>
|
|
491
|
-
Effect.
|
|
491
|
+
Effect.fn("document-manager.shutdown")(function* () {
|
|
492
492
|
const current = yield* Ref.get(store);
|
|
493
493
|
for (const [documentId, instance] of current) {
|
|
494
494
|
// Best effort save - don't fail shutdown if storage is unavailable
|
|
@@ -501,12 +501,12 @@ export const layer = Layer.scoped(
|
|
|
501
501
|
);
|
|
502
502
|
}
|
|
503
503
|
yield* Effect.logInfo("DocumentManager shutdown complete");
|
|
504
|
-
})
|
|
504
|
+
})()
|
|
505
505
|
);
|
|
506
506
|
|
|
507
507
|
return {
|
|
508
|
-
submit: (
|
|
509
|
-
|
|
508
|
+
submit: Effect.fn("document.transaction.submit")(
|
|
509
|
+
function* (documentId: string, transaction: Transaction.Transaction) {
|
|
510
510
|
const instance = yield* getOrCreateDocument(documentId);
|
|
511
511
|
const submitStartTime = Date.now();
|
|
512
512
|
|
|
@@ -577,28 +577,30 @@ export const layer = Layer.scoped(
|
|
|
577
577
|
success: true as const,
|
|
578
578
|
version: validation.nextVersion,
|
|
579
579
|
};
|
|
580
|
-
}
|
|
580
|
+
}
|
|
581
|
+
),
|
|
581
582
|
|
|
582
|
-
getSnapshot: (
|
|
583
|
-
|
|
583
|
+
getSnapshot: Effect.fn("document.snapshot.get")(
|
|
584
|
+
function* (documentId: string) {
|
|
584
585
|
const instance = yield* getOrCreateDocument(documentId);
|
|
585
586
|
return instance.document.getSnapshot();
|
|
586
|
-
}
|
|
587
|
+
}
|
|
588
|
+
),
|
|
587
589
|
|
|
588
|
-
subscribe: (
|
|
589
|
-
|
|
590
|
+
subscribe: Effect.fn("document.subscribe")(
|
|
591
|
+
function* (documentId: string) {
|
|
590
592
|
const instance = yield* getOrCreateDocument(documentId);
|
|
591
593
|
return Stream.fromPubSub(instance.pubsub);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
594
|
+
}
|
|
595
|
+
),
|
|
596
|
+
|
|
597
|
+
touch: Effect.fn("document.touch")(function* (documentId: string) {
|
|
598
|
+
const current = yield* Ref.get(store);
|
|
599
|
+
const existing = HashMap.get(current, documentId);
|
|
600
|
+
if (existing._tag === "Some") {
|
|
601
|
+
yield* Ref.set(existing.value.lastActivityTime, Date.now());
|
|
602
|
+
}
|
|
603
|
+
}),
|
|
602
604
|
};
|
|
603
605
|
})
|
|
604
606
|
);
|