@voidhash/mimic-effect 1.0.0-beta.16 → 1.0.0-beta.17
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/dist/ColdStorage.cjs +1 -1
- package/dist/ColdStorage.d.cts +2 -2
- package/dist/ColdStorage.d.cts.map +1 -1
- package/dist/ColdStorage.d.mts +2 -2
- package/dist/ColdStorage.d.mts.map +1 -1
- package/dist/ColdStorage.mjs +2 -2
- package/dist/ColdStorage.mjs.map +1 -1
- package/dist/DocumentInstance.cjs +13 -13
- package/dist/DocumentInstance.mjs +13 -13
- package/dist/DocumentInstance.mjs.map +1 -1
- package/dist/Errors.d.cts +8 -8
- package/dist/Errors.d.cts.map +1 -1
- package/dist/Errors.d.mts +8 -8
- package/dist/Errors.d.mts.map +1 -1
- package/dist/HotStorage.cjs +1 -1
- package/dist/HotStorage.d.cts +2 -2
- package/dist/HotStorage.d.mts +2 -2
- package/dist/HotStorage.mjs +2 -2
- package/dist/HotStorage.mjs.map +1 -1
- package/dist/Metrics.cjs +6 -6
- package/dist/Metrics.d.cts +21 -23
- package/dist/Metrics.d.cts.map +1 -1
- package/dist/Metrics.d.mts +21 -23
- package/dist/Metrics.d.mts.map +1 -1
- package/dist/Metrics.mjs +7 -7
- package/dist/Metrics.mjs.map +1 -1
- package/dist/MimicAuthService.cjs +1 -1
- package/dist/MimicAuthService.d.cts +2 -2
- package/dist/MimicAuthService.d.cts.map +1 -1
- package/dist/MimicAuthService.d.mts +2 -2
- package/dist/MimicAuthService.d.mts.map +1 -1
- package/dist/MimicAuthService.mjs +2 -2
- package/dist/MimicAuthService.mjs.map +1 -1
- package/dist/MimicClusterServerEngine.cjs +38 -41
- package/dist/MimicClusterServerEngine.d.cts +1 -1
- package/dist/MimicClusterServerEngine.d.mts +1 -1
- package/dist/MimicClusterServerEngine.mjs +31 -34
- package/dist/MimicClusterServerEngine.mjs.map +1 -1
- package/dist/MimicServer.cjs +23 -23
- package/dist/MimicServer.d.cts +3 -3
- package/dist/MimicServer.d.cts.map +1 -1
- package/dist/MimicServer.d.mts +3 -3
- package/dist/MimicServer.d.mts.map +1 -1
- package/dist/MimicServer.mjs +22 -22
- package/dist/MimicServer.mjs.map +1 -1
- package/dist/MimicServerEngine.cjs +13 -13
- package/dist/MimicServerEngine.d.cts +2 -2
- package/dist/MimicServerEngine.d.mts +2 -2
- package/dist/MimicServerEngine.mjs +14 -14
- package/dist/MimicServerEngine.mjs.map +1 -1
- package/dist/PresenceManager.cjs +4 -4
- package/dist/PresenceManager.d.cts +2 -2
- package/dist/PresenceManager.d.mts +2 -2
- package/dist/PresenceManager.mjs +5 -5
- package/dist/PresenceManager.mjs.map +1 -1
- package/dist/Types.d.cts +1 -1
- package/dist/Types.d.mts +1 -1
- package/dist/testing/ColdStorageTestSuite.cjs +3 -3
- package/dist/testing/ColdStorageTestSuite.mjs +3 -3
- package/dist/testing/ColdStorageTestSuite.mjs.map +1 -1
- package/dist/testing/HotStorageTestSuite.cjs +13 -13
- package/dist/testing/HotStorageTestSuite.mjs +13 -13
- package/dist/testing/HotStorageTestSuite.mjs.map +1 -1
- package/dist/testing/StorageIntegrationTestSuite.cjs +3 -3
- package/dist/testing/StorageIntegrationTestSuite.mjs +3 -3
- package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -1
- package/dist/testing/types.d.cts +1 -1
- package/dist/testing/types.d.cts.map +1 -1
- package/dist/testing/types.d.mts +3 -3
- package/dist/testing/types.d.mts.map +1 -1
- package/package.json +17 -21
- package/src/ColdStorage.ts +4 -5
- package/src/DocumentInstance.ts +13 -13
- package/src/HotStorage.ts +3 -3
- package/src/Metrics.ts +22 -16
- package/src/MimicAuthService.ts +3 -3
- package/src/MimicClusterServerEngine.ts +35 -35
- package/src/MimicServer.ts +26 -30
- package/src/MimicServerEngine.ts +15 -15
- package/src/PresenceManager.ts +6 -6
- package/src/Types.ts +1 -1
- package/src/testing/ColdStorageTestSuite.ts +3 -3
- package/src/testing/HotStorageTestSuite.ts +17 -17
- package/src/testing/StorageIntegrationTestSuite.ts +3 -3
- package/.turbo/turbo-build.log +0 -154
- package/tests/ColdStorage.test.ts +0 -24
- package/tests/DocumentInstance.test.ts +0 -669
- package/tests/HotStorage.test.ts +0 -24
- package/tests/MimicAuthService.test.ts +0 -153
- package/tests/MimicClusterServerEngine.test.ts +0 -587
- package/tests/MimicServer.test.ts +0 -142
- package/tests/MimicServerEngine.test.ts +0 -547
- package/tests/PresenceManager.test.ts +0 -380
- package/tests/Protocol.test.ts +0 -190
- package/tests/StorageIntegration.test.ts +0 -259
- package/tsconfig.build.json +0 -24
- package/tsconfig.json +0 -8
- package/tsdown.config.ts +0 -18
- package/vitest.mts +0 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MimicServerEngine.mjs","names":["Metrics.documentsEvicted","Metrics.documentsActive","Metrics.storageIdleSnapshots","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 HashMap,\n Layer,\n Metric,\n Ref,\n Schedule,\n Scope,\n Stream,\n} from \"effect\";\nimport type { 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 DocumentInstance,\n type SubmitResult,\n type DocumentInstance as DocumentInstanceType,\n} from \"./DocumentInstance\";\nimport {\n PresenceManagerTag,\n layer as presenceManagerLayer,\n} from \"./PresenceManager\";\nimport * as Metrics from \"./Metrics\";\nimport type { ColdStorageError, HotStorageError } from \"./Errors\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Error type for MimicServerEngine operations\n */\nexport type MimicServerEngineError = ColdStorageError | HotStorageError;\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 MimicServerEngineError if storage is unavailable.\n */\n readonly submit: (\n documentId: string,\n transaction: Transaction.Transaction\n ) => Effect.Effect<SubmitResult, MimicServerEngineError>;\n\n /**\n * Get document snapshot (current state and version).\n * Returns flat state format used for storage and synchronization.\n * May fail with MimicServerEngineError if storage is unavailable.\n */\n readonly getSnapshot: (\n documentId: string\n ) => Effect.Effect<{ state: unknown; version: number }, MimicServerEngineError>;\n\n /**\n * Get tree-like snapshot for rendering.\n * Returns a readonly structure where trees are converted from\n * flat state to nested/hierarchical structure suitable for UI rendering.\n * May fail with MimicServerEngineError if storage is unavailable.\n */\n readonly getTreeSnapshot: (\n documentId: string\n ) => Effect.Effect<unknown, MimicServerEngineError>;\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 MimicServerEngineError if storage is unavailable.\n */\n readonly subscribe: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<Protocol.ServerMessage, never, never>, MimicServerEngineError, 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;\nconst DEFAULT_SNAPSHOT_IDLE_TIMEOUT = Duration.seconds(30);\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 idleTimeout: config.snapshot?.idleTimeout\n ? Duration.decode(config.snapshot.idleTimeout)\n : DEFAULT_SNAPSHOT_IDLE_TIMEOUT,\n },\n});\n\n// =============================================================================\n// Internal Types\n// =============================================================================\n\n/**\n * Store entry for a document instance with last activity time\n */\ninterface StoreEntry<TSchema extends Primitive.AnyPrimitive> {\n readonly instance: DocumentInstanceType<TSchema>;\n readonly lastActivityTime: Ref.Ref<number>;\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 return Layer.scoped(\n MimicServerEngineTag,\n Effect.gen(function* () {\n const coldStorage = yield* ColdStorageTag;\n const hotStorage = yield* HotStorageTag;\n const presenceManager = yield* PresenceManagerTag;\n\n // Store: documentId -> StoreEntry\n const store = yield* Ref.make(\n HashMap.empty<string, StoreEntry<TSchema>>()\n );\n\n /**\n * Get or create a document instance\n */\n const getOrCreateDocument = Effect.fn(\"engine.document.get-or-create\")(\n function* (documentId: string) {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n\n if (existing._tag === \"Some\") {\n // Update activity time\n yield* Ref.set(existing.value.lastActivityTime, Date.now());\n return existing.value.instance;\n }\n\n // Create new document instance\n const instance = yield* DocumentInstance.make(\n documentId,\n {\n schema: config.schema,\n initial: config.initial,\n maxTransactionHistory: resolvedConfig.maxTransactionHistory,\n snapshot: resolvedConfig.snapshot,\n },\n coldStorage,\n hotStorage\n );\n\n const lastActivityTime = yield* Ref.make(Date.now());\n\n // Store it\n yield* Ref.update(store, (map) =>\n HashMap.set(map, documentId, { instance, lastActivityTime })\n );\n\n return instance;\n }\n );\n\n /**\n * Start background GC fiber\n */\n const startGCFiber = Effect.fn(\"engine.gc.start\")(function* () {\n const gcLoop = Effect.fn(\"engine.gc.loop\")(function* () {\n const current = yield* Ref.get(store);\n const now = Date.now();\n const maxIdleMs = Duration.toMillis(resolvedConfig.maxIdleTime);\n\n for (const [documentId, entry] of current) {\n const lastActivity = yield* Ref.get(entry.lastActivityTime);\n if (now - lastActivity >= maxIdleMs) {\n // Save final snapshot before eviction (best effort)\n yield* Effect.catchAll(entry.instance.saveSnapshot(), (e) =>\n Effect.logError(\"Failed to save snapshot during eviction\", {\n documentId,\n error: e,\n })\n );\n\n // Remove from store\n yield* Ref.update(store, (map) => HashMap.remove(map, documentId));\n\n // Track eviction metrics\n yield* Metric.increment(Metrics.documentsEvicted);\n yield* Metric.incrementBy(Metrics.documentsActive, -1);\n\n yield* Effect.logInfo(\"Document evicted due to idle timeout\", {\n documentId,\n });\n }\n }\n });\n\n // Run GC every minute\n yield* gcLoop().pipe(\n Effect.repeat(Schedule.spaced(\"1 minute\")),\n Effect.fork\n );\n });\n\n // Start GC fiber\n yield* startGCFiber();\n\n /**\n * Start background snapshot fiber for idle documents.\n * This ensures documents with unsnapshot transactions get persisted\n * even without new transaction activity.\n */\n const startSnapshotFiber = Effect.fn(\"engine.snapshot.fiber.start\")(function* () {\n const idleTimeoutMs = Duration.toMillis(resolvedConfig.snapshot.idleTimeout);\n\n // Skip if idle snapshots are disabled\n if (idleTimeoutMs <= 0) {\n return;\n }\n\n const snapshotLoop = Effect.fn(\"engine.snapshot.loop\")(function* () {\n const current = yield* Ref.get(store);\n const now = Date.now();\n\n for (const [documentId, entry] of current) {\n // Check if document has been idle long enough\n const lastActivity = yield* Ref.get(entry.lastActivityTime);\n const idleDuration = now - lastActivity;\n\n if (idleDuration < idleTimeoutMs) {\n // Document not idle long enough, skip\n continue;\n }\n\n // Check if document has unsnapshot transactions\n const needs = yield* entry.instance.needsSnapshot();\n if (!needs) {\n continue;\n }\n\n // Save snapshot (with error handling)\n yield* Effect.catchAll(entry.instance.saveSnapshot(), (e) =>\n Effect.logWarning(\"Periodic snapshot save failed\", {\n documentId,\n error: e,\n })\n );\n\n // Track metric\n yield* Metric.increment(Metrics.storageIdleSnapshots);\n }\n });\n\n // Run snapshot check every 10 seconds\n yield* snapshotLoop().pipe(\n Effect.repeat(Schedule.spaced(\"10 seconds\")),\n Effect.fork\n );\n });\n\n // Start snapshot fiber\n yield* startSnapshotFiber();\n\n // Cleanup on shutdown\n yield* Effect.addFinalizer(() =>\n Effect.fn(\"engine.shutdown\")(function* () {\n const current = yield* Ref.get(store);\n for (const [documentId, entry] of current) {\n // Best effort save - don't fail shutdown if storage is unavailable\n yield* Effect.catchAll(entry.instance.saveSnapshot(), (e) =>\n Effect.logError(\"Failed to save snapshot during shutdown\", {\n documentId,\n error: e,\n })\n );\n }\n yield* Effect.logInfo(\"MimicServerEngine shutdown complete\");\n })()\n );\n\n const engine: MimicServerEngine = {\n submit: (documentId, transaction) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return yield* instance.submit(transaction);\n }),\n\n getSnapshot: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return instance.getSnapshot();\n }),\n\n getTreeSnapshot: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return instance.toSnapshot();\n }),\n\n subscribe: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return Stream.fromPubSub(instance.pubsub) as Stream.Stream<\n Protocol.ServerMessage,\n never,\n never\n >;\n }),\n\n touch: (documentId) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n if (existing._tag === \"Some\") {\n yield* Ref.set(existing.value.lastActivityTime, Date.now());\n }\n }),\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(presenceManagerLayer));\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":"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"}
|
|
1
|
+
{"version":3,"file":"MimicServerEngine.mjs","names":["Metrics.documentsEvicted","Metrics.documentsActive","Metrics.storageIdleSnapshots","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 ServiceMap,\n Duration,\n Effect,\n HashMap,\n Layer,\n Metric,\n Ref,\n Schedule,\n Scope,\n Stream,\n} from \"effect\";\nimport type { 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 DocumentInstance,\n type SubmitResult,\n type DocumentInstance as DocumentInstanceType,\n} from \"./DocumentInstance\";\nimport {\n PresenceManagerTag,\n layer as presenceManagerLayer,\n} from \"./PresenceManager\";\nimport * as Metrics from \"./Metrics\";\nimport type { ColdStorageError, HotStorageError } from \"./Errors\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Error type for MimicServerEngine operations\n */\nexport type MimicServerEngineError = ColdStorageError | HotStorageError;\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 MimicServerEngineError if storage is unavailable.\n */\n readonly submit: (\n documentId: string,\n transaction: Transaction.Transaction\n ) => Effect.Effect<SubmitResult, MimicServerEngineError>;\n\n /**\n * Get document snapshot (current state and version).\n * Returns flat state format used for storage and synchronization.\n * May fail with MimicServerEngineError if storage is unavailable.\n */\n readonly getSnapshot: (\n documentId: string\n ) => Effect.Effect<{ state: unknown; version: number }, MimicServerEngineError>;\n\n /**\n * Get tree-like snapshot for rendering.\n * Returns a readonly structure where trees are converted from\n * flat state to nested/hierarchical structure suitable for UI rendering.\n * May fail with MimicServerEngineError if storage is unavailable.\n */\n readonly getTreeSnapshot: (\n documentId: string\n ) => Effect.Effect<unknown, MimicServerEngineError>;\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 MimicServerEngineError if storage is unavailable.\n */\n readonly subscribe: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<Protocol.ServerMessage, never, never>, MimicServerEngineError, 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 ServiceMap.Service<MimicServerEngineTag, MimicServerEngine>()(\n \"@voidhash/mimic-effect/MimicServerEngine\"\n) {}\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;\nconst DEFAULT_SNAPSHOT_IDLE_TIMEOUT = Duration.seconds(30);\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.fromInputUnsafe(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.fromInputUnsafe(config.snapshot.interval)\n : DEFAULT_SNAPSHOT_INTERVAL,\n transactionThreshold:\n config.snapshot?.transactionThreshold ?? DEFAULT_SNAPSHOT_THRESHOLD,\n idleTimeout: config.snapshot?.idleTimeout\n ? Duration.fromInputUnsafe(config.snapshot.idleTimeout)\n : DEFAULT_SNAPSHOT_IDLE_TIMEOUT,\n },\n});\n\n// =============================================================================\n// Internal Types\n// =============================================================================\n\n/**\n * Store entry for a document instance with last activity time\n */\ninterface StoreEntry<TSchema extends Primitive.AnyPrimitive> {\n readonly instance: DocumentInstanceType<TSchema>;\n readonly lastActivityTime: Ref.Ref<number>;\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 return Layer.effect(\n MimicServerEngineTag,\n Effect.gen(function* () {\n const coldStorage = yield* ColdStorageTag;\n const hotStorage = yield* HotStorageTag;\n const presenceManager = yield* PresenceManagerTag;\n\n // Store: documentId -> StoreEntry\n const store = yield* Ref.make(\n HashMap.empty<string, StoreEntry<TSchema>>()\n );\n\n /**\n * Get or create a document instance\n */\n const getOrCreateDocument = Effect.fn(\"engine.document.get-or-create\")(\n function* (documentId: string) {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n\n if (existing._tag === \"Some\") {\n // Update activity time\n yield* Ref.set(existing.value.lastActivityTime, Date.now());\n return existing.value.instance;\n }\n\n // Create new document instance\n const instance = yield* DocumentInstance.make(\n documentId,\n {\n schema: config.schema,\n initial: config.initial,\n maxTransactionHistory: resolvedConfig.maxTransactionHistory,\n snapshot: resolvedConfig.snapshot,\n },\n coldStorage,\n hotStorage\n );\n\n const lastActivityTime = yield* Ref.make(Date.now());\n\n // Store it\n yield* Ref.update(store, (map) =>\n HashMap.set(map, documentId, { instance, lastActivityTime })\n );\n\n return instance;\n }\n );\n\n /**\n * Start background GC fiber\n */\n const startGCFiber = Effect.fn(\"engine.gc.start\")(function* () {\n const gcLoop = Effect.fn(\"engine.gc.loop\")(function* () {\n const current = yield* Ref.get(store);\n const now = Date.now();\n const maxIdleMs = Duration.toMillis(resolvedConfig.maxIdleTime);\n\n for (const [documentId, entry] of current) {\n const lastActivity = yield* Ref.get(entry.lastActivityTime);\n if (now - lastActivity >= maxIdleMs) {\n // Save final snapshot before eviction (best effort)\n yield* Effect[\"catch\"](entry.instance.saveSnapshot(), (e) =>\n Effect.logError(\"Failed to save snapshot during eviction\", {\n documentId,\n error: e,\n })\n );\n\n // Remove from store\n yield* Ref.update(store, (map) => HashMap.remove(map, documentId));\n\n // Track eviction metrics\n yield* Metric.update(Metrics.documentsEvicted, 1);\n yield* Metric.update(Metrics.documentsActive, -1);\n\n yield* Effect.logInfo(\"Document evicted due to idle timeout\", {\n documentId,\n });\n }\n }\n });\n\n // Run GC every minute\n yield* gcLoop().pipe(\n Effect.repeat(Schedule.spaced(\"1 minute\")),\n Effect.forkChild\n );\n });\n\n // Start GC fiber\n yield* startGCFiber();\n\n /**\n * Start background snapshot fiber for idle documents.\n * This ensures documents with unsnapshot transactions get persisted\n * even without new transaction activity.\n */\n const startSnapshotFiber = Effect.fn(\"engine.snapshot.fiber.start\")(function* () {\n const idleTimeoutMs = Duration.toMillis(resolvedConfig.snapshot.idleTimeout);\n\n // Skip if idle snapshots are disabled\n if (idleTimeoutMs <= 0) {\n return;\n }\n\n const snapshotLoop = Effect.fn(\"engine.snapshot.loop\")(function* () {\n const current = yield* Ref.get(store);\n const now = Date.now();\n\n for (const [documentId, entry] of current) {\n // Check if document has been idle long enough\n const lastActivity = yield* Ref.get(entry.lastActivityTime);\n const idleDuration = now - lastActivity;\n\n if (idleDuration < idleTimeoutMs) {\n // Document not idle long enough, skip\n continue;\n }\n\n // Check if document has unsnapshot transactions\n const needs = yield* entry.instance.needsSnapshot();\n if (!needs) {\n continue;\n }\n\n // Save snapshot (with error handling)\n yield* Effect[\"catch\"](entry.instance.saveSnapshot(), (e) =>\n Effect.logWarning(\"Periodic snapshot save failed\", {\n documentId,\n error: e,\n })\n );\n\n // Track metric\n yield* Metric.update(Metrics.storageIdleSnapshots, 1);\n }\n });\n\n // Run snapshot check every 10 seconds\n yield* snapshotLoop().pipe(\n Effect.repeat(Schedule.spaced(\"10 seconds\")),\n Effect.forkChild\n );\n });\n\n // Start snapshot fiber\n yield* startSnapshotFiber();\n\n // Cleanup on shutdown\n yield* Effect.addFinalizer(() =>\n Effect.fn(\"engine.shutdown\")(function* () {\n const current = yield* Ref.get(store);\n for (const [documentId, entry] of current) {\n // Best effort save - don't fail shutdown if storage is unavailable\n yield* Effect[\"catch\"](entry.instance.saveSnapshot(), (e) =>\n Effect.logError(\"Failed to save snapshot during shutdown\", {\n documentId,\n error: e,\n })\n );\n }\n yield* Effect.logInfo(\"MimicServerEngine shutdown complete\");\n })()\n );\n\n const engine: MimicServerEngine = {\n submit: (documentId, transaction) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return yield* instance.submit(transaction);\n }),\n\n getSnapshot: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return instance.getSnapshot();\n }),\n\n getTreeSnapshot: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return instance.toSnapshot();\n }),\n\n subscribe: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return Stream.fromPubSub(instance.pubsub) as Stream.Stream<\n Protocol.ServerMessage,\n never,\n never\n >;\n }),\n\n touch: (documentId) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n if (existing._tag === \"Some\") {\n yield* Ref.set(existing.value.lastActivityTime, Date.now());\n }\n }),\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(presenceManagerLayer));\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":"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"}
|
package/dist/PresenceManager.cjs
CHANGED
|
@@ -11,7 +11,7 @@ let effect = require("effect");
|
|
|
11
11
|
/**
|
|
12
12
|
* Context tag for PresenceManager service
|
|
13
13
|
*/
|
|
14
|
-
var PresenceManagerTag = class extends effect.
|
|
14
|
+
var PresenceManagerTag = class extends effect.ServiceMap.Service()("@voidhash/mimic-effect/PresenceManager") {};
|
|
15
15
|
/**
|
|
16
16
|
* Create the PresenceManager layer.
|
|
17
17
|
*/
|
|
@@ -48,8 +48,8 @@ const layer = effect.Layer.effect(PresenceManagerTag, effect.Effect.gen(function
|
|
|
48
48
|
if (existing._tag === "None") return map;
|
|
49
49
|
return effect.HashMap.set(map, documentId, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, existing.value), {}, { presences: effect.HashMap.set(existing.value.presences, connectionId, entry) }));
|
|
50
50
|
});
|
|
51
|
-
yield* effect.Metric.
|
|
52
|
-
yield* effect.Metric.
|
|
51
|
+
yield* effect.Metric.update(require_Metrics.presenceUpdates, 1);
|
|
52
|
+
yield* effect.Metric.update(require_Metrics.presenceActive, 1);
|
|
53
53
|
const event = {
|
|
54
54
|
type: "presence_update",
|
|
55
55
|
id: connectionId,
|
|
@@ -68,7 +68,7 @@ const layer = effect.Layer.effect(PresenceManagerTag, effect.Effect.gen(function
|
|
|
68
68
|
if (docState._tag === "None") return map;
|
|
69
69
|
return effect.HashMap.set(map, documentId, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, docState.value), {}, { presences: effect.HashMap.remove(docState.value.presences, connectionId) }));
|
|
70
70
|
});
|
|
71
|
-
yield* effect.Metric.
|
|
71
|
+
yield* effect.Metric.update(require_Metrics.presenceActive, -1);
|
|
72
72
|
const event = {
|
|
73
73
|
type: "presence_remove",
|
|
74
74
|
id: connectionId
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PresenceEntry, PresenceEvent, PresenceSnapshot } from "./Types.cjs";
|
|
2
|
-
import {
|
|
2
|
+
import { Effect, Layer, Scope, ServiceMap, Stream } from "effect";
|
|
3
3
|
|
|
4
4
|
//#region src/PresenceManager.d.ts
|
|
5
5
|
|
|
@@ -28,7 +28,7 @@ interface PresenceManager {
|
|
|
28
28
|
*/
|
|
29
29
|
readonly subscribe: (documentId: string) => Effect.Effect<Stream.Stream<PresenceEvent>, never, Scope.Scope>;
|
|
30
30
|
}
|
|
31
|
-
declare const PresenceManagerTag_base:
|
|
31
|
+
declare const PresenceManagerTag_base: ServiceMap.ServiceClass<PresenceManagerTag, "@voidhash/mimic-effect/PresenceManager", PresenceManager>;
|
|
32
32
|
/**
|
|
33
33
|
* Context tag for PresenceManager service
|
|
34
34
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PresenceEntry, PresenceEvent, PresenceSnapshot } from "./Types.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { Effect, Layer, Scope, ServiceMap, Stream } from "effect";
|
|
3
3
|
|
|
4
4
|
//#region src/PresenceManager.d.ts
|
|
5
5
|
|
|
@@ -28,7 +28,7 @@ interface PresenceManager {
|
|
|
28
28
|
*/
|
|
29
29
|
readonly subscribe: (documentId: string) => Effect.Effect<Stream.Stream<PresenceEvent>, never, Scope.Scope>;
|
|
30
30
|
}
|
|
31
|
-
declare const PresenceManagerTag_base:
|
|
31
|
+
declare const PresenceManagerTag_base: ServiceMap.ServiceClass<PresenceManagerTag, "@voidhash/mimic-effect/PresenceManager", PresenceManager>;
|
|
32
32
|
/**
|
|
33
33
|
* Context tag for PresenceManager service
|
|
34
34
|
*/
|
package/dist/PresenceManager.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { presenceActive, presenceUpdates } from "./Metrics.mjs";
|
|
2
2
|
import { _objectSpread2 } from "./_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { Effect, HashMap, Layer, Metric, PubSub, Ref, ServiceMap, Stream } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/PresenceManager.ts
|
|
6
6
|
/**
|
|
@@ -11,7 +11,7 @@ import { Context, Effect, HashMap, Layer, Metric, PubSub, Ref, Stream } from "ef
|
|
|
11
11
|
/**
|
|
12
12
|
* Context tag for PresenceManager service
|
|
13
13
|
*/
|
|
14
|
-
var PresenceManagerTag = class extends
|
|
14
|
+
var PresenceManagerTag = class extends ServiceMap.Service()("@voidhash/mimic-effect/PresenceManager") {};
|
|
15
15
|
/**
|
|
16
16
|
* Create the PresenceManager layer.
|
|
17
17
|
*/
|
|
@@ -48,8 +48,8 @@ const layer = Layer.effect(PresenceManagerTag, Effect.gen(function* () {
|
|
|
48
48
|
if (existing._tag === "None") return map;
|
|
49
49
|
return HashMap.set(map, documentId, _objectSpread2(_objectSpread2({}, existing.value), {}, { presences: HashMap.set(existing.value.presences, connectionId, entry) }));
|
|
50
50
|
});
|
|
51
|
-
yield* Metric.
|
|
52
|
-
yield* Metric.
|
|
51
|
+
yield* Metric.update(presenceUpdates, 1);
|
|
52
|
+
yield* Metric.update(presenceActive, 1);
|
|
53
53
|
const event = {
|
|
54
54
|
type: "presence_update",
|
|
55
55
|
id: connectionId,
|
|
@@ -68,7 +68,7 @@ const layer = Layer.effect(PresenceManagerTag, Effect.gen(function* () {
|
|
|
68
68
|
if (docState._tag === "None") return map;
|
|
69
69
|
return HashMap.set(map, documentId, _objectSpread2(_objectSpread2({}, docState.value), {}, { presences: HashMap.remove(docState.value.presences, connectionId) }));
|
|
70
70
|
});
|
|
71
|
-
yield* Metric.
|
|
71
|
+
yield* Metric.update(presenceActive, -1);
|
|
72
72
|
const event = {
|
|
73
73
|
type: "presence_remove",
|
|
74
74
|
id: connectionId
|
|
@@ -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
|
|
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 Effect,\n HashMap,\n Layer,\n Metric,\n PubSub,\n Ref,\n Scope,\n ServiceMap,\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 ServiceMap.Service<PresenceManagerTag, PresenceManager>()(\n \"@voidhash/mimic-effect/PresenceManager\"\n) {}\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.update(Metrics.presenceUpdates, 1);\n yield* Metric.update(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.update(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,WAAW,SAA8C,CAC/F,yCACD,CAAC;;;;AAqBF,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,OAAOC,iBAAyB,EAAE;AAChD,UAAO,OAAO,OAAOC,gBAAwB,EAAE;GAG/C,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,OAAOD,gBAAwB,GAAG;GAGhD,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/Types.d.cts
CHANGED
|
@@ -79,7 +79,7 @@ type PresenceEvent = PresenceUpdateEvent | PresenceRemoveEvent;
|
|
|
79
79
|
/**
|
|
80
80
|
* Duration input type - matches Effect's DurationInput
|
|
81
81
|
*/
|
|
82
|
-
type DurationInput = Duration.
|
|
82
|
+
type DurationInput = Duration.Input;
|
|
83
83
|
/**
|
|
84
84
|
* Snapshot configuration
|
|
85
85
|
*/
|
package/dist/Types.d.mts
CHANGED
|
@@ -79,7 +79,7 @@ type PresenceEvent = PresenceUpdateEvent | PresenceRemoveEvent;
|
|
|
79
79
|
/**
|
|
80
80
|
* Duration input type - matches Effect's DurationInput
|
|
81
81
|
*/
|
|
82
|
-
type DurationInput = Duration.
|
|
82
|
+
type DurationInput = Duration.Input;
|
|
83
83
|
/**
|
|
84
84
|
* Snapshot configuration
|
|
85
85
|
*/
|
|
@@ -482,11 +482,11 @@ const runAll = () => effect.Effect.gen(function* () {
|
|
|
482
482
|
const passed = [];
|
|
483
483
|
const failed = [];
|
|
484
484
|
for (const test of tests) {
|
|
485
|
-
const result = yield* effect.Effect.
|
|
486
|
-
if (result._tag === "
|
|
485
|
+
const result = yield* effect.Effect.result(test.run);
|
|
486
|
+
if (result._tag === "Success") passed.push(test);
|
|
487
487
|
else failed.push({
|
|
488
488
|
test,
|
|
489
|
-
error: result.
|
|
489
|
+
error: result.failure
|
|
490
490
|
});
|
|
491
491
|
}
|
|
492
492
|
return {
|
|
@@ -482,11 +482,11 @@ const runAll = () => Effect.gen(function* () {
|
|
|
482
482
|
const passed = [];
|
|
483
483
|
const failed = [];
|
|
484
484
|
for (const test of tests) {
|
|
485
|
-
const result = yield* Effect.
|
|
486
|
-
if (result._tag === "
|
|
485
|
+
const result = yield* Effect.result(test.run);
|
|
486
|
+
if (result._tag === "Success") passed.push(test);
|
|
487
487
|
else failed.push({
|
|
488
488
|
test,
|
|
489
|
-
error: result.
|
|
489
|
+
error: result.failure
|
|
490
490
|
});
|
|
491
491
|
}
|
|
492
492
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ColdStorageTestSuite.mjs","names":["tests: StorageTestCase<ColdStorageTestError, ColdStorageTag>[]","doc: StoredDocument","passed: StorageTestCase<ColdStorageTestError, ColdStorageTag>[]","failed: Array<{\n test: StorageTestCase<ColdStorageTestError, ColdStorageTag>;\n error: ColdStorageTestError;\n }>"],"sources":["../../src/testing/ColdStorageTestSuite.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect/testing - ColdStorage Test Suite\n *\n * Comprehensive test suite for ColdStorage adapter implementations.\n * These tests verify that an adapter correctly implements the ColdStorage interface\n * and can reliably store/retrieve document snapshots without data loss.\n */\nimport { Effect } from \"effect\";\nimport { ColdStorageTag } from \"../ColdStorage\";\nimport type { ColdStorageError } from \"../Errors\";\nimport type { StoredDocument } from \"../Types\";\nimport type { StorageTestCase, TestResults } from \"./types\";\nimport { TestError } from \"./types\";\nimport {\n assertEqual,\n assertUndefined,\n assertDefined,\n} from \"./assertions\";\n\n/**\n * Error type for ColdStorage tests - can be either a TestError or a ColdStorageError\n */\nexport type ColdStorageTestError = TestError | ColdStorageError;\n\n// =============================================================================\n// Categories\n// =============================================================================\n\nexport const Categories = {\n BasicOperations: \"Basic Operations\",\n DataIntegrity: \"Data Integrity\",\n VersionHandling: \"Version Handling\",\n TimestampHandling: \"Timestamp Handling\",\n DocumentIdEdgeCases: \"Document ID Edge Cases\",\n DocumentIsolation: \"Document Isolation\",\n ConsistencyGuarantees: \"Consistency Guarantees\",\n} as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\nconst makeDoc = (overrides: Partial<StoredDocument> = {}): StoredDocument => ({\n state: { title: \"Test Document\" },\n version: 1,\n schemaVersion: 1,\n savedAt: Date.now(),\n ...overrides,\n});\n\nconst generateLargeState = (sizeKB: number): Record<string, unknown> => {\n const chars = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n const targetBytes = sizeKB * 1024;\n let content = \"\";\n while (content.length < targetBytes) {\n content += chars[Math.floor(Math.random() * chars.length)];\n }\n return { content, padding: Array(100).fill(\"x\") };\n};\n\n// =============================================================================\n// Test Definitions\n// =============================================================================\n\nconst tests: StorageTestCase<ColdStorageTestError, ColdStorageTag>[] = [\n // ---------------------------------------------------------------------------\n // Basic Operations\n // ---------------------------------------------------------------------------\n {\n name: \"load returns undefined for non-existent document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const result = yield* storage.load(\"non-existent-doc-id-12345\");\n yield* assertUndefined(result, \"Expected undefined for non-existent document\");\n }),\n },\n\n {\n name: \"save then load returns exact same document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc();\n yield* storage.save(\"test-doc-1\", doc);\n const loaded = yield* storage.load(\"test-doc-1\");\n yield* assertEqual(loaded, doc, \"Loaded document should match saved document\");\n }),\n },\n\n {\n name: \"save overwrites existing document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { title: \"First\" }, version: 1 });\n const doc2 = makeDoc({ state: { title: \"Second\" }, version: 2 });\n yield* storage.save(\"test-doc-2\", doc1);\n yield* storage.save(\"test-doc-2\", doc2);\n const loaded = yield* storage.load(\"test-doc-2\");\n yield* assertEqual(loaded, doc2, \"Should return the second (overwritten) document\");\n }),\n },\n\n {\n name: \"delete removes document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc();\n yield* storage.save(\"test-doc-3\", doc);\n yield* storage.delete(\"test-doc-3\");\n const loaded = yield* storage.load(\"test-doc-3\");\n yield* assertUndefined(loaded, \"Document should be undefined after deletion\");\n }),\n },\n\n {\n name: \"delete on non-existent document does not error\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n yield* storage.delete(\"definitely-non-existent-doc-xyz\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Data Integrity & Serialization\n // ---------------------------------------------------------------------------\n {\n name: \"all StoredDocument fields are preserved\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc: StoredDocument = {\n state: { nested: { value: 42 } },\n version: 123,\n schemaVersion: 2,\n savedAt: 1704067200000,\n };\n yield* storage.save(\"fields-test\", doc);\n const loaded = yield* storage.load(\"fields-test\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.state, doc.state, \"state should be preserved\");\n yield* assertEqual(definedLoaded.version, doc.version, \"version should be preserved\");\n yield* assertEqual(definedLoaded.schemaVersion, doc.schemaVersion, \"schemaVersion should be preserved\");\n yield* assertEqual(definedLoaded.savedAt, doc.savedAt, \"savedAt should be preserved\");\n }),\n },\n\n {\n name: \"complex nested state objects survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const complexState = {\n level1: {\n level2: {\n level3: {\n value: \"deep\",\n array: [1, 2, { nested: true }],\n },\n },\n sibling: \"value\",\n },\n topLevel: 42,\n };\n const doc = makeDoc({ state: complexState });\n yield* storage.save(\"complex-state\", doc);\n const loaded = yield* storage.load(\"complex-state\");\n yield* assertEqual(loaded, doc, \"Complex nested state should survive roundtrip\");\n }),\n },\n\n {\n name: \"arrays in state survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({\n state: {\n numbers: [1, 2, 3, 4, 5],\n strings: [\"a\", \"b\", \"c\"],\n mixed: [1, \"two\", { three: 3 }, [4]],\n empty: [],\n },\n });\n yield* storage.save(\"arrays-test\", doc);\n const loaded = yield* storage.load(\"arrays-test\");\n yield* assertEqual(loaded, doc, \"Arrays should survive roundtrip\");\n }),\n },\n\n {\n name: \"null values in state survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({\n state: {\n nullValue: null,\n nested: { alsoNull: null },\n array: [null, 1, null],\n },\n });\n yield* storage.save(\"null-test\", doc);\n const loaded = yield* storage.load(\"null-test\");\n yield* assertEqual(loaded, doc, \"null values should survive roundtrip\");\n }),\n },\n\n {\n name: \"empty object survives roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ state: {} });\n yield* storage.save(\"empty-obj\", doc);\n const loaded = yield* storage.load(\"empty-obj\");\n yield* assertEqual(loaded, doc, \"Empty object should survive roundtrip\");\n }),\n },\n\n {\n name: \"empty array survives roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ state: [] });\n yield* storage.save(\"empty-arr\", doc);\n const loaded = yield* storage.load(\"empty-arr\");\n yield* assertEqual(loaded, doc, \"Empty array should survive roundtrip\");\n }),\n },\n\n {\n name: \"unicode strings in state survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({\n state: {\n emoji: \"Hello! How are you?\",\n chinese: \"Chinese Characters\",\n arabic: \"Arabic Letters\",\n special: \"Combined: Cafe\",\n },\n });\n yield* storage.save(\"unicode-test\", doc);\n const loaded = yield* storage.load(\"unicode-test\");\n yield* assertEqual(loaded, doc, \"Unicode strings should survive roundtrip\");\n }),\n },\n\n {\n name: \"large state objects (100KB+) survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const largeState = generateLargeState(100);\n const doc = makeDoc({ state: largeState });\n yield* storage.save(\"large-doc\", doc);\n const loaded = yield* storage.load(\"large-doc\");\n yield* assertEqual(loaded, doc, \"Large documents should survive roundtrip\");\n }),\n },\n\n {\n name: \"special JSON characters survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({\n state: {\n quotes: 'He said \"hello\"',\n backslash: \"path\\\\to\\\\file\",\n newline: \"line1\\nline2\",\n tab: \"col1\\tcol2\",\n mixed: 'All: \"\\\\\\n\\t',\n },\n });\n yield* storage.save(\"special-chars\", doc);\n const loaded = yield* storage.load(\"special-chars\");\n yield* assertEqual(loaded, doc, \"Special JSON characters should survive roundtrip\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Version Number Edge Cases\n // ---------------------------------------------------------------------------\n {\n name: \"version 0 is preserved correctly\",\n category: Categories.VersionHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ version: 0 });\n yield* storage.save(\"version-0\", doc);\n const loaded = yield* storage.load(\"version-0\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.version, 0, \"Version 0 should be preserved\");\n }),\n },\n\n {\n name: \"version 1 is preserved correctly\",\n category: Categories.VersionHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ version: 1 });\n yield* storage.save(\"version-1\", doc);\n const loaded = yield* storage.load(\"version-1\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.version, 1, \"Version 1 should be preserved\");\n }),\n },\n\n {\n name: \"large version numbers are preserved\",\n category: Categories.VersionHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const largeVersion = Number.MAX_SAFE_INTEGER;\n const doc = makeDoc({ version: largeVersion });\n yield* storage.save(\"large-version\", doc);\n const loaded = yield* storage.load(\"large-version\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(\n definedLoaded.version,\n largeVersion,\n \"Large version number should be preserved exactly\"\n );\n }),\n },\n\n {\n name: \"schema version is preserved correctly\",\n category: Categories.VersionHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ schemaVersion: 42 });\n yield* storage.save(\"schema-version\", doc);\n const loaded = yield* storage.load(\"schema-version\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.schemaVersion, 42, \"Schema version should be preserved\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Timestamp Handling\n // ---------------------------------------------------------------------------\n {\n name: \"savedAt timestamp is preserved exactly\",\n category: Categories.TimestampHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const timestamp = 1704067200000;\n const doc = makeDoc({ savedAt: timestamp });\n yield* storage.save(\"timestamp-exact\", doc);\n const loaded = yield* storage.load(\"timestamp-exact\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.savedAt, timestamp, \"savedAt should be preserved exactly\");\n }),\n },\n\n {\n name: \"timestamp 0 is preserved correctly\",\n category: Categories.TimestampHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ savedAt: 0 });\n yield* storage.save(\"timestamp-0\", doc);\n const loaded = yield* storage.load(\"timestamp-0\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.savedAt, 0, \"Timestamp 0 should be preserved\");\n }),\n },\n\n {\n name: \"recent timestamps are preserved correctly\",\n category: Categories.TimestampHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const now = Date.now();\n const doc = makeDoc({ savedAt: now });\n yield* storage.save(\"timestamp-recent\", doc);\n const loaded = yield* storage.load(\"timestamp-recent\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.savedAt, now, \"Recent timestamp should be preserved\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Document ID Edge Cases\n // ---------------------------------------------------------------------------\n {\n name: \"long documentId (1000+ chars) works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const longId = \"x\".repeat(1000);\n const doc = makeDoc();\n yield* storage.save(longId, doc);\n const loaded = yield* storage.load(longId);\n yield* assertEqual(loaded, doc, \"Long documentId should work correctly\");\n }),\n },\n\n {\n name: \"unicode documentId works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const unicodeId = \"doc-test-id\";\n const doc = makeDoc();\n yield* storage.save(unicodeId, doc);\n const loaded = yield* storage.load(unicodeId);\n yield* assertEqual(loaded, doc, \"Unicode documentId should work correctly\");\n }),\n },\n\n {\n name: \"documentId with special chars works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const specialId = \"doc/path:to.file\";\n const doc = makeDoc();\n yield* storage.save(specialId, doc);\n const loaded = yield* storage.load(specialId);\n yield* assertEqual(loaded, doc, \"DocumentId with special chars should work\");\n }),\n },\n\n {\n name: \"documentId with spaces works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const spacedId = \"doc with spaces\";\n const doc = makeDoc();\n yield* storage.save(spacedId, doc);\n const loaded = yield* storage.load(spacedId);\n yield* assertEqual(loaded, doc, \"DocumentId with spaces should work\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Document Isolation\n // ---------------------------------------------------------------------------\n {\n name: \"different documents are stored independently\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { id: 1 }, version: 1 });\n const doc2 = makeDoc({ state: { id: 2 }, version: 2 });\n yield* storage.save(\"iso-doc-1\", doc1);\n yield* storage.save(\"iso-doc-2\", doc2);\n const loaded1 = yield* storage.load(\"iso-doc-1\");\n const loaded2 = yield* storage.load(\"iso-doc-2\");\n yield* assertEqual(loaded1, doc1, \"First document should be unchanged\");\n yield* assertEqual(loaded2, doc2, \"Second document should be unchanged\");\n }),\n },\n\n {\n name: \"deleting one document does not affect others\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { id: 1 } });\n const doc2 = makeDoc({ state: { id: 2 } });\n yield* storage.save(\"del-iso-1\", doc1);\n yield* storage.save(\"del-iso-2\", doc2);\n yield* storage.delete(\"del-iso-1\");\n const loaded1 = yield* storage.load(\"del-iso-1\");\n const loaded2 = yield* storage.load(\"del-iso-2\");\n yield* assertUndefined(loaded1, \"Deleted document should be undefined\");\n yield* assertEqual(loaded2, doc2, \"Other document should be unchanged\");\n }),\n },\n\n {\n name: \"updating one document does not affect others\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1v1 = makeDoc({ state: { id: 1, v: 1 }, version: 1 });\n const doc1v2 = makeDoc({ state: { id: 1, v: 2 }, version: 2 });\n const doc2 = makeDoc({ state: { id: 2 } });\n yield* storage.save(\"upd-iso-1\", doc1v1);\n yield* storage.save(\"upd-iso-2\", doc2);\n yield* storage.save(\"upd-iso-1\", doc1v2);\n const loaded1 = yield* storage.load(\"upd-iso-1\");\n const loaded2 = yield* storage.load(\"upd-iso-2\");\n yield* assertEqual(loaded1, doc1v2, \"Updated document should have new value\");\n yield* assertEqual(loaded2, doc2, \"Other document should be unchanged\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Consistency Guarantees\n // ---------------------------------------------------------------------------\n {\n name: \"multiple saves to same doc, load returns latest\",\n category: Categories.ConsistencyGuarantees,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { v: 1 }, version: 1 });\n const doc2 = makeDoc({ state: { v: 2 }, version: 2 });\n const doc3 = makeDoc({ state: { v: 3 }, version: 3 });\n yield* storage.save(\"multi-save\", doc1);\n yield* storage.save(\"multi-save\", doc2);\n yield* storage.save(\"multi-save\", doc3);\n const loaded = yield* storage.load(\"multi-save\");\n yield* assertEqual(loaded, doc3, \"Should return the latest saved document\");\n }),\n },\n\n {\n name: \"save-delete-save sequence works correctly\",\n category: Categories.ConsistencyGuarantees,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { phase: \"first\" }, version: 1 });\n const doc2 = makeDoc({ state: { phase: \"second\" }, version: 2 });\n yield* storage.save(\"sds-test\", doc1);\n yield* storage.delete(\"sds-test\");\n yield* storage.save(\"sds-test\", doc2);\n const loaded = yield* storage.load(\"sds-test\");\n yield* assertEqual(loaded, doc2, \"Should return document saved after delete\");\n }),\n },\n];\n\n// =============================================================================\n// Exports\n// =============================================================================\n\n/**\n * Get all ColdStorage test cases.\n *\n * @returns Array of test cases that require ColdStorageTag\n */\nexport const makeTests = (): StorageTestCase<\n ColdStorageTestError,\n ColdStorageTag\n>[] => tests;\n\n/**\n * Run all tests and collect results.\n *\n * @returns Effect that produces TestResults\n */\nexport const runAll = (): Effect.Effect<\n TestResults<ColdStorageTestError, ColdStorageTag>,\n never,\n ColdStorageTag\n> =>\n Effect.gen(function* () {\n const passed: StorageTestCase<ColdStorageTestError, ColdStorageTag>[] = [];\n const failed: Array<{\n test: StorageTestCase<ColdStorageTestError, ColdStorageTag>;\n error: ColdStorageTestError;\n }> = [];\n\n for (const test of tests) {\n const result = yield* Effect.either(test.run);\n if (result._tag === \"Right\") {\n passed.push(test);\n } else {\n failed.push({ test, error: result.left });\n }\n }\n\n return {\n passed,\n failed,\n total: tests.length,\n passCount: passed.length,\n failCount: failed.length,\n };\n });\n\nexport const ColdStorageTestSuite = {\n Categories,\n makeTests,\n runAll,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ColdStorageTestSuite.mjs","names":["tests: StorageTestCase<ColdStorageTestError, ColdStorageTag>[]","doc: StoredDocument","passed: StorageTestCase<ColdStorageTestError, ColdStorageTag>[]","failed: Array<{\n test: StorageTestCase<ColdStorageTestError, ColdStorageTag>;\n error: ColdStorageTestError;\n }>"],"sources":["../../src/testing/ColdStorageTestSuite.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect/testing - ColdStorage Test Suite\n *\n * Comprehensive test suite for ColdStorage adapter implementations.\n * These tests verify that an adapter correctly implements the ColdStorage interface\n * and can reliably store/retrieve document snapshots without data loss.\n */\nimport { Effect } from \"effect\";\nimport { ColdStorageTag } from \"../ColdStorage\";\nimport type { ColdStorageError } from \"../Errors\";\nimport type { StoredDocument } from \"../Types\";\nimport type { StorageTestCase, TestResults } from \"./types\";\nimport { TestError } from \"./types\";\nimport {\n assertEqual,\n assertUndefined,\n assertDefined,\n} from \"./assertions\";\n\n/**\n * Error type for ColdStorage tests - can be either a TestError or a ColdStorageError\n */\nexport type ColdStorageTestError = TestError | ColdStorageError;\n\n// =============================================================================\n// Categories\n// =============================================================================\n\nexport const Categories = {\n BasicOperations: \"Basic Operations\",\n DataIntegrity: \"Data Integrity\",\n VersionHandling: \"Version Handling\",\n TimestampHandling: \"Timestamp Handling\",\n DocumentIdEdgeCases: \"Document ID Edge Cases\",\n DocumentIsolation: \"Document Isolation\",\n ConsistencyGuarantees: \"Consistency Guarantees\",\n} as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\nconst makeDoc = (overrides: Partial<StoredDocument> = {}): StoredDocument => ({\n state: { title: \"Test Document\" },\n version: 1,\n schemaVersion: 1,\n savedAt: Date.now(),\n ...overrides,\n});\n\nconst generateLargeState = (sizeKB: number): Record<string, unknown> => {\n const chars = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n const targetBytes = sizeKB * 1024;\n let content = \"\";\n while (content.length < targetBytes) {\n content += chars[Math.floor(Math.random() * chars.length)];\n }\n return { content, padding: Array(100).fill(\"x\") };\n};\n\n// =============================================================================\n// Test Definitions\n// =============================================================================\n\nconst tests: StorageTestCase<ColdStorageTestError, ColdStorageTag>[] = [\n // ---------------------------------------------------------------------------\n // Basic Operations\n // ---------------------------------------------------------------------------\n {\n name: \"load returns undefined for non-existent document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const result = yield* storage.load(\"non-existent-doc-id-12345\");\n yield* assertUndefined(result, \"Expected undefined for non-existent document\");\n }),\n },\n\n {\n name: \"save then load returns exact same document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc();\n yield* storage.save(\"test-doc-1\", doc);\n const loaded = yield* storage.load(\"test-doc-1\");\n yield* assertEqual(loaded, doc, \"Loaded document should match saved document\");\n }),\n },\n\n {\n name: \"save overwrites existing document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { title: \"First\" }, version: 1 });\n const doc2 = makeDoc({ state: { title: \"Second\" }, version: 2 });\n yield* storage.save(\"test-doc-2\", doc1);\n yield* storage.save(\"test-doc-2\", doc2);\n const loaded = yield* storage.load(\"test-doc-2\");\n yield* assertEqual(loaded, doc2, \"Should return the second (overwritten) document\");\n }),\n },\n\n {\n name: \"delete removes document\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc();\n yield* storage.save(\"test-doc-3\", doc);\n yield* storage.delete(\"test-doc-3\");\n const loaded = yield* storage.load(\"test-doc-3\");\n yield* assertUndefined(loaded, \"Document should be undefined after deletion\");\n }),\n },\n\n {\n name: \"delete on non-existent document does not error\",\n category: Categories.BasicOperations,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n yield* storage.delete(\"definitely-non-existent-doc-xyz\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Data Integrity & Serialization\n // ---------------------------------------------------------------------------\n {\n name: \"all StoredDocument fields are preserved\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc: StoredDocument = {\n state: { nested: { value: 42 } },\n version: 123,\n schemaVersion: 2,\n savedAt: 1704067200000,\n };\n yield* storage.save(\"fields-test\", doc);\n const loaded = yield* storage.load(\"fields-test\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.state, doc.state, \"state should be preserved\");\n yield* assertEqual(definedLoaded.version, doc.version, \"version should be preserved\");\n yield* assertEqual(definedLoaded.schemaVersion, doc.schemaVersion, \"schemaVersion should be preserved\");\n yield* assertEqual(definedLoaded.savedAt, doc.savedAt, \"savedAt should be preserved\");\n }),\n },\n\n {\n name: \"complex nested state objects survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const complexState = {\n level1: {\n level2: {\n level3: {\n value: \"deep\",\n array: [1, 2, { nested: true }],\n },\n },\n sibling: \"value\",\n },\n topLevel: 42,\n };\n const doc = makeDoc({ state: complexState });\n yield* storage.save(\"complex-state\", doc);\n const loaded = yield* storage.load(\"complex-state\");\n yield* assertEqual(loaded, doc, \"Complex nested state should survive roundtrip\");\n }),\n },\n\n {\n name: \"arrays in state survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({\n state: {\n numbers: [1, 2, 3, 4, 5],\n strings: [\"a\", \"b\", \"c\"],\n mixed: [1, \"two\", { three: 3 }, [4]],\n empty: [],\n },\n });\n yield* storage.save(\"arrays-test\", doc);\n const loaded = yield* storage.load(\"arrays-test\");\n yield* assertEqual(loaded, doc, \"Arrays should survive roundtrip\");\n }),\n },\n\n {\n name: \"null values in state survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({\n state: {\n nullValue: null,\n nested: { alsoNull: null },\n array: [null, 1, null],\n },\n });\n yield* storage.save(\"null-test\", doc);\n const loaded = yield* storage.load(\"null-test\");\n yield* assertEqual(loaded, doc, \"null values should survive roundtrip\");\n }),\n },\n\n {\n name: \"empty object survives roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ state: {} });\n yield* storage.save(\"empty-obj\", doc);\n const loaded = yield* storage.load(\"empty-obj\");\n yield* assertEqual(loaded, doc, \"Empty object should survive roundtrip\");\n }),\n },\n\n {\n name: \"empty array survives roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ state: [] });\n yield* storage.save(\"empty-arr\", doc);\n const loaded = yield* storage.load(\"empty-arr\");\n yield* assertEqual(loaded, doc, \"Empty array should survive roundtrip\");\n }),\n },\n\n {\n name: \"unicode strings in state survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({\n state: {\n emoji: \"Hello! How are you?\",\n chinese: \"Chinese Characters\",\n arabic: \"Arabic Letters\",\n special: \"Combined: Cafe\",\n },\n });\n yield* storage.save(\"unicode-test\", doc);\n const loaded = yield* storage.load(\"unicode-test\");\n yield* assertEqual(loaded, doc, \"Unicode strings should survive roundtrip\");\n }),\n },\n\n {\n name: \"large state objects (100KB+) survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const largeState = generateLargeState(100);\n const doc = makeDoc({ state: largeState });\n yield* storage.save(\"large-doc\", doc);\n const loaded = yield* storage.load(\"large-doc\");\n yield* assertEqual(loaded, doc, \"Large documents should survive roundtrip\");\n }),\n },\n\n {\n name: \"special JSON characters survive roundtrip\",\n category: Categories.DataIntegrity,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({\n state: {\n quotes: 'He said \"hello\"',\n backslash: \"path\\\\to\\\\file\",\n newline: \"line1\\nline2\",\n tab: \"col1\\tcol2\",\n mixed: 'All: \"\\\\\\n\\t',\n },\n });\n yield* storage.save(\"special-chars\", doc);\n const loaded = yield* storage.load(\"special-chars\");\n yield* assertEqual(loaded, doc, \"Special JSON characters should survive roundtrip\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Version Number Edge Cases\n // ---------------------------------------------------------------------------\n {\n name: \"version 0 is preserved correctly\",\n category: Categories.VersionHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ version: 0 });\n yield* storage.save(\"version-0\", doc);\n const loaded = yield* storage.load(\"version-0\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.version, 0, \"Version 0 should be preserved\");\n }),\n },\n\n {\n name: \"version 1 is preserved correctly\",\n category: Categories.VersionHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ version: 1 });\n yield* storage.save(\"version-1\", doc);\n const loaded = yield* storage.load(\"version-1\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.version, 1, \"Version 1 should be preserved\");\n }),\n },\n\n {\n name: \"large version numbers are preserved\",\n category: Categories.VersionHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const largeVersion = Number.MAX_SAFE_INTEGER;\n const doc = makeDoc({ version: largeVersion });\n yield* storage.save(\"large-version\", doc);\n const loaded = yield* storage.load(\"large-version\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(\n definedLoaded.version,\n largeVersion,\n \"Large version number should be preserved exactly\"\n );\n }),\n },\n\n {\n name: \"schema version is preserved correctly\",\n category: Categories.VersionHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ schemaVersion: 42 });\n yield* storage.save(\"schema-version\", doc);\n const loaded = yield* storage.load(\"schema-version\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.schemaVersion, 42, \"Schema version should be preserved\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Timestamp Handling\n // ---------------------------------------------------------------------------\n {\n name: \"savedAt timestamp is preserved exactly\",\n category: Categories.TimestampHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const timestamp = 1704067200000;\n const doc = makeDoc({ savedAt: timestamp });\n yield* storage.save(\"timestamp-exact\", doc);\n const loaded = yield* storage.load(\"timestamp-exact\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.savedAt, timestamp, \"savedAt should be preserved exactly\");\n }),\n },\n\n {\n name: \"timestamp 0 is preserved correctly\",\n category: Categories.TimestampHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc = makeDoc({ savedAt: 0 });\n yield* storage.save(\"timestamp-0\", doc);\n const loaded = yield* storage.load(\"timestamp-0\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.savedAt, 0, \"Timestamp 0 should be preserved\");\n }),\n },\n\n {\n name: \"recent timestamps are preserved correctly\",\n category: Categories.TimestampHandling,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const now = Date.now();\n const doc = makeDoc({ savedAt: now });\n yield* storage.save(\"timestamp-recent\", doc);\n const loaded = yield* storage.load(\"timestamp-recent\");\n const definedLoaded = yield* assertDefined(loaded, \"Document should exist\");\n yield* assertEqual(definedLoaded.savedAt, now, \"Recent timestamp should be preserved\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Document ID Edge Cases\n // ---------------------------------------------------------------------------\n {\n name: \"long documentId (1000+ chars) works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const longId = \"x\".repeat(1000);\n const doc = makeDoc();\n yield* storage.save(longId, doc);\n const loaded = yield* storage.load(longId);\n yield* assertEqual(loaded, doc, \"Long documentId should work correctly\");\n }),\n },\n\n {\n name: \"unicode documentId works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const unicodeId = \"doc-test-id\";\n const doc = makeDoc();\n yield* storage.save(unicodeId, doc);\n const loaded = yield* storage.load(unicodeId);\n yield* assertEqual(loaded, doc, \"Unicode documentId should work correctly\");\n }),\n },\n\n {\n name: \"documentId with special chars works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const specialId = \"doc/path:to.file\";\n const doc = makeDoc();\n yield* storage.save(specialId, doc);\n const loaded = yield* storage.load(specialId);\n yield* assertEqual(loaded, doc, \"DocumentId with special chars should work\");\n }),\n },\n\n {\n name: \"documentId with spaces works\",\n category: Categories.DocumentIdEdgeCases,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const spacedId = \"doc with spaces\";\n const doc = makeDoc();\n yield* storage.save(spacedId, doc);\n const loaded = yield* storage.load(spacedId);\n yield* assertEqual(loaded, doc, \"DocumentId with spaces should work\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Document Isolation\n // ---------------------------------------------------------------------------\n {\n name: \"different documents are stored independently\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { id: 1 }, version: 1 });\n const doc2 = makeDoc({ state: { id: 2 }, version: 2 });\n yield* storage.save(\"iso-doc-1\", doc1);\n yield* storage.save(\"iso-doc-2\", doc2);\n const loaded1 = yield* storage.load(\"iso-doc-1\");\n const loaded2 = yield* storage.load(\"iso-doc-2\");\n yield* assertEqual(loaded1, doc1, \"First document should be unchanged\");\n yield* assertEqual(loaded2, doc2, \"Second document should be unchanged\");\n }),\n },\n\n {\n name: \"deleting one document does not affect others\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { id: 1 } });\n const doc2 = makeDoc({ state: { id: 2 } });\n yield* storage.save(\"del-iso-1\", doc1);\n yield* storage.save(\"del-iso-2\", doc2);\n yield* storage.delete(\"del-iso-1\");\n const loaded1 = yield* storage.load(\"del-iso-1\");\n const loaded2 = yield* storage.load(\"del-iso-2\");\n yield* assertUndefined(loaded1, \"Deleted document should be undefined\");\n yield* assertEqual(loaded2, doc2, \"Other document should be unchanged\");\n }),\n },\n\n {\n name: \"updating one document does not affect others\",\n category: Categories.DocumentIsolation,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1v1 = makeDoc({ state: { id: 1, v: 1 }, version: 1 });\n const doc1v2 = makeDoc({ state: { id: 1, v: 2 }, version: 2 });\n const doc2 = makeDoc({ state: { id: 2 } });\n yield* storage.save(\"upd-iso-1\", doc1v1);\n yield* storage.save(\"upd-iso-2\", doc2);\n yield* storage.save(\"upd-iso-1\", doc1v2);\n const loaded1 = yield* storage.load(\"upd-iso-1\");\n const loaded2 = yield* storage.load(\"upd-iso-2\");\n yield* assertEqual(loaded1, doc1v2, \"Updated document should have new value\");\n yield* assertEqual(loaded2, doc2, \"Other document should be unchanged\");\n }),\n },\n\n // ---------------------------------------------------------------------------\n // Consistency Guarantees\n // ---------------------------------------------------------------------------\n {\n name: \"multiple saves to same doc, load returns latest\",\n category: Categories.ConsistencyGuarantees,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { v: 1 }, version: 1 });\n const doc2 = makeDoc({ state: { v: 2 }, version: 2 });\n const doc3 = makeDoc({ state: { v: 3 }, version: 3 });\n yield* storage.save(\"multi-save\", doc1);\n yield* storage.save(\"multi-save\", doc2);\n yield* storage.save(\"multi-save\", doc3);\n const loaded = yield* storage.load(\"multi-save\");\n yield* assertEqual(loaded, doc3, \"Should return the latest saved document\");\n }),\n },\n\n {\n name: \"save-delete-save sequence works correctly\",\n category: Categories.ConsistencyGuarantees,\n run: Effect.gen(function* () {\n const storage = yield* ColdStorageTag;\n const doc1 = makeDoc({ state: { phase: \"first\" }, version: 1 });\n const doc2 = makeDoc({ state: { phase: \"second\" }, version: 2 });\n yield* storage.save(\"sds-test\", doc1);\n yield* storage.delete(\"sds-test\");\n yield* storage.save(\"sds-test\", doc2);\n const loaded = yield* storage.load(\"sds-test\");\n yield* assertEqual(loaded, doc2, \"Should return document saved after delete\");\n }),\n },\n];\n\n// =============================================================================\n// Exports\n// =============================================================================\n\n/**\n * Get all ColdStorage test cases.\n *\n * @returns Array of test cases that require ColdStorageTag\n */\nexport const makeTests = (): StorageTestCase<\n ColdStorageTestError,\n ColdStorageTag\n>[] => tests;\n\n/**\n * Run all tests and collect results.\n *\n * @returns Effect that produces TestResults\n */\nexport const runAll = (): Effect.Effect<\n TestResults<ColdStorageTestError, ColdStorageTag>,\n never,\n ColdStorageTag\n> =>\n Effect.gen(function* () {\n const passed: StorageTestCase<ColdStorageTestError, ColdStorageTag>[] = [];\n const failed: Array<{\n test: StorageTestCase<ColdStorageTestError, ColdStorageTag>;\n error: ColdStorageTestError;\n }> = [];\n\n for (const test of tests) {\n const result = yield* Effect.result(test.run);\n if (result._tag === \"Success\") {\n passed.push(test);\n } else {\n failed.push({ test, error: result.failure });\n }\n }\n\n return {\n passed,\n failed,\n total: tests.length,\n passCount: passed.length,\n failCount: failed.length,\n };\n });\n\nexport const ColdStorageTestSuite = {\n Categories,\n makeTests,\n runAll,\n};\n"],"mappings":"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"}
|
|
@@ -507,9 +507,9 @@ const tests = [
|
|
|
507
507
|
run: effect.Effect.gen(function* () {
|
|
508
508
|
const storage = yield* require_HotStorage.HotStorageTag;
|
|
509
509
|
yield* storage.appendWithCheck("gap-check-fail", makeEntry(1), 1);
|
|
510
|
-
const result = yield* effect.Effect.
|
|
511
|
-
yield* require_assertions.assertTrue(result._tag === "
|
|
512
|
-
if (result._tag === "
|
|
510
|
+
const result = yield* effect.Effect.result(storage.appendWithCheck("gap-check-fail", makeEntry(3), 3));
|
|
511
|
+
yield* require_assertions.assertTrue(result._tag === "Failure", "appendWithCheck should fail when there's a version gap");
|
|
512
|
+
if (result._tag === "Failure") yield* require_assertions.assertTrue(result.failure._tag === "WalVersionGapError", "Error should be WalVersionGapError");
|
|
513
513
|
const entries = yield* storage.getEntries("gap-check-fail", 0);
|
|
514
514
|
yield* require_assertions.assertLength(entries, 1, "Should only have version 1");
|
|
515
515
|
yield* require_assertions.assertEqual(entries[0].version, 1, "Only version 1 should exist");
|
|
@@ -520,9 +520,9 @@ const tests = [
|
|
|
520
520
|
category: Categories.GapChecking,
|
|
521
521
|
run: effect.Effect.gen(function* () {
|
|
522
522
|
const storage = yield* require_HotStorage.HotStorageTag;
|
|
523
|
-
const result = yield* effect.Effect.
|
|
524
|
-
yield* require_assertions.assertTrue(result._tag === "
|
|
525
|
-
if (result._tag === "
|
|
523
|
+
const result = yield* effect.Effect.result(storage.appendWithCheck("gap-check-not-first", makeEntry(2), 2));
|
|
524
|
+
yield* require_assertions.assertTrue(result._tag === "Failure", "appendWithCheck should fail when first entry is not version 1");
|
|
525
|
+
if (result._tag === "Failure") yield* require_assertions.assertTrue(result.failure._tag === "WalVersionGapError", "Error should be WalVersionGapError");
|
|
526
526
|
yield* require_assertions.assertEmpty(yield* storage.getEntries("gap-check-not-first", 0), "No entries should exist after failed append");
|
|
527
527
|
})
|
|
528
528
|
},
|
|
@@ -532,7 +532,7 @@ const tests = [
|
|
|
532
532
|
run: effect.Effect.gen(function* () {
|
|
533
533
|
const storage = yield* require_HotStorage.HotStorageTag;
|
|
534
534
|
yield* storage.appendWithCheck("gap-check-duplicate", makeEntry(1), 1);
|
|
535
|
-
yield* require_assertions.assertTrue((yield* effect.Effect.
|
|
535
|
+
yield* require_assertions.assertTrue((yield* effect.Effect.result(storage.appendWithCheck("gap-check-duplicate", makeEntry(1), 1)))._tag === "Failure", "appendWithCheck should fail when version already exists");
|
|
536
536
|
yield* require_assertions.assertLength(yield* storage.getEntries("gap-check-duplicate", 0), 1, "Should still only have one entry");
|
|
537
537
|
})
|
|
538
538
|
},
|
|
@@ -574,9 +574,9 @@ const tests = [
|
|
|
574
574
|
run: effect.Effect.gen(function* () {
|
|
575
575
|
const storage = yield* require_HotStorage.HotStorageTag;
|
|
576
576
|
yield* storage.truncate("gap-base-detect", 5);
|
|
577
|
-
const result = yield* effect.Effect.
|
|
578
|
-
yield* require_assertions.assertTrue(result._tag === "
|
|
579
|
-
if (result._tag === "
|
|
577
|
+
const result = yield* effect.Effect.result(storage.appendWithCheck("gap-base-detect", makeEntry(7), 7, 5));
|
|
578
|
+
yield* require_assertions.assertTrue(result._tag === "Failure", "Should fail when skipping version 6");
|
|
579
|
+
if (result._tag === "Failure") yield* require_assertions.assertTrue(result.failure._tag === "WalVersionGapError", "Error should be WalVersionGapError");
|
|
580
580
|
})
|
|
581
581
|
},
|
|
582
582
|
{
|
|
@@ -832,11 +832,11 @@ const runAll = () => effect.Effect.gen(function* () {
|
|
|
832
832
|
const passed = [];
|
|
833
833
|
const failed = [];
|
|
834
834
|
for (const test of tests) {
|
|
835
|
-
const result = yield* effect.Effect.
|
|
836
|
-
if (result._tag === "
|
|
835
|
+
const result = yield* effect.Effect.result(test.run);
|
|
836
|
+
if (result._tag === "Success") passed.push(test);
|
|
837
837
|
else failed.push({
|
|
838
838
|
test,
|
|
839
|
-
error: result.
|
|
839
|
+
error: result.failure
|
|
840
840
|
});
|
|
841
841
|
}
|
|
842
842
|
return {
|
|
@@ -507,9 +507,9 @@ const tests = [
|
|
|
507
507
|
run: Effect.gen(function* () {
|
|
508
508
|
const storage = yield* HotStorageTag;
|
|
509
509
|
yield* storage.appendWithCheck("gap-check-fail", makeEntry(1), 1);
|
|
510
|
-
const result = yield* Effect.
|
|
511
|
-
yield* assertTrue(result._tag === "
|
|
512
|
-
if (result._tag === "
|
|
510
|
+
const result = yield* Effect.result(storage.appendWithCheck("gap-check-fail", makeEntry(3), 3));
|
|
511
|
+
yield* assertTrue(result._tag === "Failure", "appendWithCheck should fail when there's a version gap");
|
|
512
|
+
if (result._tag === "Failure") yield* assertTrue(result.failure._tag === "WalVersionGapError", "Error should be WalVersionGapError");
|
|
513
513
|
const entries = yield* storage.getEntries("gap-check-fail", 0);
|
|
514
514
|
yield* assertLength(entries, 1, "Should only have version 1");
|
|
515
515
|
yield* assertEqual(entries[0].version, 1, "Only version 1 should exist");
|
|
@@ -520,9 +520,9 @@ const tests = [
|
|
|
520
520
|
category: Categories.GapChecking,
|
|
521
521
|
run: Effect.gen(function* () {
|
|
522
522
|
const storage = yield* HotStorageTag;
|
|
523
|
-
const result = yield* Effect.
|
|
524
|
-
yield* assertTrue(result._tag === "
|
|
525
|
-
if (result._tag === "
|
|
523
|
+
const result = yield* Effect.result(storage.appendWithCheck("gap-check-not-first", makeEntry(2), 2));
|
|
524
|
+
yield* assertTrue(result._tag === "Failure", "appendWithCheck should fail when first entry is not version 1");
|
|
525
|
+
if (result._tag === "Failure") yield* assertTrue(result.failure._tag === "WalVersionGapError", "Error should be WalVersionGapError");
|
|
526
526
|
yield* assertEmpty(yield* storage.getEntries("gap-check-not-first", 0), "No entries should exist after failed append");
|
|
527
527
|
})
|
|
528
528
|
},
|
|
@@ -532,7 +532,7 @@ const tests = [
|
|
|
532
532
|
run: Effect.gen(function* () {
|
|
533
533
|
const storage = yield* HotStorageTag;
|
|
534
534
|
yield* storage.appendWithCheck("gap-check-duplicate", makeEntry(1), 1);
|
|
535
|
-
yield* assertTrue((yield* Effect.
|
|
535
|
+
yield* assertTrue((yield* Effect.result(storage.appendWithCheck("gap-check-duplicate", makeEntry(1), 1)))._tag === "Failure", "appendWithCheck should fail when version already exists");
|
|
536
536
|
yield* assertLength(yield* storage.getEntries("gap-check-duplicate", 0), 1, "Should still only have one entry");
|
|
537
537
|
})
|
|
538
538
|
},
|
|
@@ -574,9 +574,9 @@ const tests = [
|
|
|
574
574
|
run: Effect.gen(function* () {
|
|
575
575
|
const storage = yield* HotStorageTag;
|
|
576
576
|
yield* storage.truncate("gap-base-detect", 5);
|
|
577
|
-
const result = yield* Effect.
|
|
578
|
-
yield* assertTrue(result._tag === "
|
|
579
|
-
if (result._tag === "
|
|
577
|
+
const result = yield* Effect.result(storage.appendWithCheck("gap-base-detect", makeEntry(7), 7, 5));
|
|
578
|
+
yield* assertTrue(result._tag === "Failure", "Should fail when skipping version 6");
|
|
579
|
+
if (result._tag === "Failure") yield* assertTrue(result.failure._tag === "WalVersionGapError", "Error should be WalVersionGapError");
|
|
580
580
|
})
|
|
581
581
|
},
|
|
582
582
|
{
|
|
@@ -832,11 +832,11 @@ const runAll = () => Effect.gen(function* () {
|
|
|
832
832
|
const passed = [];
|
|
833
833
|
const failed = [];
|
|
834
834
|
for (const test of tests) {
|
|
835
|
-
const result = yield* Effect.
|
|
836
|
-
if (result._tag === "
|
|
835
|
+
const result = yield* Effect.result(test.run);
|
|
836
|
+
if (result._tag === "Success") passed.push(test);
|
|
837
837
|
else failed.push({
|
|
838
838
|
test,
|
|
839
|
-
error: result.
|
|
839
|
+
error: result.failure
|
|
840
840
|
});
|
|
841
841
|
}
|
|
842
842
|
return {
|