@korajs/test 1.0.0-beta.2 → 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -349,6 +349,14 @@ var TestServer = class {
|
|
|
349
349
|
getConnectionCount() {
|
|
350
350
|
return this.syncServer.getConnectionCount();
|
|
351
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Retransmit relay batches connected clients have not acknowledged. Lets a test
|
|
354
|
+
* deterministically trigger the redelivery that the server also runs on a timer,
|
|
355
|
+
* without depending on wall-clock time. `staleMs` of 0 resends every pending relay.
|
|
356
|
+
*/
|
|
357
|
+
retransmitPendingRelays(staleMs = 0) {
|
|
358
|
+
this.syncServer.retransmitPendingRelays(staleMs);
|
|
359
|
+
}
|
|
352
360
|
/** Every blob reference still reachable from live records on the server. */
|
|
353
361
|
getLiveBlobRefs() {
|
|
354
362
|
return this.syncServer.getLiveBlobRefs();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/test-network.ts","../src/test-device.ts","../src/test-server.ts","../src/assertions.ts","../src/protobuf-wire-transport.ts","../src/server-clock-transport.ts"],"sourcesContent":["// @korajs/test — testing harness for Kora.js\n// Creates virtual device networks for testing sync convergence and conflicts.\n\n// === Factory ===\nexport { createTestNetwork, createMixedTestNetwork } from './test-network'\nexport type {\n\tTestNetwork,\n\tTestNetworkOptions,\n\tMixedTestDeviceConfig,\n} from './test-network'\n\n// === Device ===\nexport { TestDevice } from './test-device'\nexport type { TestDeviceOptions } from './test-device'\n\n// === Server ===\nexport { TestServer } from './test-server'\nexport type { TestServerOptions } from './test-server'\n\n// === Assertions ===\nexport { checkConvergence, expectConverged, expectConvergedEventually } from './assertions'\nexport type {\n\tCollectionDifference,\n\tConvergenceResult,\n\tFieldDifference,\n} from './assertions'\n\n// === Protobuf wire transport (for testing convergence through the real wire format) ===\nexport { wrapTransportPairWithProtobufWire } from './protobuf-wire-transport'\nexport type { TransportPair } from './protobuf-wire-transport'\n\n// === Server-clock transport (for testing clock-skew / rebase integration) ===\nexport { wrapTransportPairWithServerClock } from './server-clock-transport'\n\n// === Re-export ChaosTransport for convenience ===\nexport { ChaosTransport } from '@korajs/sync'\nexport type { ChaosConfig } from '@korajs/sync'\n","import { mkdtempSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport type { OperationTransform, SchemaDefinition } from '@korajs/core'\nimport { createServerTransportPair } from '@korajs/server/internal'\nimport type { ChaosConfig } from '@korajs/sync'\nimport { ChaosTransport } from '@korajs/sync'\nimport type { SyncTransport } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\nimport { TestDevice } from './test-device'\nimport { TestServer, type TestServerOptions } from './test-server'\n\n/**\n * Options for creating a test network.\n */\nexport interface TestNetworkOptions {\n\t/** Number of devices to create. Defaults to 2. */\n\tdevices?: number\n\t/** Custom device names. If not provided, uses 'device-0', 'device-1', etc. */\n\tdeviceNames?: string[]\n\t/** Optional chaos transport settings applied to each device link. */\n\tchaos?: ChaosConfig\n\t/**\n\t * Optional wrapper applied to each device's transport pair after the base\n\t * (in-memory, optionally chaos-wrapped) pair is created. Use it to route\n\t * messages through the protobuf wire codec\n\t * ({@link wrapTransportPairWithProtobufWire}) or to inject a controllable\n\t * server clock. Called once per device connection.\n\t */\n\twrapTransport?: (pair: TransportPair) => TransportPair\n\t/** Enable central blob storage on the server (persist + serve uploaded blob bytes). */\n\tblobStorage?: boolean\n\t/** Adjudicate untrusted client operations on the server before materialization. */\n\tvalidateOperation?: import('@korajs/server').OperationValidator\n}\n\n/**\n * A test network with a server and multiple devices.\n */\nexport interface TestNetwork {\n\t/** The test server */\n\tserver: TestServer\n\t/** All devices in the network */\n\tdevices: TestDevice[]\n\t/** Temporary directory for DB files */\n\ttmpDir: string\n\t/** Close all devices and the server, clean up temp files */\n\tclose(): Promise<void>\n}\n\n/**\n * Create a test network with a server and multiple virtual devices.\n *\n * Each device has its own local SQLite store and SyncEngine. Devices\n * communicate with the server via in-memory transports.\n *\n * @param schema - The schema all devices share\n * @param options - Network configuration\n * @returns A test network with server and devices ready for use\n *\n * @example\n * ```typescript\n * const network = await createTestNetwork(schema, { devices: 2 })\n * const [deviceA, deviceB] = network.devices\n *\n * await deviceA.collection('todos').insert({ title: 'Hello' })\n * await deviceA.sync()\n * await deviceB.sync()\n *\n * const todos = await deviceB.getState('todos')\n * expect(todos).toHaveLength(1)\n *\n * await network.close()\n * ```\n */\nexport async function createTestNetwork(\n\tschema: SchemaDefinition,\n\toptions?: TestNetworkOptions,\n): Promise<TestNetwork> {\n\tconst deviceCount = options?.devices ?? 2\n\tconst deviceNames =\n\t\toptions?.deviceNames ?? Array.from({ length: deviceCount }, (_, i) => `device-${i}`)\n\n\t// Create temp directory for DB files\n\tconst tmpDir = mkdtempSync(join(tmpdir(), 'kora-test-'))\n\n\t// Create server\n\tconst server = new TestServer(schema, {\n\t\tblobStorage: options?.blobStorage,\n\t\tvalidateOperation: options?.validateOperation,\n\t})\n\n\t// Create devices\n\tconst devices: TestDevice[] = []\n\tfor (const name of deviceNames) {\n\t\tconst device = new TestDevice({\n\t\t\tname,\n\t\t\tschema,\n\t\t\tserver,\n\t\t\tcreateTransportPair: () => {\n\t\t\t\tconst pair = createServerTransportPair()\n\t\t\t\tconst client = pair.client as unknown as SyncTransport\n\t\t\t\tconst chaos = options?.chaos\n\t\t\t\tconst base: TransportPair = {\n\t\t\t\t\tclient: chaos ? new ChaosTransport(client, chaos) : client,\n\t\t\t\t\tserverTransport: pair.server,\n\t\t\t\t}\n\t\t\t\treturn options?.wrapTransport ? options.wrapTransport(base) : base\n\t\t\t},\n\t\t\ttmpDir,\n\t\t})\n\t\tawait device.open()\n\t\tdevices.push(device)\n\t}\n\n\treturn {\n\t\tserver,\n\t\tdevices,\n\t\ttmpDir,\n\t\tasync close(): Promise<void> {\n\t\t\tfor (const device of devices) {\n\t\t\t\tawait device.close()\n\t\t\t}\n\t\t\tawait server.close()\n\t\t\t// Clean up temp DB files\n\t\t\ttry {\n\t\t\t\tconst { rmSync } = await import('node:fs')\n\t\t\t\trmSync(tmpDir, { recursive: true, force: true })\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t},\n\t}\n}\n\n/**\n * Per-device configuration when devices use different local schemas or sync settings.\n */\nexport interface MixedTestDeviceConfig {\n\tname: string\n\tschema: SchemaDefinition\n\tsyncSchemaVersion?: number\n\toperationTransforms?: OperationTransform[]\n}\n\n/**\n * Create a test network where devices may use different schema versions and transforms.\n * The server uses `serverSchema` for materialization and handshake bounds.\n */\nexport async function createMixedTestNetwork(\n\tserverSchema: SchemaDefinition,\n\tserverOptions: TestServerOptions,\n\tdeviceConfigs: MixedTestDeviceConfig[],\n): Promise<TestNetwork> {\n\tconst tmpDir = mkdtempSync(join(tmpdir(), 'kora-test-'))\n\tconst server = new TestServer(serverSchema, serverOptions)\n\n\tconst devices: TestDevice[] = []\n\tfor (const config of deviceConfigs) {\n\t\tconst device = new TestDevice({\n\t\t\tname: config.name,\n\t\t\tschema: config.schema,\n\t\t\tserver,\n\t\t\tsyncSchemaVersion: config.syncSchemaVersion,\n\t\t\toperationTransforms: config.operationTransforms,\n\t\t\tcreateTransportPair: () => {\n\t\t\t\tconst pair = createServerTransportPair()\n\t\t\t\treturn {\n\t\t\t\t\tclient: pair.client as unknown as SyncTransport,\n\t\t\t\t\tserverTransport: pair.server,\n\t\t\t\t}\n\t\t\t},\n\t\t\ttmpDir,\n\t\t})\n\t\tawait device.open()\n\t\tdevices.push(device)\n\t}\n\n\treturn {\n\t\tserver,\n\t\tdevices,\n\t\ttmpDir,\n\t\tasync close(): Promise<void> {\n\t\t\tfor (const device of devices) {\n\t\t\t\tawait device.close()\n\t\t\t}\n\t\t\tawait server.close()\n\t\t\ttry {\n\t\t\t\tconst { rmSync } = await import('node:fs')\n\t\t\t\trmSync(tmpDir, { recursive: true, force: true })\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t},\n\t}\n}\n","import type { OperationTransform, SchemaDefinition } from '@korajs/core'\nimport type { VersionVector } from '@korajs/core'\nimport type { KoraEventEmitter } from '@korajs/core'\nimport type { BlobRef } from '@korajs/core'\nimport { SimpleEventEmitter } from '@korajs/core/internal'\nimport { MergeEngine } from '@korajs/merge'\nimport {\n\tMemoryBlobStore,\n\tStore,\n\tcreateRemoteChunkProvider,\n\tprepareBlobForSend,\n\tputBlobForTransfer,\n\treceiveBlob,\n\tresolveBlobManifest,\n\tserveBlobChunks,\n} from '@korajs/store'\nimport type {\n\tBlobManifest,\n\tChunkProvider,\n\tCollectionAccessor,\n\tReceiveBlobResult,\n\tStorageAdapter,\n} from '@korajs/store'\nimport { BetterSqlite3Adapter } from '@korajs/store/better-sqlite3'\nimport { SyncEngine } from '@korajs/sync'\nimport type { SyncTransport } from '@korajs/sync'\nimport {\n\tApplyPipeline,\n\tMergeAwareSyncStore,\n\tStoreQueueStorage,\n\tStoreSyncStatePersistence,\n\tcreateSyncEngineChunkPort,\n\twireAuditPersistence,\n\twireBlobUpload,\n} from 'korajs/testing'\nimport type { TestServer } from './test-server'\n\n/**\n * Options for creating a TestDevice.\n */\nexport interface TestDeviceOptions {\n\t/** Unique device name (used for DB file naming) */\n\tname: string\n\t/** Schema definition */\n\tschema: SchemaDefinition\n\t/** Test server to connect to */\n\tserver: TestServer\n\t/** Transport factory — creates a linked client/server transport pair */\n\tcreateTransportPair: () => {\n\t\tclient: SyncTransport\n\t\tserverTransport: import('@korajs/server').ServerTransport\n\t}\n\t/** Optional directory for temp DB files */\n\ttmpDir: string\n\t/** Client handshake schema version. Defaults to `schema.version`. */\n\tsyncSchemaVersion?: number\n\t/** Transforms applied to inbound operations before local apply. */\n\toperationTransforms?: OperationTransform[]\n}\n\n/**\n * A virtual device in a test network.\n * Each device has its own Store (with real SQLite), SyncEngine, and MergeEngine.\n * Provides high-level methods for syncing, disconnecting, and inspecting state.\n */\nexport class TestDevice {\n\treadonly name: string\n\treadonly store: Store\n\treadonly emitter: KoraEventEmitter & { clear(): void }\n\t/** Content-addressed blob store for out-of-band blob bytes (chunks + full blobs). */\n\treadonly blobStore = new MemoryBlobStore()\n\n\tprivate readonly schema: SchemaDefinition\n\tprivate readonly server: TestServer\n\tprivate readonly mergeEngine: MergeEngine\n\tprivate readonly createTransportPair: TestDeviceOptions['createTransportPair']\n\tprivate readonly adapter: StorageAdapter\n\tprivate readonly dbPath: string\n\tprivate readonly syncSchemaVersion: number\n\tprivate readonly operationTransforms: OperationTransform[]\n\n\tprivate applyPipeline: ApplyPipeline | null = null\n\tprivate syncEngine: SyncEngine | null = null\n\tprivate blobChunkProvider: ChunkProvider | null = null\n\tprivate currentTransport: SyncTransport | null = null\n\tprivate unsubscribeSync: (() => void) | null = null\n\tprivate unsubscribeAudit: (() => void) | null = null\n\tprivate closing = false\n\n\tconstructor(options: TestDeviceOptions) {\n\t\tthis.name = options.name\n\t\tthis.schema = options.schema\n\t\tthis.server = options.server\n\t\tthis.createTransportPair = options.createTransportPair\n\t\tthis.dbPath = `${options.tmpDir}/test-device-${options.name}.db`\n\t\tthis.syncSchemaVersion = options.syncSchemaVersion ?? options.schema.version\n\t\tthis.operationTransforms = options.operationTransforms ?? []\n\n\t\tthis.emitter = new SimpleEventEmitter()\n\t\tthis.mergeEngine = new MergeEngine()\n\t\tthis.adapter = new BetterSqlite3Adapter(this.dbPath)\n\t\tthis.store = new Store({\n\t\t\tschema: options.schema,\n\t\t\tadapter: this.adapter,\n\t\t\temitter: this.emitter,\n\t\t})\n\t}\n\n\t/**\n\t * Open the store (must be called before sync or collection operations).\n\t */\n\tasync open(): Promise<void> {\n\t\tawait this.store.open()\n\t\tthis.applyPipeline = new ApplyPipeline({\n\t\t\tstore: this.store,\n\t\t\tmergeEngine: this.mergeEngine,\n\t\t\temitter: this.emitter,\n\t\t})\n\t\tthis.store.setLocalMutationHandler(this.applyPipeline)\n\t\t// Match production wiring (createApp): merge/constraint traces persist to\n\t\t// `_kora_audit_traces`. Without this, harness devices emit merge events\n\t\t// but the durable audit trail every real app has stays empty — a fidelity\n\t\t// gap that hid from tests until Studio's Merges view made it visible.\n\t\tthis.unsubscribeAudit = wireAuditPersistence(this.store, this.emitter)\n\t}\n\n\t/**\n\t * Connect to the test server and perform initial sync.\n\t * If already connected, flushes any pending operations.\n\t */\n\tasync sync(): Promise<void> {\n\t\tif (this.syncEngine && this.currentTransport?.isConnected()) {\n\t\t\t// Already connected — flush outbound ops, then allow inbound relay to settle\n\t\t\tawait this.waitForPendingOps()\n\t\t\tawait this.waitForSettled()\n\t\t\treturn\n\t\t}\n\n\t\t// Create a new transport pair and connect\n\t\tconst { client, serverTransport } = this.createTransportPair()\n\t\tthis.currentTransport = client\n\n\t\tconst conflictHandler: { fn?: () => void } = {}\n\t\tconst syncStore = new MergeAwareSyncStore(this.store, this.mergeEngine, this.emitter, {\n\t\t\tonMergeConflict: () => conflictHandler.fn?.(),\n\t\t})\n\n\t\tthis.syncEngine = new SyncEngine({\n\t\t\ttransport: client,\n\t\t\tstore: syncStore,\n\t\t\tqueueStorage: new StoreQueueStorage(this.adapter),\n\t\t\tsyncState: new StoreSyncStatePersistence(this.store),\n\t\t\tconfig: {\n\t\t\t\turl: 'ws://test-network',\n\t\t\t\tschemaVersion: this.syncSchemaVersion,\n\t\t\t\toperationTransforms:\n\t\t\t\t\tthis.operationTransforms.length > 0 ? this.operationTransforms : undefined,\n\t\t\t},\n\t\t\temitter: this.emitter,\n\t\t})\n\t\tconflictHandler.fn = () => this.syncEngine?.recordConflict()\n\n\t\t// Bind the blob chunk channel to this connection: serve chunks this device\n\t\t// holds, and prepare a provider to pull chunks it needs. Both ride the same\n\t\t// sync socket; the request/response handlers are disjoint by message type.\n\t\tconst chunkPort = createSyncEngineChunkPort(this.syncEngine)\n\t\tserveBlobChunks(chunkPort, this.blobStore)\n\t\tthis.blobChunkProvider = createRemoteChunkProvider(chunkPort)\n\t\t// Mirror createApp: auto-upload blob bytes to the server as ops sync, so\n\t\t// blobs stay available after this device disconnects.\n\t\twireBlobUpload(this.emitter, this.syncEngine, this.blobStore)\n\n\t\t// Wire local mutations to sync outbound queue\n\t\tconst engine = this.syncEngine\n\t\tthis.unsubscribeSync = this.emitter.on('operation:created', (event) => {\n\t\t\tif (!this.closing && this.syncEngine === engine && this.currentTransport?.isConnected()) {\n\t\t\t\t// Catch async errors from push racing with disconnect during teardown\n\t\t\t\tthis.syncEngine.pushOperation(event.operation).catch(() => {})\n\t\t\t}\n\t\t})\n\n\t\t// Register server-side connection\n\t\tthis.server.handleConnection(serverTransport)\n\n\t\t// Start sync engine (connects, handshakes, exchanges deltas)\n\t\tawait this.syncEngine.start()\n\n\t\t// Wait for sync messages to propagate (in-memory transport is synchronous\n\t\t// but some processing is async)\n\t\tawait this.waitForSettled()\n\t}\n\n\t/**\n\t * Disconnect from the test server.\n\t */\n\tasync disconnect(): Promise<void> {\n\t\tif (this.unsubscribeSync) {\n\t\t\tthis.unsubscribeSync()\n\t\t\tthis.unsubscribeSync = null\n\t\t}\n\t\tif (this.syncEngine) {\n\t\t\tawait this.syncEngine.stop()\n\t\t\tthis.syncEngine = null\n\t\t}\n\t\tthis.blobChunkProvider = null\n\t\tthis.currentTransport = null\n\t}\n\n\t/**\n\t * Reconnect to the test server after a disconnect.\n\t */\n\tasync reconnect(): Promise<void> {\n\t\tawait this.sync()\n\t}\n\n\t/**\n\t * Get a collection accessor for performing CRUD operations.\n\t */\n\tcollection(name: string): CollectionAccessor {\n\t\treturn this.store.collection(name)\n\t}\n\n\t/**\n\t * Get all records from a collection (convenience method).\n\t */\n\tasync getState(collectionName: string): Promise<Record<string, unknown>[]> {\n\t\tconst accessor = this.store.collection(collectionName)\n\t\treturn accessor.where({}).exec()\n\t}\n\n\t/**\n\t * Get the device's node ID.\n\t */\n\tgetNodeId(): string {\n\t\treturn this.store.getNodeId()\n\t}\n\n\t/** Exposes the sync engine for integration tests (e.g. doc channel, chaos). */\n\tgetSyncEngine(): SyncEngine | null {\n\t\treturn this.syncEngine\n\t}\n\n\t/**\n\t * Operations the server rejected for this device that have not been reconciled.\n\t * Empty when the device has never connected.\n\t */\n\tasync getRejectedOperations(): Promise<import('@korajs/sync').RejectedOperation[]> {\n\t\tif (!this.syncEngine) return []\n\t\treturn this.syncEngine.getRejectedOperations()\n\t}\n\n\t/**\n\t * Stage a blob's bytes into this device's blob store, splitting them into\n\t * content-addressed chunks this device can then serve to peers over the sync\n\t * connection. Returns the manifest (blob hash + ordered chunk hashes) needed\n\t * to pull the blob elsewhere.\n\t */\n\tasync stageBlob(bytes: Uint8Array, options?: { chunkSize?: number }): Promise<BlobManifest> {\n\t\tconst { manifest } = await prepareBlobForSend(bytes, this.blobStore, options)\n\t\treturn manifest\n\t}\n\n\t/**\n\t * Store a blob for transfer the way `app.blobs.put` does: stage chunks, store\n\t * the full blob, and store the manifest as its own content-addressed object.\n\t * The returned reference carries a `manifestHash`, so a peer can pull the bytes\n\t * knowing only the reference.\n\t */\n\tasync putBlob(\n\t\tbytes: Uint8Array,\n\t\toptions?: { chunkSize?: number; mimeType?: string; filename?: string },\n\t): Promise<{ ref: BlobRef; manifest: BlobManifest }> {\n\t\treturn putBlobForTransfer(this.blobStore, bytes, options)\n\t}\n\n\t/**\n\t * Pull a blob's bytes over the live connection knowing only its reference: the\n\t * manifest is resolved by `ref.manifestHash` first, then the chunks are fetched.\n\t */\n\tasync pullBlobByRef(ref: BlobRef): Promise<ReceiveBlobResult> {\n\t\tif (!this.blobChunkProvider) {\n\t\t\tthrow new Error('Cannot pull a blob before the device has connected (call sync() first)')\n\t\t}\n\t\tconst manifest = await resolveBlobManifest(this.blobChunkProvider, ref)\n\t\treturn receiveBlob(manifest, this.blobChunkProvider, {\n\t\t\tchunkStore: this.blobStore,\n\t\t\tblobStore: this.blobStore,\n\t\t})\n\t}\n\n\t/**\n\t * Pull a blob's bytes from peers over the live sync connection. Requests only\n\t * the chunks this device is missing, reassembles them, and verifies integrity\n\t * against the manifest's blob hash before storing the full blob locally.\n\t */\n\tasync pullBlob(manifest: BlobManifest): Promise<ReceiveBlobResult> {\n\t\tif (!this.blobChunkProvider) {\n\t\t\tthrow new Error('Cannot pull a blob before the device has connected (call sync() first)')\n\t\t}\n\t\treturn receiveBlob(manifest, this.blobChunkProvider, {\n\t\t\tchunkStore: this.blobStore,\n\t\t\tblobStore: this.blobStore,\n\t\t})\n\t}\n\n\t/** Read fully-assembled blob bytes from this device's store, or null if absent. */\n\tasync getBlobBytes(hash: string): Promise<Uint8Array | null> {\n\t\treturn this.blobStore.get(hash)\n\t}\n\n\t/**\n\t * Get the device's version vector.\n\t */\n\tgetVersionVector(): VersionVector {\n\t\treturn this.store.getVersionVector()\n\t}\n\n\t/**\n\t * Check if the device is currently connected to the server.\n\t */\n\tisConnected(): boolean {\n\t\treturn this.currentTransport?.isConnected() ?? false\n\t}\n\n\t/**\n\t * Close the device, releasing all resources.\n\t */\n\tasync close(): Promise<void> {\n\t\tthis.closing = true\n\t\tif (this.unsubscribeAudit) {\n\t\t\tthis.unsubscribeAudit()\n\t\t\tthis.unsubscribeAudit = null\n\t\t}\n\t\tawait this.disconnect()\n\t\tawait this.store.close()\n\t\tthis.emitter.clear()\n\t}\n\n\t/**\n\t * Wait for in-flight sync operations to settle.\n\t * In-memory transports are near-synchronous, but apply/relay work is async.\n\t */\n\tprivate async waitForSettled(): Promise<void> {\n\t\tfor (let i = 0; i < 15; i++) {\n\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 20))\n\t\t}\n\t}\n\n\t/**\n\t * Wait for all pending outbound operations to be acknowledged.\n\t */\n\tprivate async waitForPendingOps(): Promise<void> {\n\t\tif (!this.syncEngine) return\n\t\tconst maxWait = 2000\n\t\tconst start = Date.now()\n\t\twhile (Date.now() - start < maxWait) {\n\t\t\tconst status = this.syncEngine.getStatus()\n\t\t\tif (status.pendingOperations === 0) return\n\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 10))\n\t\t}\n\t}\n}\n","import type { BlobRef, Operation, SchemaDefinition } from '@korajs/core'\nimport { MemoryServerStore } from '@korajs/server'\nimport { KoraSyncServer } from '@korajs/server'\nimport type { OperationValidator, ServerTransport } from '@korajs/server'\nimport { type ContentAddressedBlobStore, createMemoryServerBlobStore } from '@korajs/store'\n\n/**\n * In-memory test server wrapping KoraSyncServer with MemoryServerStore.\n * Handles client connections via memory transports.\n */\nexport interface TestServerOptions {\n\t/** Handshake schema version advertised by the server. Defaults to `schema.version`. */\n\tschemaVersion?: number\n\t/** Inclusive client schema versions accepted at handshake. */\n\tsupportedSchemaVersions?: { min: number; max: number }\n\t/** Enable central blob storage: the server persists and serves uploaded blob bytes. */\n\tblobStorage?: boolean\n\t/** Adjudicate untrusted client operations before materialization. */\n\tvalidateOperation?: OperationValidator\n}\n\nexport class TestServer {\n\treadonly store: MemoryServerStore\n\t/** The server's central blob store, present when `blobStorage` was enabled. */\n\treadonly blobStore: ContentAddressedBlobStore | null\n\tprivate readonly syncServer: KoraSyncServer\n\n\tconstructor(schema: SchemaDefinition, options?: TestServerOptions) {\n\t\tthis.store = new MemoryServerStore()\n\t\tconst schemaVersion = options?.schemaVersion ?? schema.version\n\t\tconst blob = options?.blobStorage ? createMemoryServerBlobStore() : null\n\t\tthis.blobStore = blob?.store ?? null\n\t\tthis.syncServer = new KoraSyncServer({\n\t\t\tstore: this.store,\n\t\t\tschemaVersion,\n\t\t\tsupportedSchemaVersions: options?.supportedSchemaVersions ?? {\n\t\t\t\tmin: schemaVersion,\n\t\t\t\tmax: schemaVersion,\n\t\t\t},\n\t\t\t...(blob ? blob.callbacks : {}),\n\t\t\t...(options?.validateOperation ? { validateOperation: options.validateOperation } : {}),\n\t\t})\n\t\tvoid this.store.setSchema(schema)\n\t}\n\n\t/**\n\t * Register a client connection transport with the server.\n\t * Returns the session ID assigned by the server.\n\t */\n\thandleConnection(transport: ServerTransport): string {\n\t\treturn this.syncServer.handleConnection(transport)\n\t}\n\n\t/**\n\t * Get all operations stored on the server.\n\t */\n\tgetAllOperations(): Operation[] {\n\t\treturn this.store.getAllOperations()\n\t}\n\n\t/**\n\t * Get the number of connected clients.\n\t */\n\tgetConnectionCount(): number {\n\t\treturn this.syncServer.getConnectionCount()\n\t}\n\n\t/** Every blob reference still reachable from live records on the server. */\n\tgetLiveBlobRefs(): Promise<BlobRef[]> {\n\t\treturn this.syncServer.getLiveBlobRefs()\n\t}\n\n\t/**\n\t * Shut down the server and close all sessions.\n\t */\n\tasync close(): Promise<void> {\n\t\tawait this.syncServer.stop()\n\t\tawait this.store.close()\n\t}\n}\n","import type { SchemaDefinition } from '@korajs/core'\nimport type { TestDevice } from './test-device'\n\n/**\n * Result of a convergence check.\n */\nexport interface ConvergenceResult {\n\t/** Whether all devices have converged to the same state */\n\tconverged: boolean\n\t/** Per-collection comparison details (only populated on failure) */\n\tdifferences: CollectionDifference[]\n}\n\n/**\n * Describes a difference in a collection between devices.\n */\nexport interface CollectionDifference {\n\tcollection: string\n\tdeviceA: string\n\tdeviceB: string\n\t/** Records present in deviceA but not deviceB */\n\tmissingInB: string[]\n\t/** Records present in deviceB but not deviceA */\n\tmissingInA: string[]\n\t/** Records present in both but with different values */\n\tfieldDifferences: FieldDifference[]\n}\n\n/**\n * A specific field-level difference between two devices.\n */\nexport interface FieldDifference {\n\trecordId: string\n\tfield: string\n\tvalueInA: unknown\n\tvalueInB: unknown\n}\n\n/**\n * Assert that all devices have converged to identical collection states.\n *\n * Compares every collection across all device pairs. Throws an error\n * with detailed diagnostics if any differences are found.\n *\n * @param devices - The devices to check for convergence\n * @param schema - The schema (used to enumerate collections)\n *\n * @example\n * ```typescript\n * await deviceA.sync()\n * await deviceB.sync()\n * await expectConverged([deviceA, deviceB], schema)\n * ```\n */\nexport async function expectConverged(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n): Promise<void> {\n\tif (devices.length < 2) return\n\n\tconst result = await checkConvergence(devices, schema)\n\tif (!result.converged) {\n\t\tconst details = result.differences\n\t\t\t.map((d) => {\n\t\t\t\tconst parts: string[] = [\n\t\t\t\t\t` Collection \"${d.collection}\" differs between ${d.deviceA} and ${d.deviceB}:`,\n\t\t\t\t]\n\t\t\t\tif (d.missingInB.length > 0) {\n\t\t\t\t\tparts.push(` Missing in ${d.deviceB}: ${d.missingInB.join(', ')}`)\n\t\t\t\t}\n\t\t\t\tif (d.missingInA.length > 0) {\n\t\t\t\t\tparts.push(` Missing in ${d.deviceA}: ${d.missingInA.join(', ')}`)\n\t\t\t\t}\n\t\t\t\tfor (const fd of d.fieldDifferences) {\n\t\t\t\t\tparts.push(\n\t\t\t\t\t\t` Record \"${fd.recordId}\" field \"${fd.field}\": ${JSON.stringify(fd.valueInA)} vs ${JSON.stringify(fd.valueInB)}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn parts.join('\\n')\n\t\t\t})\n\t\t\t.join('\\n')\n\n\t\tthrow new Error(`Devices have not converged:\\n${details}`)\n\t}\n}\n\n/**\n * Poll until all devices converge or timeout. Use after sync in integration tests\n * where relay/apply work may still be in flight under parallel CI load.\n */\nexport async function expectConvergedEventually(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n\toptions?: { timeoutMs?: number; intervalMs?: number },\n): Promise<void> {\n\tif (devices.length < 2) return\n\n\tconst timeoutMs = options?.timeoutMs ?? 5000\n\tconst intervalMs = options?.intervalMs ?? 25\n\tconst deadline = Date.now() + timeoutMs\n\n\twhile (Date.now() < deadline) {\n\t\tconst result = await checkConvergence(devices, schema)\n\t\tif (result.converged) {\n\t\t\treturn\n\t\t}\n\t\tawait new Promise<void>((resolve) => setTimeout(resolve, intervalMs))\n\t}\n\n\tawait expectConverged(devices, schema)\n}\n\n/**\n * Check whether all devices have converged without throwing.\n *\n * @param devices - The devices to check\n * @param schema - The schema (for collection enumeration)\n * @returns Convergence result with details\n */\nexport async function checkConvergence(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n): Promise<ConvergenceResult> {\n\tconst differences: CollectionDifference[] = []\n\tconst collectionNames = Object.keys(schema.collections)\n\n\tfor (let i = 0; i < devices.length - 1; i++) {\n\t\tfor (let j = i + 1; j < devices.length; j++) {\n\t\t\tconst deviceA = devices[i] as TestDevice\n\t\t\tconst deviceB = devices[j] as TestDevice\n\n\t\t\tfor (const collection of collectionNames) {\n\t\t\t\tconst stateA = await deviceA.getState(collection)\n\t\t\t\tconst stateB = await deviceB.getState(collection)\n\n\t\t\t\tconst diff = compareCollectionStates(collection, deviceA.name, deviceB.name, stateA, stateB)\n\n\t\t\t\tif (diff) {\n\t\t\t\t\tdifferences.push(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tconverged: differences.length === 0,\n\t\tdifferences,\n\t}\n}\n\n/**\n * Compare two collection states and return differences if any.\n */\nfunction compareCollectionStates(\n\tcollection: string,\n\tnameA: string,\n\tnameB: string,\n\tstateA: Record<string, unknown>[],\n\tstateB: Record<string, unknown>[],\n): CollectionDifference | null {\n\tconst mapA = new Map(stateA.map((r) => [r.id as string, r]))\n\tconst mapB = new Map(stateB.map((r) => [r.id as string, r]))\n\n\tconst missingInB: string[] = []\n\tconst missingInA: string[] = []\n\tconst fieldDifferences: FieldDifference[] = []\n\n\t// Check records in A\n\tfor (const [id, recordA] of mapA) {\n\t\tconst recordB = mapB.get(id)\n\t\tif (!recordB) {\n\t\t\tmissingInB.push(id)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Compare fields (skip internal fields like _created_at, _updated_at)\n\t\tconst allFields = new Set([\n\t\t\t...Object.keys(recordA).filter((k) => !k.startsWith('_')),\n\t\t\t...Object.keys(recordB).filter((k) => !k.startsWith('_')),\n\t\t])\n\n\t\tfor (const field of allFields) {\n\t\t\tif (field === 'id') continue\n\t\t\tconst valA = recordA[field]\n\t\t\tconst valB = recordB[field]\n\t\t\tif (!deepEqual(valA, valB)) {\n\t\t\t\tfieldDifferences.push({\n\t\t\t\t\trecordId: id,\n\t\t\t\t\tfield,\n\t\t\t\t\tvalueInA: valA,\n\t\t\t\t\tvalueInB: valB,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check records only in B\n\tfor (const id of mapB.keys()) {\n\t\tif (!mapA.has(id)) {\n\t\t\tmissingInA.push(id)\n\t\t}\n\t}\n\n\tif (missingInB.length === 0 && missingInA.length === 0 && fieldDifferences.length === 0) {\n\t\treturn null\n\t}\n\n\treturn {\n\t\tcollection,\n\t\tdeviceA: nameA,\n\t\tdeviceB: nameB,\n\t\tmissingInB,\n\t\tmissingInA,\n\t\tfieldDifferences,\n\t}\n}\n\n/**\n * Deep equality check for comparing field values.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n\tif (a === b) return true\n\tif (a === null || b === null) return false\n\tif (a === undefined || b === undefined) return false\n\tif (typeof a !== typeof b) return false\n\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\tif (a.length !== b.length) return false\n\t\treturn a.every((val, i) => deepEqual(val, b[i]))\n\t}\n\n\tif (typeof a === 'object' && typeof b === 'object') {\n\t\tconst keysA = Object.keys(a as Record<string, unknown>)\n\t\tconst keysB = Object.keys(b as Record<string, unknown>)\n\t\tif (keysA.length !== keysB.length) return false\n\t\treturn keysA.every((key) =>\n\t\t\tdeepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n\t\t)\n\t}\n\n\treturn false\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { MessageSerializer, SyncMessage, SyncTransport, TransportOptions } from '@korajs/sync'\nimport { ProtobufMessageSerializer } from '@korajs/sync'\n\n/**\n * A linked client/server transport pair, as consumed by {@link TestDevice}.\n */\nexport interface TransportPair {\n\tclient: SyncTransport\n\tserverTransport: ServerTransport\n}\n\n/**\n * Message types the protobuf serializer models. Ephemeral messages\n * (awareness-update, yjs-doc-update) are JSON-only in the protocol, so they are\n * deep-copied instead of forced through the protobuf envelope.\n */\nconst PROTOBUF_WIRE_TYPES: ReadonlySet<SyncMessage['type']> = new Set([\n\t'handshake',\n\t'handshake-response',\n\t'operation-batch',\n\t'acknowledgment',\n\t'error',\n])\n\n/**\n * Round-trips a message through the real protobuf wire codec: object → protobuf\n * bytes → object. This is what makes a transport pair exercise the true wire\n * format rather than passing live object references between the two ends.\n */\nfunction roundTripThroughProtobuf(\n\tmessage: SyncMessage,\n\tserializer: MessageSerializer,\n): SyncMessage {\n\tif (!PROTOBUF_WIRE_TYPES.has(message.type)) {\n\t\t// Not part of the protobuf schema — still copy so no live reference leaks\n\t\t// across the \"wire\".\n\t\treturn structuredClone(message)\n\t}\n\n\tconst encoded = serializer.encode(message)\n\tif (!(encoded instanceof Uint8Array)) {\n\t\tthrow new Error('Expected the protobuf serializer to encode to bytes')\n\t}\n\treturn serializer.decode(encoded)\n}\n\n/**\n * Wraps a client transport so every outbound message crosses the protobuf wire.\n */\nclass ProtobufWireClientTransport implements SyncTransport {\n\tconstructor(\n\t\tprivate readonly inner: SyncTransport,\n\t\tprivate readonly serializer: MessageSerializer,\n\t) {}\n\n\tconnect(url: string, options?: TransportOptions): Promise<void> {\n\t\treturn this.inner.connect(url, options)\n\t}\n\n\tdisconnect(): Promise<void> {\n\t\treturn this.inner.disconnect()\n\t}\n\n\tsend(message: SyncMessage): void {\n\t\tthis.inner.send(roundTripThroughProtobuf(message, this.serializer))\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n}\n\n/**\n * Wraps a server transport so every outbound message crosses the protobuf wire.\n */\nclass ProtobufWireServerTransport implements ServerTransport {\n\tconstructor(\n\t\tprivate readonly inner: ServerTransport,\n\t\tprivate readonly serializer: MessageSerializer,\n\t) {}\n\n\tsend(message: SyncMessage): void {\n\t\tthis.inner.send(roundTripThroughProtobuf(message, this.serializer))\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (code: number, reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.inner.close(code, reason)\n\t}\n}\n\n/**\n * Wraps a transport pair so that every sync message exchanged between the two\n * ends is encoded to protobuf bytes and decoded back — a faithful stand-in for\n * a real network hop that uses the protobuf wire format.\n *\n * Both directions are wrapped, so operations, handshakes, batches, and acks all\n * travel as bytes rather than shared object references.\n *\n * @param pair - The underlying (in-memory) transport pair to wrap\n * @returns A transport pair that routes messages through the protobuf codec\n */\nexport function wrapTransportPairWithProtobufWire(pair: TransportPair): TransportPair {\n\tconst serializer = new ProtobufMessageSerializer()\n\treturn {\n\t\tclient: new ProtobufWireClientTransport(pair.client, serializer),\n\t\tserverTransport: new ProtobufWireServerTransport(pair.serverTransport, serializer),\n\t}\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { SyncMessage } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\n\n/**\n * Wraps a server transport so its handshake responses advertise a caller-chosen\n * wall-clock time, independent of the process `Date.now()`.\n *\n * The sync engine measures clock skew as `serverTime - Date.now()` at handshake.\n * To simulate a device whose clock is fast while the server's clock is correct —\n * both running in one test process — the client mocks `Date.now()` and this\n * wrapper injects the server's true time into the handshake response, so the\n * skew the engine sees is real.\n */\nclass ServerClockServerTransport implements ServerTransport {\n\tconstructor(\n\t\tprivate readonly inner: ServerTransport,\n\t\tprivate readonly serverNow: () => number,\n\t) {}\n\n\tsend(message: SyncMessage): void {\n\t\tif (message.type === 'handshake-response') {\n\t\t\tthis.inner.send({ ...message, serverTime: this.serverNow() })\n\t\t\treturn\n\t\t}\n\t\tthis.inner.send(message)\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (code: number, reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.inner.close(code, reason)\n\t}\n}\n\n/**\n * Wraps a transport pair so the server side reports `serverNow()` as its\n * handshake wall-clock time.\n *\n * @param pair - The underlying transport pair\n * @param serverNow - Returns the server's true current time (ms since epoch)\n * @returns A transport pair whose handshake responses carry the injected time\n */\nexport function wrapTransportPairWithServerClock(\n\tpair: TransportPair,\n\tserverNow: () => number,\n): TransportPair {\n\treturn {\n\t\tclient: pair.client,\n\t\tserverTransport: new ServerClockServerTransport(pair.serverTransport, serverNow),\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAA4B;AAC5B,qBAAuB;AACvB,uBAAqB;AAErB,IAAAA,mBAA0C;AAE1C,IAAAC,eAA+B;;;ACF/B,sBAAmC;AACnC,mBAA4B;AAC5B,mBASO;AAQP,4BAAqC;AACrC,kBAA2B;AAE3B,qBAQO;AA+BA,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,YAAY,IAAI,6BAAgB;AAAA,EAExB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,gBAAsC;AAAA,EACtC,aAAgC;AAAA,EAChC,oBAA0C;AAAA,EAC1C,mBAAyC;AAAA,EACzC,kBAAuC;AAAA,EACvC,mBAAwC;AAAA,EACxC,UAAU;AAAA,EAElB,YAAY,SAA4B;AACvC,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,QAAQ;AACtB,SAAK,sBAAsB,QAAQ;AACnC,SAAK,SAAS,GAAG,QAAQ,MAAM,gBAAgB,QAAQ,IAAI;AAC3D,SAAK,oBAAoB,QAAQ,qBAAqB,QAAQ,OAAO;AACrE,SAAK,sBAAsB,QAAQ,uBAAuB,CAAC;AAE3D,SAAK,UAAU,IAAI,mCAAmB;AACtC,SAAK,cAAc,IAAI,yBAAY;AACnC,SAAK,UAAU,IAAI,2CAAqB,KAAK,MAAM;AACnD,SAAK,QAAQ,IAAI,mBAAM;AAAA,MACtB,QAAQ,QAAQ;AAAA,MAChB,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,IACf,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC3B,UAAM,KAAK,MAAM,KAAK;AACtB,SAAK,gBAAgB,IAAI,6BAAc;AAAA,MACtC,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK;AAAA,MAClB,SAAS,KAAK;AAAA,IACf,CAAC;AACD,SAAK,MAAM,wBAAwB,KAAK,aAAa;AAKrD,SAAK,uBAAmB,qCAAqB,KAAK,OAAO,KAAK,OAAO;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAsB;AAC3B,QAAI,KAAK,cAAc,KAAK,kBAAkB,YAAY,GAAG;AAE5D,YAAM,KAAK,kBAAkB;AAC7B,YAAM,KAAK,eAAe;AAC1B;AAAA,IACD;AAGA,UAAM,EAAE,QAAQ,gBAAgB,IAAI,KAAK,oBAAoB;AAC7D,SAAK,mBAAmB;AAExB,UAAM,kBAAuC,CAAC;AAC9C,UAAM,YAAY,IAAI,mCAAoB,KAAK,OAAO,KAAK,aAAa,KAAK,SAAS;AAAA,MACrF,iBAAiB,MAAM,gBAAgB,KAAK;AAAA,IAC7C,CAAC;AAED,SAAK,aAAa,IAAI,uBAAW;AAAA,MAChC,WAAW;AAAA,MACX,OAAO;AAAA,MACP,cAAc,IAAI,iCAAkB,KAAK,OAAO;AAAA,MAChD,WAAW,IAAI,yCAA0B,KAAK,KAAK;AAAA,MACnD,QAAQ;AAAA,QACP,KAAK;AAAA,QACL,eAAe,KAAK;AAAA,QACpB,qBACC,KAAK,oBAAoB,SAAS,IAAI,KAAK,sBAAsB;AAAA,MACnE;AAAA,MACA,SAAS,KAAK;AAAA,IACf,CAAC;AACD,oBAAgB,KAAK,MAAM,KAAK,YAAY,eAAe;AAK3D,UAAM,gBAAY,0CAA0B,KAAK,UAAU;AAC3D,sCAAgB,WAAW,KAAK,SAAS;AACzC,SAAK,wBAAoB,wCAA0B,SAAS;AAG5D,uCAAe,KAAK,SAAS,KAAK,YAAY,KAAK,SAAS;AAG5D,UAAM,SAAS,KAAK;AACpB,SAAK,kBAAkB,KAAK,QAAQ,GAAG,qBAAqB,CAAC,UAAU;AACtE,UAAI,CAAC,KAAK,WAAW,KAAK,eAAe,UAAU,KAAK,kBAAkB,YAAY,GAAG;AAExF,aAAK,WAAW,cAAc,MAAM,SAAS,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC9D;AAAA,IACD,CAAC;AAGD,SAAK,OAAO,iBAAiB,eAAe;AAG5C,UAAM,KAAK,WAAW,MAAM;AAI5B,UAAM,KAAK,eAAe;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AACjC,QAAI,KAAK,iBAAiB;AACzB,WAAK,gBAAgB;AACrB,WAAK,kBAAkB;AAAA,IACxB;AACA,QAAI,KAAK,YAAY;AACpB,YAAM,KAAK,WAAW,KAAK;AAC3B,WAAK,aAAa;AAAA,IACnB;AACA,SAAK,oBAAoB;AACzB,SAAK,mBAAmB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAA2B;AAChC,UAAM,KAAK,KAAK;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAkC;AAC5C,WAAO,KAAK,MAAM,WAAW,IAAI;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,gBAA4D;AAC1E,UAAM,WAAW,KAAK,MAAM,WAAW,cAAc;AACrD,WAAO,SAAS,MAAM,CAAC,CAAC,EAAE,KAAK;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAoB;AACnB,WAAO,KAAK,MAAM,UAAU;AAAA,EAC7B;AAAA;AAAA,EAGA,gBAAmC;AAClC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,wBAA6E;AAClF,QAAI,CAAC,KAAK,WAAY,QAAO,CAAC;AAC9B,WAAO,KAAK,WAAW,sBAAsB;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAU,OAAmB,SAAyD;AAC3F,UAAM,EAAE,SAAS,IAAI,UAAM,iCAAmB,OAAO,KAAK,WAAW,OAAO;AAC5E,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QACL,OACA,SACoD;AACpD,eAAO,iCAAmB,KAAK,WAAW,OAAO,OAAO;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,KAA0C;AAC7D,QAAI,CAAC,KAAK,mBAAmB;AAC5B,YAAM,IAAI,MAAM,wEAAwE;AAAA,IACzF;AACA,UAAM,WAAW,UAAM,kCAAoB,KAAK,mBAAmB,GAAG;AACtE,eAAO,0BAAY,UAAU,KAAK,mBAAmB;AAAA,MACpD,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,IACjB,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAAS,UAAoD;AAClE,QAAI,CAAC,KAAK,mBAAmB;AAC5B,YAAM,IAAI,MAAM,wEAAwE;AAAA,IACzF;AACA,eAAO,0BAAY,UAAU,KAAK,mBAAmB;AAAA,MACpD,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,IACjB,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,aAAa,MAA0C;AAC5D,WAAO,KAAK,UAAU,IAAI,IAAI;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAkC;AACjC,WAAO,KAAK,MAAM,iBAAiB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,cAAuB;AACtB,WAAO,KAAK,kBAAkB,YAAY,KAAK;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC5B,SAAK,UAAU;AACf,QAAI,KAAK,kBAAkB;AAC1B,WAAK,iBAAiB;AACtB,WAAK,mBAAmB;AAAA,IACzB;AACA,UAAM,KAAK,WAAW;AACtB,UAAM,KAAK,MAAM,MAAM;AACvB,SAAK,QAAQ,MAAM;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,iBAAgC;AAC7C,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC5B,YAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,IAC7D;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,oBAAmC;AAChD,QAAI,CAAC,KAAK,WAAY;AACtB,UAAM,UAAU;AAChB,UAAM,QAAQ,KAAK,IAAI;AACvB,WAAO,KAAK,IAAI,IAAI,QAAQ,SAAS;AACpC,YAAM,SAAS,KAAK,WAAW,UAAU;AACzC,UAAI,OAAO,sBAAsB,EAAG;AACpC,YAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,IAC7D;AAAA,EACD;AACD;;;ACxWA,oBAAkC;AAClC,IAAAC,iBAA+B;AAE/B,IAAAC,gBAA4E;AAiBrE,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,EACQ;AAAA,EAEjB,YAAY,QAA0B,SAA6B;AAClE,SAAK,QAAQ,IAAI,gCAAkB;AACnC,UAAM,gBAAgB,SAAS,iBAAiB,OAAO;AACvD,UAAM,OAAO,SAAS,kBAAc,2CAA4B,IAAI;AACpE,SAAK,YAAY,MAAM,SAAS;AAChC,SAAK,aAAa,IAAI,8BAAe;AAAA,MACpC,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,yBAAyB,SAAS,2BAA2B;AAAA,QAC5D,KAAK;AAAA,QACL,KAAK;AAAA,MACN;AAAA,MACA,GAAI,OAAO,KAAK,YAAY,CAAC;AAAA,MAC7B,GAAI,SAAS,oBAAoB,EAAE,mBAAmB,QAAQ,kBAAkB,IAAI,CAAC;AAAA,IACtF,CAAC;AACD,SAAK,KAAK,MAAM,UAAU,MAAM;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,WAAoC;AACpD,WAAO,KAAK,WAAW,iBAAiB,SAAS;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAgC;AAC/B,WAAO,KAAK,MAAM,iBAAiB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC5B,WAAO,KAAK,WAAW,mBAAmB;AAAA,EAC3C;AAAA;AAAA,EAGA,kBAAsC;AACrC,WAAO,KAAK,WAAW,gBAAgB;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC5B,UAAM,KAAK,WAAW,KAAK;AAC3B,UAAM,KAAK,MAAM,MAAM;AAAA,EACxB;AACD;;;AFJA,eAAsB,kBACrB,QACA,SACuB;AACvB,QAAM,cAAc,SAAS,WAAW;AACxC,QAAM,cACL,SAAS,eAAe,MAAM,KAAK,EAAE,QAAQ,YAAY,GAAG,CAAC,GAAG,MAAM,UAAU,CAAC,EAAE;AAGpF,QAAM,aAAS,gCAAY,2BAAK,uBAAO,GAAG,YAAY,CAAC;AAGvD,QAAM,SAAS,IAAI,WAAW,QAAQ;AAAA,IACrC,aAAa,SAAS;AAAA,IACtB,mBAAmB,SAAS;AAAA,EAC7B,CAAC;AAGD,QAAM,UAAwB,CAAC;AAC/B,aAAW,QAAQ,aAAa;AAC/B,UAAM,SAAS,IAAI,WAAW;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA,qBAAqB,MAAM;AAC1B,cAAM,WAAO,4CAA0B;AACvC,cAAM,SAAS,KAAK;AACpB,cAAM,QAAQ,SAAS;AACvB,cAAM,OAAsB;AAAA,UAC3B,QAAQ,QAAQ,IAAI,4BAAe,QAAQ,KAAK,IAAI;AAAA,UACpD,iBAAiB,KAAK;AAAA,QACvB;AACA,eAAO,SAAS,gBAAgB,QAAQ,cAAc,IAAI,IAAI;AAAA,MAC/D;AAAA,MACA;AAAA,IACD,CAAC;AACD,UAAM,OAAO,KAAK;AAClB,YAAQ,KAAK,MAAM;AAAA,EACpB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,QAAuB;AAC5B,iBAAW,UAAU,SAAS;AAC7B,cAAM,OAAO,MAAM;AAAA,MACpB;AACA,YAAM,OAAO,MAAM;AAEnB,UAAI;AACH,cAAM,EAAE,OAAO,IAAI,MAAM,OAAO,IAAS;AACzC,eAAO,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MAChD,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AACD;AAgBA,eAAsB,uBACrB,cACA,eACA,eACuB;AACvB,QAAM,aAAS,gCAAY,2BAAK,uBAAO,GAAG,YAAY,CAAC;AACvD,QAAM,SAAS,IAAI,WAAW,cAAc,aAAa;AAEzD,QAAM,UAAwB,CAAC;AAC/B,aAAW,UAAU,eAAe;AACnC,UAAM,SAAS,IAAI,WAAW;AAAA,MAC7B,MAAM,OAAO;AAAA,MACb,QAAQ,OAAO;AAAA,MACf;AAAA,MACA,mBAAmB,OAAO;AAAA,MAC1B,qBAAqB,OAAO;AAAA,MAC5B,qBAAqB,MAAM;AAC1B,cAAM,WAAO,4CAA0B;AACvC,eAAO;AAAA,UACN,QAAQ,KAAK;AAAA,UACb,iBAAiB,KAAK;AAAA,QACvB;AAAA,MACD;AAAA,MACA;AAAA,IACD,CAAC;AACD,UAAM,OAAO,KAAK;AAClB,YAAQ,KAAK,MAAM;AAAA,EACpB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,QAAuB;AAC5B,iBAAW,UAAU,SAAS;AAC7B,cAAM,OAAO,MAAM;AAAA,MACpB;AACA,YAAM,OAAO,MAAM;AACnB,UAAI;AACH,cAAM,EAAE,OAAO,IAAI,MAAM,OAAO,IAAS;AACzC,eAAO,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MAChD,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AACD;;;AG7IA,eAAsB,gBACrB,SACA,QACgB;AAChB,MAAI,QAAQ,SAAS,EAAG;AAExB,QAAM,SAAS,MAAM,iBAAiB,SAAS,MAAM;AACrD,MAAI,CAAC,OAAO,WAAW;AACtB,UAAM,UAAU,OAAO,YACrB,IAAI,CAAC,MAAM;AACX,YAAM,QAAkB;AAAA,QACvB,iBAAiB,EAAE,UAAU,qBAAqB,EAAE,OAAO,QAAQ,EAAE,OAAO;AAAA,MAC7E;AACA,UAAI,EAAE,WAAW,SAAS,GAAG;AAC5B,cAAM,KAAK,kBAAkB,EAAE,OAAO,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC,EAAE;AAAA,MACrE;AACA,UAAI,EAAE,WAAW,SAAS,GAAG;AAC5B,cAAM,KAAK,kBAAkB,EAAE,OAAO,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC,EAAE;AAAA,MACrE;AACA,iBAAW,MAAM,EAAE,kBAAkB;AACpC,cAAM;AAAA,UACL,eAAe,GAAG,QAAQ,YAAY,GAAG,KAAK,MAAM,KAAK,UAAU,GAAG,QAAQ,CAAC,OAAO,KAAK,UAAU,GAAG,QAAQ,CAAC;AAAA,QAClH;AAAA,MACD;AACA,aAAO,MAAM,KAAK,IAAI;AAAA,IACvB,CAAC,EACA,KAAK,IAAI;AAEX,UAAM,IAAI,MAAM;AAAA,EAAgC,OAAO,EAAE;AAAA,EAC1D;AACD;AAMA,eAAsB,0BACrB,SACA,QACA,SACgB;AAChB,MAAI,QAAQ,SAAS,EAAG;AAExB,QAAM,YAAY,SAAS,aAAa;AACxC,QAAM,aAAa,SAAS,cAAc;AAC1C,QAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC7B,UAAM,SAAS,MAAM,iBAAiB,SAAS,MAAM;AACrD,QAAI,OAAO,WAAW;AACrB;AAAA,IACD;AACA,UAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,UAAU,CAAC;AAAA,EACrE;AAEA,QAAM,gBAAgB,SAAS,MAAM;AACtC;AASA,eAAsB,iBACrB,SACA,QAC6B;AAC7B,QAAM,cAAsC,CAAC;AAC7C,QAAM,kBAAkB,OAAO,KAAK,OAAO,WAAW;AAEtD,WAAS,IAAI,GAAG,IAAI,QAAQ,SAAS,GAAG,KAAK;AAC5C,aAAS,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AAC5C,YAAM,UAAU,QAAQ,CAAC;AACzB,YAAM,UAAU,QAAQ,CAAC;AAEzB,iBAAW,cAAc,iBAAiB;AACzC,cAAM,SAAS,MAAM,QAAQ,SAAS,UAAU;AAChD,cAAM,SAAS,MAAM,QAAQ,SAAS,UAAU;AAEhD,cAAM,OAAO,wBAAwB,YAAY,QAAQ,MAAM,QAAQ,MAAM,QAAQ,MAAM;AAE3F,YAAI,MAAM;AACT,sBAAY,KAAK,IAAI;AAAA,QACtB;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AAAA,IACN,WAAW,YAAY,WAAW;AAAA,IAClC;AAAA,EACD;AACD;AAKA,SAAS,wBACR,YACA,OACA,OACA,QACA,QAC8B;AAC9B,QAAM,OAAO,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,IAAc,CAAC,CAAC,CAAC;AAC3D,QAAM,OAAO,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,IAAc,CAAC,CAAC,CAAC;AAE3D,QAAM,aAAuB,CAAC;AAC9B,QAAM,aAAuB,CAAC;AAC9B,QAAM,mBAAsC,CAAC;AAG7C,aAAW,CAAC,IAAI,OAAO,KAAK,MAAM;AACjC,UAAM,UAAU,KAAK,IAAI,EAAE;AAC3B,QAAI,CAAC,SAAS;AACb,iBAAW,KAAK,EAAE;AAClB;AAAA,IACD;AAGA,UAAM,YAAY,oBAAI,IAAI;AAAA,MACzB,GAAG,OAAO,KAAK,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC;AAAA,MACxD,GAAG,OAAO,KAAK,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC;AAAA,IACzD,CAAC;AAED,eAAW,SAAS,WAAW;AAC9B,UAAI,UAAU,KAAM;AACpB,YAAM,OAAO,QAAQ,KAAK;AAC1B,YAAM,OAAO,QAAQ,KAAK;AAC1B,UAAI,CAAC,UAAU,MAAM,IAAI,GAAG;AAC3B,yBAAiB,KAAK;AAAA,UACrB,UAAU;AAAA,UACV;AAAA,UACA,UAAU;AAAA,UACV,UAAU;AAAA,QACX,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAGA,aAAW,MAAM,KAAK,KAAK,GAAG;AAC7B,QAAI,CAAC,KAAK,IAAI,EAAE,GAAG;AAClB,iBAAW,KAAK,EAAE;AAAA,IACnB;AAAA,EACD;AAEA,MAAI,WAAW,WAAW,KAAK,WAAW,WAAW,KAAK,iBAAiB,WAAW,GAAG;AACxF,WAAO;AAAA,EACR;AAEA,SAAO;AAAA,IACN;AAAA,IACA,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AAKA,SAAS,UAAU,GAAY,GAAqB;AACnD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO;AACrC,MAAI,MAAM,UAAa,MAAM,OAAW,QAAO;AAC/C,MAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAElC,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AACzC,QAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,WAAO,EAAE,MAAM,CAAC,KAAK,MAAM,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC;AAAA,EAChD;AAEA,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AACnD,UAAM,QAAQ,OAAO,KAAK,CAA4B;AACtD,UAAM,QAAQ,OAAO,KAAK,CAA4B;AACtD,QAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,WAAO,MAAM;AAAA,MAAM,CAAC,QACnB,UAAW,EAA8B,GAAG,GAAI,EAA8B,GAAG,CAAC;AAAA,IACnF;AAAA,EACD;AAEA,SAAO;AACR;;;AC/OA,IAAAC,eAA0C;AAe1C,IAAM,sBAAwD,oBAAI,IAAI;AAAA,EACrE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC;AAOD,SAAS,yBACR,SACA,YACc;AACd,MAAI,CAAC,oBAAoB,IAAI,QAAQ,IAAI,GAAG;AAG3C,WAAO,gBAAgB,OAAO;AAAA,EAC/B;AAEA,QAAM,UAAU,WAAW,OAAO,OAAO;AACzC,MAAI,EAAE,mBAAmB,aAAa;AACrC,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACtE;AACA,SAAO,WAAW,OAAO,OAAO;AACjC;AAKA,IAAM,8BAAN,MAA2D;AAAA,EAC1D,YACkB,OACA,YAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAGlB,QAAQ,KAAa,SAA2C;AAC/D,WAAO,KAAK,MAAM,QAAQ,KAAK,OAAO;AAAA,EACvC;AAAA,EAEA,aAA4B;AAC3B,WAAO,KAAK,MAAM,WAAW;AAAA,EAC9B;AAAA,EAEA,KAAK,SAA4B;AAChC,SAAK,MAAM,KAAK,yBAAyB,SAAS,KAAK,UAAU,CAAC;AAAA,EACnE;AAAA,EAEA,UAAU,SAA+C;AACxD,SAAK,MAAM,UAAU,OAAO;AAAA,EAC7B;AAAA,EAEA,QAAQ,SAAyC;AAChD,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,QAAQ,SAAuC;AAC9C,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,cAAuB;AACtB,WAAO,KAAK,MAAM,YAAY;AAAA,EAC/B;AACD;AAKA,IAAM,8BAAN,MAA6D;AAAA,EAC5D,YACkB,OACA,YAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAGlB,KAAK,SAA4B;AAChC,SAAK,MAAM,KAAK,yBAAyB,SAAS,KAAK,UAAU,CAAC;AAAA,EACnE;AAAA,EAEA,UAAU,SAA+C;AACxD,SAAK,MAAM,UAAU,OAAO;AAAA,EAC7B;AAAA,EAEA,QAAQ,SAAuD;AAC9D,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,QAAQ,SAAuC;AAC9C,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,cAAuB;AACtB,WAAO,KAAK,MAAM,YAAY;AAAA,EAC/B;AAAA,EAEA,MAAM,MAAe,QAAuB;AAC3C,SAAK,MAAM,MAAM,MAAM,MAAM;AAAA,EAC9B;AACD;AAaO,SAAS,kCAAkC,MAAoC;AACrF,QAAM,aAAa,IAAI,uCAA0B;AACjD,SAAO;AAAA,IACN,QAAQ,IAAI,4BAA4B,KAAK,QAAQ,UAAU;AAAA,IAC/D,iBAAiB,IAAI,4BAA4B,KAAK,iBAAiB,UAAU;AAAA,EAClF;AACD;;;AC1HA,IAAM,6BAAN,MAA4D;AAAA,EAC3D,YACkB,OACA,WAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAGlB,KAAK,SAA4B;AAChC,QAAI,QAAQ,SAAS,sBAAsB;AAC1C,WAAK,MAAM,KAAK,EAAE,GAAG,SAAS,YAAY,KAAK,UAAU,EAAE,CAAC;AAC5D;AAAA,IACD;AACA,SAAK,MAAM,KAAK,OAAO;AAAA,EACxB;AAAA,EAEA,UAAU,SAA+C;AACxD,SAAK,MAAM,UAAU,OAAO;AAAA,EAC7B;AAAA,EAEA,QAAQ,SAAuD;AAC9D,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,QAAQ,SAAuC;AAC9C,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,cAAuB;AACtB,WAAO,KAAK,MAAM,YAAY;AAAA,EAC/B;AAAA,EAEA,MAAM,MAAe,QAAuB;AAC3C,SAAK,MAAM,MAAM,MAAM,MAAM;AAAA,EAC9B;AACD;AAUO,SAAS,iCACf,MACA,WACgB;AAChB,SAAO;AAAA,IACN,QAAQ,KAAK;AAAA,IACb,iBAAiB,IAAI,2BAA2B,KAAK,iBAAiB,SAAS;AAAA,EAChF;AACD;;;AN9BA,IAAAC,eAA+B;","names":["import_internal","import_sync","import_server","import_store","import_sync","import_sync"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/test-network.ts","../src/test-device.ts","../src/test-server.ts","../src/assertions.ts","../src/protobuf-wire-transport.ts","../src/server-clock-transport.ts"],"sourcesContent":["// @korajs/test — testing harness for Kora.js\n// Creates virtual device networks for testing sync convergence and conflicts.\n\n// === Factory ===\nexport { createTestNetwork, createMixedTestNetwork } from './test-network'\nexport type {\n\tTestNetwork,\n\tTestNetworkOptions,\n\tMixedTestDeviceConfig,\n} from './test-network'\n\n// === Device ===\nexport { TestDevice } from './test-device'\nexport type { TestDeviceOptions } from './test-device'\n\n// === Server ===\nexport { TestServer } from './test-server'\nexport type { TestServerOptions } from './test-server'\n\n// === Assertions ===\nexport { checkConvergence, expectConverged, expectConvergedEventually } from './assertions'\nexport type {\n\tCollectionDifference,\n\tConvergenceResult,\n\tFieldDifference,\n} from './assertions'\n\n// === Protobuf wire transport (for testing convergence through the real wire format) ===\nexport { wrapTransportPairWithProtobufWire } from './protobuf-wire-transport'\nexport type { TransportPair } from './protobuf-wire-transport'\n\n// === Server-clock transport (for testing clock-skew / rebase integration) ===\nexport { wrapTransportPairWithServerClock } from './server-clock-transport'\n\n// === Re-export ChaosTransport for convenience ===\nexport { ChaosTransport } from '@korajs/sync'\nexport type { ChaosConfig } from '@korajs/sync'\n","import { mkdtempSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport type { OperationTransform, SchemaDefinition } from '@korajs/core'\nimport { createServerTransportPair } from '@korajs/server/internal'\nimport type { ChaosConfig } from '@korajs/sync'\nimport { ChaosTransport } from '@korajs/sync'\nimport type { SyncTransport } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\nimport { TestDevice } from './test-device'\nimport { TestServer, type TestServerOptions } from './test-server'\n\n/**\n * Options for creating a test network.\n */\nexport interface TestNetworkOptions {\n\t/** Number of devices to create. Defaults to 2. */\n\tdevices?: number\n\t/** Custom device names. If not provided, uses 'device-0', 'device-1', etc. */\n\tdeviceNames?: string[]\n\t/** Optional chaos transport settings applied to each device link. */\n\tchaos?: ChaosConfig\n\t/**\n\t * Optional wrapper applied to each device's transport pair after the base\n\t * (in-memory, optionally chaos-wrapped) pair is created. Use it to route\n\t * messages through the protobuf wire codec\n\t * ({@link wrapTransportPairWithProtobufWire}) or to inject a controllable\n\t * server clock. Called once per device connection.\n\t */\n\twrapTransport?: (pair: TransportPair) => TransportPair\n\t/** Enable central blob storage on the server (persist + serve uploaded blob bytes). */\n\tblobStorage?: boolean\n\t/** Adjudicate untrusted client operations on the server before materialization. */\n\tvalidateOperation?: import('@korajs/server').OperationValidator\n}\n\n/**\n * A test network with a server and multiple devices.\n */\nexport interface TestNetwork {\n\t/** The test server */\n\tserver: TestServer\n\t/** All devices in the network */\n\tdevices: TestDevice[]\n\t/** Temporary directory for DB files */\n\ttmpDir: string\n\t/** Close all devices and the server, clean up temp files */\n\tclose(): Promise<void>\n}\n\n/**\n * Create a test network with a server and multiple virtual devices.\n *\n * Each device has its own local SQLite store and SyncEngine. Devices\n * communicate with the server via in-memory transports.\n *\n * @param schema - The schema all devices share\n * @param options - Network configuration\n * @returns A test network with server and devices ready for use\n *\n * @example\n * ```typescript\n * const network = await createTestNetwork(schema, { devices: 2 })\n * const [deviceA, deviceB] = network.devices\n *\n * await deviceA.collection('todos').insert({ title: 'Hello' })\n * await deviceA.sync()\n * await deviceB.sync()\n *\n * const todos = await deviceB.getState('todos')\n * expect(todos).toHaveLength(1)\n *\n * await network.close()\n * ```\n */\nexport async function createTestNetwork(\n\tschema: SchemaDefinition,\n\toptions?: TestNetworkOptions,\n): Promise<TestNetwork> {\n\tconst deviceCount = options?.devices ?? 2\n\tconst deviceNames =\n\t\toptions?.deviceNames ?? Array.from({ length: deviceCount }, (_, i) => `device-${i}`)\n\n\t// Create temp directory for DB files\n\tconst tmpDir = mkdtempSync(join(tmpdir(), 'kora-test-'))\n\n\t// Create server\n\tconst server = new TestServer(schema, {\n\t\tblobStorage: options?.blobStorage,\n\t\tvalidateOperation: options?.validateOperation,\n\t})\n\n\t// Create devices\n\tconst devices: TestDevice[] = []\n\tfor (const name of deviceNames) {\n\t\tconst device = new TestDevice({\n\t\t\tname,\n\t\t\tschema,\n\t\t\tserver,\n\t\t\tcreateTransportPair: () => {\n\t\t\t\tconst pair = createServerTransportPair()\n\t\t\t\tconst client = pair.client as unknown as SyncTransport\n\t\t\t\tconst chaos = options?.chaos\n\t\t\t\tconst base: TransportPair = {\n\t\t\t\t\tclient: chaos ? new ChaosTransport(client, chaos) : client,\n\t\t\t\t\tserverTransport: pair.server,\n\t\t\t\t}\n\t\t\t\treturn options?.wrapTransport ? options.wrapTransport(base) : base\n\t\t\t},\n\t\t\ttmpDir,\n\t\t})\n\t\tawait device.open()\n\t\tdevices.push(device)\n\t}\n\n\treturn {\n\t\tserver,\n\t\tdevices,\n\t\ttmpDir,\n\t\tasync close(): Promise<void> {\n\t\t\tfor (const device of devices) {\n\t\t\t\tawait device.close()\n\t\t\t}\n\t\t\tawait server.close()\n\t\t\t// Clean up temp DB files\n\t\t\ttry {\n\t\t\t\tconst { rmSync } = await import('node:fs')\n\t\t\t\trmSync(tmpDir, { recursive: true, force: true })\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t},\n\t}\n}\n\n/**\n * Per-device configuration when devices use different local schemas or sync settings.\n */\nexport interface MixedTestDeviceConfig {\n\tname: string\n\tschema: SchemaDefinition\n\tsyncSchemaVersion?: number\n\toperationTransforms?: OperationTransform[]\n}\n\n/**\n * Create a test network where devices may use different schema versions and transforms.\n * The server uses `serverSchema` for materialization and handshake bounds.\n */\nexport async function createMixedTestNetwork(\n\tserverSchema: SchemaDefinition,\n\tserverOptions: TestServerOptions,\n\tdeviceConfigs: MixedTestDeviceConfig[],\n): Promise<TestNetwork> {\n\tconst tmpDir = mkdtempSync(join(tmpdir(), 'kora-test-'))\n\tconst server = new TestServer(serverSchema, serverOptions)\n\n\tconst devices: TestDevice[] = []\n\tfor (const config of deviceConfigs) {\n\t\tconst device = new TestDevice({\n\t\t\tname: config.name,\n\t\t\tschema: config.schema,\n\t\t\tserver,\n\t\t\tsyncSchemaVersion: config.syncSchemaVersion,\n\t\t\toperationTransforms: config.operationTransforms,\n\t\t\tcreateTransportPair: () => {\n\t\t\t\tconst pair = createServerTransportPair()\n\t\t\t\treturn {\n\t\t\t\t\tclient: pair.client as unknown as SyncTransport,\n\t\t\t\t\tserverTransport: pair.server,\n\t\t\t\t}\n\t\t\t},\n\t\t\ttmpDir,\n\t\t})\n\t\tawait device.open()\n\t\tdevices.push(device)\n\t}\n\n\treturn {\n\t\tserver,\n\t\tdevices,\n\t\ttmpDir,\n\t\tasync close(): Promise<void> {\n\t\t\tfor (const device of devices) {\n\t\t\t\tawait device.close()\n\t\t\t}\n\t\t\tawait server.close()\n\t\t\ttry {\n\t\t\t\tconst { rmSync } = await import('node:fs')\n\t\t\t\trmSync(tmpDir, { recursive: true, force: true })\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t},\n\t}\n}\n","import type { OperationTransform, SchemaDefinition } from '@korajs/core'\nimport type { VersionVector } from '@korajs/core'\nimport type { KoraEventEmitter } from '@korajs/core'\nimport type { BlobRef } from '@korajs/core'\nimport { SimpleEventEmitter } from '@korajs/core/internal'\nimport { MergeEngine } from '@korajs/merge'\nimport {\n\tMemoryBlobStore,\n\tStore,\n\tcreateRemoteChunkProvider,\n\tprepareBlobForSend,\n\tputBlobForTransfer,\n\treceiveBlob,\n\tresolveBlobManifest,\n\tserveBlobChunks,\n} from '@korajs/store'\nimport type {\n\tBlobManifest,\n\tChunkProvider,\n\tCollectionAccessor,\n\tReceiveBlobResult,\n\tStorageAdapter,\n} from '@korajs/store'\nimport { BetterSqlite3Adapter } from '@korajs/store/better-sqlite3'\nimport { SyncEngine } from '@korajs/sync'\nimport type { SyncTransport } from '@korajs/sync'\nimport {\n\tApplyPipeline,\n\tMergeAwareSyncStore,\n\tStoreQueueStorage,\n\tStoreSyncStatePersistence,\n\tcreateSyncEngineChunkPort,\n\twireAuditPersistence,\n\twireBlobUpload,\n} from 'korajs/testing'\nimport type { TestServer } from './test-server'\n\n/**\n * Options for creating a TestDevice.\n */\nexport interface TestDeviceOptions {\n\t/** Unique device name (used for DB file naming) */\n\tname: string\n\t/** Schema definition */\n\tschema: SchemaDefinition\n\t/** Test server to connect to */\n\tserver: TestServer\n\t/** Transport factory — creates a linked client/server transport pair */\n\tcreateTransportPair: () => {\n\t\tclient: SyncTransport\n\t\tserverTransport: import('@korajs/server').ServerTransport\n\t}\n\t/** Optional directory for temp DB files */\n\ttmpDir: string\n\t/** Client handshake schema version. Defaults to `schema.version`. */\n\tsyncSchemaVersion?: number\n\t/** Transforms applied to inbound operations before local apply. */\n\toperationTransforms?: OperationTransform[]\n}\n\n/**\n * A virtual device in a test network.\n * Each device has its own Store (with real SQLite), SyncEngine, and MergeEngine.\n * Provides high-level methods for syncing, disconnecting, and inspecting state.\n */\nexport class TestDevice {\n\treadonly name: string\n\treadonly store: Store\n\treadonly emitter: KoraEventEmitter & { clear(): void }\n\t/** Content-addressed blob store for out-of-band blob bytes (chunks + full blobs). */\n\treadonly blobStore = new MemoryBlobStore()\n\n\tprivate readonly schema: SchemaDefinition\n\tprivate readonly server: TestServer\n\tprivate readonly mergeEngine: MergeEngine\n\tprivate readonly createTransportPair: TestDeviceOptions['createTransportPair']\n\tprivate readonly adapter: StorageAdapter\n\tprivate readonly dbPath: string\n\tprivate readonly syncSchemaVersion: number\n\tprivate readonly operationTransforms: OperationTransform[]\n\n\tprivate applyPipeline: ApplyPipeline | null = null\n\tprivate syncEngine: SyncEngine | null = null\n\tprivate blobChunkProvider: ChunkProvider | null = null\n\tprivate currentTransport: SyncTransport | null = null\n\tprivate unsubscribeSync: (() => void) | null = null\n\tprivate unsubscribeAudit: (() => void) | null = null\n\tprivate closing = false\n\n\tconstructor(options: TestDeviceOptions) {\n\t\tthis.name = options.name\n\t\tthis.schema = options.schema\n\t\tthis.server = options.server\n\t\tthis.createTransportPair = options.createTransportPair\n\t\tthis.dbPath = `${options.tmpDir}/test-device-${options.name}.db`\n\t\tthis.syncSchemaVersion = options.syncSchemaVersion ?? options.schema.version\n\t\tthis.operationTransforms = options.operationTransforms ?? []\n\n\t\tthis.emitter = new SimpleEventEmitter()\n\t\tthis.mergeEngine = new MergeEngine()\n\t\tthis.adapter = new BetterSqlite3Adapter(this.dbPath)\n\t\tthis.store = new Store({\n\t\t\tschema: options.schema,\n\t\t\tadapter: this.adapter,\n\t\t\temitter: this.emitter,\n\t\t})\n\t}\n\n\t/**\n\t * Open the store (must be called before sync or collection operations).\n\t */\n\tasync open(): Promise<void> {\n\t\tawait this.store.open()\n\t\tthis.applyPipeline = new ApplyPipeline({\n\t\t\tstore: this.store,\n\t\t\tmergeEngine: this.mergeEngine,\n\t\t\temitter: this.emitter,\n\t\t})\n\t\tthis.store.setLocalMutationHandler(this.applyPipeline)\n\t\t// Match production wiring (createApp): merge/constraint traces persist to\n\t\t// `_kora_audit_traces`. Without this, harness devices emit merge events\n\t\t// but the durable audit trail every real app has stays empty — a fidelity\n\t\t// gap that hid from tests until Studio's Merges view made it visible.\n\t\tthis.unsubscribeAudit = wireAuditPersistence(this.store, this.emitter)\n\t}\n\n\t/**\n\t * Connect to the test server and perform initial sync.\n\t * If already connected, flushes any pending operations.\n\t */\n\tasync sync(): Promise<void> {\n\t\tif (this.syncEngine && this.currentTransport?.isConnected()) {\n\t\t\t// Already connected — flush outbound ops, then allow inbound relay to settle\n\t\t\tawait this.waitForPendingOps()\n\t\t\tawait this.waitForSettled()\n\t\t\treturn\n\t\t}\n\n\t\t// Create a new transport pair and connect\n\t\tconst { client, serverTransport } = this.createTransportPair()\n\t\tthis.currentTransport = client\n\n\t\tconst conflictHandler: { fn?: () => void } = {}\n\t\tconst syncStore = new MergeAwareSyncStore(this.store, this.mergeEngine, this.emitter, {\n\t\t\tonMergeConflict: () => conflictHandler.fn?.(),\n\t\t})\n\n\t\tthis.syncEngine = new SyncEngine({\n\t\t\ttransport: client,\n\t\t\tstore: syncStore,\n\t\t\tqueueStorage: new StoreQueueStorage(this.adapter),\n\t\t\tsyncState: new StoreSyncStatePersistence(this.store),\n\t\t\tconfig: {\n\t\t\t\turl: 'ws://test-network',\n\t\t\t\tschemaVersion: this.syncSchemaVersion,\n\t\t\t\toperationTransforms:\n\t\t\t\t\tthis.operationTransforms.length > 0 ? this.operationTransforms : undefined,\n\t\t\t},\n\t\t\temitter: this.emitter,\n\t\t})\n\t\tconflictHandler.fn = () => this.syncEngine?.recordConflict()\n\n\t\t// Bind the blob chunk channel to this connection: serve chunks this device\n\t\t// holds, and prepare a provider to pull chunks it needs. Both ride the same\n\t\t// sync socket; the request/response handlers are disjoint by message type.\n\t\tconst chunkPort = createSyncEngineChunkPort(this.syncEngine)\n\t\tserveBlobChunks(chunkPort, this.blobStore)\n\t\tthis.blobChunkProvider = createRemoteChunkProvider(chunkPort)\n\t\t// Mirror createApp: auto-upload blob bytes to the server as ops sync, so\n\t\t// blobs stay available after this device disconnects.\n\t\twireBlobUpload(this.emitter, this.syncEngine, this.blobStore)\n\n\t\t// Wire local mutations to sync outbound queue\n\t\tconst engine = this.syncEngine\n\t\tthis.unsubscribeSync = this.emitter.on('operation:created', (event) => {\n\t\t\tif (!this.closing && this.syncEngine === engine && this.currentTransport?.isConnected()) {\n\t\t\t\t// Catch async errors from push racing with disconnect during teardown\n\t\t\t\tthis.syncEngine.pushOperation(event.operation).catch(() => {})\n\t\t\t}\n\t\t})\n\n\t\t// Register server-side connection\n\t\tthis.server.handleConnection(serverTransport)\n\n\t\t// Start sync engine (connects, handshakes, exchanges deltas)\n\t\tawait this.syncEngine.start()\n\n\t\t// Wait for sync messages to propagate (in-memory transport is synchronous\n\t\t// but some processing is async)\n\t\tawait this.waitForSettled()\n\t}\n\n\t/**\n\t * Disconnect from the test server.\n\t */\n\tasync disconnect(): Promise<void> {\n\t\tif (this.unsubscribeSync) {\n\t\t\tthis.unsubscribeSync()\n\t\t\tthis.unsubscribeSync = null\n\t\t}\n\t\tif (this.syncEngine) {\n\t\t\tawait this.syncEngine.stop()\n\t\t\tthis.syncEngine = null\n\t\t}\n\t\tthis.blobChunkProvider = null\n\t\tthis.currentTransport = null\n\t}\n\n\t/**\n\t * Reconnect to the test server after a disconnect.\n\t */\n\tasync reconnect(): Promise<void> {\n\t\tawait this.sync()\n\t}\n\n\t/**\n\t * Get a collection accessor for performing CRUD operations.\n\t */\n\tcollection(name: string): CollectionAccessor {\n\t\treturn this.store.collection(name)\n\t}\n\n\t/**\n\t * Get all records from a collection (convenience method).\n\t */\n\tasync getState(collectionName: string): Promise<Record<string, unknown>[]> {\n\t\tconst accessor = this.store.collection(collectionName)\n\t\treturn accessor.where({}).exec()\n\t}\n\n\t/**\n\t * Get the device's node ID.\n\t */\n\tgetNodeId(): string {\n\t\treturn this.store.getNodeId()\n\t}\n\n\t/** Exposes the sync engine for integration tests (e.g. doc channel, chaos). */\n\tgetSyncEngine(): SyncEngine | null {\n\t\treturn this.syncEngine\n\t}\n\n\t/**\n\t * Operations the server rejected for this device that have not been reconciled.\n\t * Empty when the device has never connected.\n\t */\n\tasync getRejectedOperations(): Promise<import('@korajs/sync').RejectedOperation[]> {\n\t\tif (!this.syncEngine) return []\n\t\treturn this.syncEngine.getRejectedOperations()\n\t}\n\n\t/**\n\t * Stage a blob's bytes into this device's blob store, splitting them into\n\t * content-addressed chunks this device can then serve to peers over the sync\n\t * connection. Returns the manifest (blob hash + ordered chunk hashes) needed\n\t * to pull the blob elsewhere.\n\t */\n\tasync stageBlob(bytes: Uint8Array, options?: { chunkSize?: number }): Promise<BlobManifest> {\n\t\tconst { manifest } = await prepareBlobForSend(bytes, this.blobStore, options)\n\t\treturn manifest\n\t}\n\n\t/**\n\t * Store a blob for transfer the way `app.blobs.put` does: stage chunks, store\n\t * the full blob, and store the manifest as its own content-addressed object.\n\t * The returned reference carries a `manifestHash`, so a peer can pull the bytes\n\t * knowing only the reference.\n\t */\n\tasync putBlob(\n\t\tbytes: Uint8Array,\n\t\toptions?: { chunkSize?: number; mimeType?: string; filename?: string },\n\t): Promise<{ ref: BlobRef; manifest: BlobManifest }> {\n\t\treturn putBlobForTransfer(this.blobStore, bytes, options)\n\t}\n\n\t/**\n\t * Pull a blob's bytes over the live connection knowing only its reference: the\n\t * manifest is resolved by `ref.manifestHash` first, then the chunks are fetched.\n\t */\n\tasync pullBlobByRef(ref: BlobRef): Promise<ReceiveBlobResult> {\n\t\tif (!this.blobChunkProvider) {\n\t\t\tthrow new Error('Cannot pull a blob before the device has connected (call sync() first)')\n\t\t}\n\t\tconst manifest = await resolveBlobManifest(this.blobChunkProvider, ref)\n\t\treturn receiveBlob(manifest, this.blobChunkProvider, {\n\t\t\tchunkStore: this.blobStore,\n\t\t\tblobStore: this.blobStore,\n\t\t})\n\t}\n\n\t/**\n\t * Pull a blob's bytes from peers over the live sync connection. Requests only\n\t * the chunks this device is missing, reassembles them, and verifies integrity\n\t * against the manifest's blob hash before storing the full blob locally.\n\t */\n\tasync pullBlob(manifest: BlobManifest): Promise<ReceiveBlobResult> {\n\t\tif (!this.blobChunkProvider) {\n\t\t\tthrow new Error('Cannot pull a blob before the device has connected (call sync() first)')\n\t\t}\n\t\treturn receiveBlob(manifest, this.blobChunkProvider, {\n\t\t\tchunkStore: this.blobStore,\n\t\t\tblobStore: this.blobStore,\n\t\t})\n\t}\n\n\t/** Read fully-assembled blob bytes from this device's store, or null if absent. */\n\tasync getBlobBytes(hash: string): Promise<Uint8Array | null> {\n\t\treturn this.blobStore.get(hash)\n\t}\n\n\t/**\n\t * Get the device's version vector.\n\t */\n\tgetVersionVector(): VersionVector {\n\t\treturn this.store.getVersionVector()\n\t}\n\n\t/**\n\t * Check if the device is currently connected to the server.\n\t */\n\tisConnected(): boolean {\n\t\treturn this.currentTransport?.isConnected() ?? false\n\t}\n\n\t/**\n\t * Close the device, releasing all resources.\n\t */\n\tasync close(): Promise<void> {\n\t\tthis.closing = true\n\t\tif (this.unsubscribeAudit) {\n\t\t\tthis.unsubscribeAudit()\n\t\t\tthis.unsubscribeAudit = null\n\t\t}\n\t\tawait this.disconnect()\n\t\tawait this.store.close()\n\t\tthis.emitter.clear()\n\t}\n\n\t/**\n\t * Wait for in-flight sync operations to settle.\n\t * In-memory transports are near-synchronous, but apply/relay work is async.\n\t */\n\tprivate async waitForSettled(): Promise<void> {\n\t\tfor (let i = 0; i < 15; i++) {\n\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 20))\n\t\t}\n\t}\n\n\t/**\n\t * Wait for all pending outbound operations to be acknowledged.\n\t */\n\tprivate async waitForPendingOps(): Promise<void> {\n\t\tif (!this.syncEngine) return\n\t\tconst maxWait = 2000\n\t\tconst start = Date.now()\n\t\twhile (Date.now() - start < maxWait) {\n\t\t\tconst status = this.syncEngine.getStatus()\n\t\t\tif (status.pendingOperations === 0) return\n\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 10))\n\t\t}\n\t}\n}\n","import type { BlobRef, Operation, SchemaDefinition } from '@korajs/core'\nimport { MemoryServerStore } from '@korajs/server'\nimport { KoraSyncServer } from '@korajs/server'\nimport type { OperationValidator, ServerTransport } from '@korajs/server'\nimport { type ContentAddressedBlobStore, createMemoryServerBlobStore } from '@korajs/store'\n\n/**\n * In-memory test server wrapping KoraSyncServer with MemoryServerStore.\n * Handles client connections via memory transports.\n */\nexport interface TestServerOptions {\n\t/** Handshake schema version advertised by the server. Defaults to `schema.version`. */\n\tschemaVersion?: number\n\t/** Inclusive client schema versions accepted at handshake. */\n\tsupportedSchemaVersions?: { min: number; max: number }\n\t/** Enable central blob storage: the server persists and serves uploaded blob bytes. */\n\tblobStorage?: boolean\n\t/** Adjudicate untrusted client operations before materialization. */\n\tvalidateOperation?: OperationValidator\n}\n\nexport class TestServer {\n\treadonly store: MemoryServerStore\n\t/** The server's central blob store, present when `blobStorage` was enabled. */\n\treadonly blobStore: ContentAddressedBlobStore | null\n\tprivate readonly syncServer: KoraSyncServer\n\n\tconstructor(schema: SchemaDefinition, options?: TestServerOptions) {\n\t\tthis.store = new MemoryServerStore()\n\t\tconst schemaVersion = options?.schemaVersion ?? schema.version\n\t\tconst blob = options?.blobStorage ? createMemoryServerBlobStore() : null\n\t\tthis.blobStore = blob?.store ?? null\n\t\tthis.syncServer = new KoraSyncServer({\n\t\t\tstore: this.store,\n\t\t\tschemaVersion,\n\t\t\tsupportedSchemaVersions: options?.supportedSchemaVersions ?? {\n\t\t\t\tmin: schemaVersion,\n\t\t\t\tmax: schemaVersion,\n\t\t\t},\n\t\t\t...(blob ? blob.callbacks : {}),\n\t\t\t...(options?.validateOperation ? { validateOperation: options.validateOperation } : {}),\n\t\t})\n\t\tvoid this.store.setSchema(schema)\n\t}\n\n\t/**\n\t * Register a client connection transport with the server.\n\t * Returns the session ID assigned by the server.\n\t */\n\thandleConnection(transport: ServerTransport): string {\n\t\treturn this.syncServer.handleConnection(transport)\n\t}\n\n\t/**\n\t * Get all operations stored on the server.\n\t */\n\tgetAllOperations(): Operation[] {\n\t\treturn this.store.getAllOperations()\n\t}\n\n\t/**\n\t * Get the number of connected clients.\n\t */\n\tgetConnectionCount(): number {\n\t\treturn this.syncServer.getConnectionCount()\n\t}\n\n\t/**\n\t * Retransmit relay batches connected clients have not acknowledged. Lets a test\n\t * deterministically trigger the redelivery that the server also runs on a timer,\n\t * without depending on wall-clock time. `staleMs` of 0 resends every pending relay.\n\t */\n\tretransmitPendingRelays(staleMs = 0): void {\n\t\tthis.syncServer.retransmitPendingRelays(staleMs)\n\t}\n\n\t/** Every blob reference still reachable from live records on the server. */\n\tgetLiveBlobRefs(): Promise<BlobRef[]> {\n\t\treturn this.syncServer.getLiveBlobRefs()\n\t}\n\n\t/**\n\t * Shut down the server and close all sessions.\n\t */\n\tasync close(): Promise<void> {\n\t\tawait this.syncServer.stop()\n\t\tawait this.store.close()\n\t}\n}\n","import type { SchemaDefinition } from '@korajs/core'\nimport type { TestDevice } from './test-device'\n\n/**\n * Result of a convergence check.\n */\nexport interface ConvergenceResult {\n\t/** Whether all devices have converged to the same state */\n\tconverged: boolean\n\t/** Per-collection comparison details (only populated on failure) */\n\tdifferences: CollectionDifference[]\n}\n\n/**\n * Describes a difference in a collection between devices.\n */\nexport interface CollectionDifference {\n\tcollection: string\n\tdeviceA: string\n\tdeviceB: string\n\t/** Records present in deviceA but not deviceB */\n\tmissingInB: string[]\n\t/** Records present in deviceB but not deviceA */\n\tmissingInA: string[]\n\t/** Records present in both but with different values */\n\tfieldDifferences: FieldDifference[]\n}\n\n/**\n * A specific field-level difference between two devices.\n */\nexport interface FieldDifference {\n\trecordId: string\n\tfield: string\n\tvalueInA: unknown\n\tvalueInB: unknown\n}\n\n/**\n * Assert that all devices have converged to identical collection states.\n *\n * Compares every collection across all device pairs. Throws an error\n * with detailed diagnostics if any differences are found.\n *\n * @param devices - The devices to check for convergence\n * @param schema - The schema (used to enumerate collections)\n *\n * @example\n * ```typescript\n * await deviceA.sync()\n * await deviceB.sync()\n * await expectConverged([deviceA, deviceB], schema)\n * ```\n */\nexport async function expectConverged(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n): Promise<void> {\n\tif (devices.length < 2) return\n\n\tconst result = await checkConvergence(devices, schema)\n\tif (!result.converged) {\n\t\tconst details = result.differences\n\t\t\t.map((d) => {\n\t\t\t\tconst parts: string[] = [\n\t\t\t\t\t` Collection \"${d.collection}\" differs between ${d.deviceA} and ${d.deviceB}:`,\n\t\t\t\t]\n\t\t\t\tif (d.missingInB.length > 0) {\n\t\t\t\t\tparts.push(` Missing in ${d.deviceB}: ${d.missingInB.join(', ')}`)\n\t\t\t\t}\n\t\t\t\tif (d.missingInA.length > 0) {\n\t\t\t\t\tparts.push(` Missing in ${d.deviceA}: ${d.missingInA.join(', ')}`)\n\t\t\t\t}\n\t\t\t\tfor (const fd of d.fieldDifferences) {\n\t\t\t\t\tparts.push(\n\t\t\t\t\t\t` Record \"${fd.recordId}\" field \"${fd.field}\": ${JSON.stringify(fd.valueInA)} vs ${JSON.stringify(fd.valueInB)}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn parts.join('\\n')\n\t\t\t})\n\t\t\t.join('\\n')\n\n\t\tthrow new Error(`Devices have not converged:\\n${details}`)\n\t}\n}\n\n/**\n * Poll until all devices converge or timeout. Use after sync in integration tests\n * where relay/apply work may still be in flight under parallel CI load.\n */\nexport async function expectConvergedEventually(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n\toptions?: { timeoutMs?: number; intervalMs?: number },\n): Promise<void> {\n\tif (devices.length < 2) return\n\n\tconst timeoutMs = options?.timeoutMs ?? 5000\n\tconst intervalMs = options?.intervalMs ?? 25\n\tconst deadline = Date.now() + timeoutMs\n\n\twhile (Date.now() < deadline) {\n\t\tconst result = await checkConvergence(devices, schema)\n\t\tif (result.converged) {\n\t\t\treturn\n\t\t}\n\t\tawait new Promise<void>((resolve) => setTimeout(resolve, intervalMs))\n\t}\n\n\tawait expectConverged(devices, schema)\n}\n\n/**\n * Check whether all devices have converged without throwing.\n *\n * @param devices - The devices to check\n * @param schema - The schema (for collection enumeration)\n * @returns Convergence result with details\n */\nexport async function checkConvergence(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n): Promise<ConvergenceResult> {\n\tconst differences: CollectionDifference[] = []\n\tconst collectionNames = Object.keys(schema.collections)\n\n\tfor (let i = 0; i < devices.length - 1; i++) {\n\t\tfor (let j = i + 1; j < devices.length; j++) {\n\t\t\tconst deviceA = devices[i] as TestDevice\n\t\t\tconst deviceB = devices[j] as TestDevice\n\n\t\t\tfor (const collection of collectionNames) {\n\t\t\t\tconst stateA = await deviceA.getState(collection)\n\t\t\t\tconst stateB = await deviceB.getState(collection)\n\n\t\t\t\tconst diff = compareCollectionStates(collection, deviceA.name, deviceB.name, stateA, stateB)\n\n\t\t\t\tif (diff) {\n\t\t\t\t\tdifferences.push(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tconverged: differences.length === 0,\n\t\tdifferences,\n\t}\n}\n\n/**\n * Compare two collection states and return differences if any.\n */\nfunction compareCollectionStates(\n\tcollection: string,\n\tnameA: string,\n\tnameB: string,\n\tstateA: Record<string, unknown>[],\n\tstateB: Record<string, unknown>[],\n): CollectionDifference | null {\n\tconst mapA = new Map(stateA.map((r) => [r.id as string, r]))\n\tconst mapB = new Map(stateB.map((r) => [r.id as string, r]))\n\n\tconst missingInB: string[] = []\n\tconst missingInA: string[] = []\n\tconst fieldDifferences: FieldDifference[] = []\n\n\t// Check records in A\n\tfor (const [id, recordA] of mapA) {\n\t\tconst recordB = mapB.get(id)\n\t\tif (!recordB) {\n\t\t\tmissingInB.push(id)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Compare fields (skip internal fields like _created_at, _updated_at)\n\t\tconst allFields = new Set([\n\t\t\t...Object.keys(recordA).filter((k) => !k.startsWith('_')),\n\t\t\t...Object.keys(recordB).filter((k) => !k.startsWith('_')),\n\t\t])\n\n\t\tfor (const field of allFields) {\n\t\t\tif (field === 'id') continue\n\t\t\tconst valA = recordA[field]\n\t\t\tconst valB = recordB[field]\n\t\t\tif (!deepEqual(valA, valB)) {\n\t\t\t\tfieldDifferences.push({\n\t\t\t\t\trecordId: id,\n\t\t\t\t\tfield,\n\t\t\t\t\tvalueInA: valA,\n\t\t\t\t\tvalueInB: valB,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check records only in B\n\tfor (const id of mapB.keys()) {\n\t\tif (!mapA.has(id)) {\n\t\t\tmissingInA.push(id)\n\t\t}\n\t}\n\n\tif (missingInB.length === 0 && missingInA.length === 0 && fieldDifferences.length === 0) {\n\t\treturn null\n\t}\n\n\treturn {\n\t\tcollection,\n\t\tdeviceA: nameA,\n\t\tdeviceB: nameB,\n\t\tmissingInB,\n\t\tmissingInA,\n\t\tfieldDifferences,\n\t}\n}\n\n/**\n * Deep equality check for comparing field values.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n\tif (a === b) return true\n\tif (a === null || b === null) return false\n\tif (a === undefined || b === undefined) return false\n\tif (typeof a !== typeof b) return false\n\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\tif (a.length !== b.length) return false\n\t\treturn a.every((val, i) => deepEqual(val, b[i]))\n\t}\n\n\tif (typeof a === 'object' && typeof b === 'object') {\n\t\tconst keysA = Object.keys(a as Record<string, unknown>)\n\t\tconst keysB = Object.keys(b as Record<string, unknown>)\n\t\tif (keysA.length !== keysB.length) return false\n\t\treturn keysA.every((key) =>\n\t\t\tdeepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n\t\t)\n\t}\n\n\treturn false\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { MessageSerializer, SyncMessage, SyncTransport, TransportOptions } from '@korajs/sync'\nimport { ProtobufMessageSerializer } from '@korajs/sync'\n\n/**\n * A linked client/server transport pair, as consumed by {@link TestDevice}.\n */\nexport interface TransportPair {\n\tclient: SyncTransport\n\tserverTransport: ServerTransport\n}\n\n/**\n * Message types the protobuf serializer models. Ephemeral messages\n * (awareness-update, yjs-doc-update) are JSON-only in the protocol, so they are\n * deep-copied instead of forced through the protobuf envelope.\n */\nconst PROTOBUF_WIRE_TYPES: ReadonlySet<SyncMessage['type']> = new Set([\n\t'handshake',\n\t'handshake-response',\n\t'operation-batch',\n\t'acknowledgment',\n\t'error',\n])\n\n/**\n * Round-trips a message through the real protobuf wire codec: object → protobuf\n * bytes → object. This is what makes a transport pair exercise the true wire\n * format rather than passing live object references between the two ends.\n */\nfunction roundTripThroughProtobuf(\n\tmessage: SyncMessage,\n\tserializer: MessageSerializer,\n): SyncMessage {\n\tif (!PROTOBUF_WIRE_TYPES.has(message.type)) {\n\t\t// Not part of the protobuf schema — still copy so no live reference leaks\n\t\t// across the \"wire\".\n\t\treturn structuredClone(message)\n\t}\n\n\tconst encoded = serializer.encode(message)\n\tif (!(encoded instanceof Uint8Array)) {\n\t\tthrow new Error('Expected the protobuf serializer to encode to bytes')\n\t}\n\treturn serializer.decode(encoded)\n}\n\n/**\n * Wraps a client transport so every outbound message crosses the protobuf wire.\n */\nclass ProtobufWireClientTransport implements SyncTransport {\n\tconstructor(\n\t\tprivate readonly inner: SyncTransport,\n\t\tprivate readonly serializer: MessageSerializer,\n\t) {}\n\n\tconnect(url: string, options?: TransportOptions): Promise<void> {\n\t\treturn this.inner.connect(url, options)\n\t}\n\n\tdisconnect(): Promise<void> {\n\t\treturn this.inner.disconnect()\n\t}\n\n\tsend(message: SyncMessage): void {\n\t\tthis.inner.send(roundTripThroughProtobuf(message, this.serializer))\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n}\n\n/**\n * Wraps a server transport so every outbound message crosses the protobuf wire.\n */\nclass ProtobufWireServerTransport implements ServerTransport {\n\tconstructor(\n\t\tprivate readonly inner: ServerTransport,\n\t\tprivate readonly serializer: MessageSerializer,\n\t) {}\n\n\tsend(message: SyncMessage): void {\n\t\tthis.inner.send(roundTripThroughProtobuf(message, this.serializer))\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (code: number, reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.inner.close(code, reason)\n\t}\n}\n\n/**\n * Wraps a transport pair so that every sync message exchanged between the two\n * ends is encoded to protobuf bytes and decoded back — a faithful stand-in for\n * a real network hop that uses the protobuf wire format.\n *\n * Both directions are wrapped, so operations, handshakes, batches, and acks all\n * travel as bytes rather than shared object references.\n *\n * @param pair - The underlying (in-memory) transport pair to wrap\n * @returns A transport pair that routes messages through the protobuf codec\n */\nexport function wrapTransportPairWithProtobufWire(pair: TransportPair): TransportPair {\n\tconst serializer = new ProtobufMessageSerializer()\n\treturn {\n\t\tclient: new ProtobufWireClientTransport(pair.client, serializer),\n\t\tserverTransport: new ProtobufWireServerTransport(pair.serverTransport, serializer),\n\t}\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { SyncMessage } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\n\n/**\n * Wraps a server transport so its handshake responses advertise a caller-chosen\n * wall-clock time, independent of the process `Date.now()`.\n *\n * The sync engine measures clock skew as `serverTime - Date.now()` at handshake.\n * To simulate a device whose clock is fast while the server's clock is correct —\n * both running in one test process — the client mocks `Date.now()` and this\n * wrapper injects the server's true time into the handshake response, so the\n * skew the engine sees is real.\n */\nclass ServerClockServerTransport implements ServerTransport {\n\tconstructor(\n\t\tprivate readonly inner: ServerTransport,\n\t\tprivate readonly serverNow: () => number,\n\t) {}\n\n\tsend(message: SyncMessage): void {\n\t\tif (message.type === 'handshake-response') {\n\t\t\tthis.inner.send({ ...message, serverTime: this.serverNow() })\n\t\t\treturn\n\t\t}\n\t\tthis.inner.send(message)\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (code: number, reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.inner.close(code, reason)\n\t}\n}\n\n/**\n * Wraps a transport pair so the server side reports `serverNow()` as its\n * handshake wall-clock time.\n *\n * @param pair - The underlying transport pair\n * @param serverNow - Returns the server's true current time (ms since epoch)\n * @returns A transport pair whose handshake responses carry the injected time\n */\nexport function wrapTransportPairWithServerClock(\n\tpair: TransportPair,\n\tserverNow: () => number,\n): TransportPair {\n\treturn {\n\t\tclient: pair.client,\n\t\tserverTransport: new ServerClockServerTransport(pair.serverTransport, serverNow),\n\t}\n}\n"],"mappings":"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","names":["import_internal","import_sync","import_server","import_store","import_sync","import_sync"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -62,6 +62,12 @@ declare class TestServer {
|
|
|
62
62
|
* Get the number of connected clients.
|
|
63
63
|
*/
|
|
64
64
|
getConnectionCount(): number;
|
|
65
|
+
/**
|
|
66
|
+
* Retransmit relay batches connected clients have not acknowledged. Lets a test
|
|
67
|
+
* deterministically trigger the redelivery that the server also runs on a timer,
|
|
68
|
+
* without depending on wall-clock time. `staleMs` of 0 resends every pending relay.
|
|
69
|
+
*/
|
|
70
|
+
retransmitPendingRelays(staleMs?: number): void;
|
|
65
71
|
/** Every blob reference still reachable from live records on the server. */
|
|
66
72
|
getLiveBlobRefs(): Promise<BlobRef[]>;
|
|
67
73
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,12 @@ declare class TestServer {
|
|
|
62
62
|
* Get the number of connected clients.
|
|
63
63
|
*/
|
|
64
64
|
getConnectionCount(): number;
|
|
65
|
+
/**
|
|
66
|
+
* Retransmit relay batches connected clients have not acknowledged. Lets a test
|
|
67
|
+
* deterministically trigger the redelivery that the server also runs on a timer,
|
|
68
|
+
* without depending on wall-clock time. `staleMs` of 0 resends every pending relay.
|
|
69
|
+
*/
|
|
70
|
+
retransmitPendingRelays(staleMs?: number): void;
|
|
65
71
|
/** Every blob reference still reachable from live records on the server. */
|
|
66
72
|
getLiveBlobRefs(): Promise<BlobRef[]>;
|
|
67
73
|
/**
|
package/dist/index.js
CHANGED
|
@@ -321,6 +321,14 @@ var TestServer = class {
|
|
|
321
321
|
getConnectionCount() {
|
|
322
322
|
return this.syncServer.getConnectionCount();
|
|
323
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Retransmit relay batches connected clients have not acknowledged. Lets a test
|
|
326
|
+
* deterministically trigger the redelivery that the server also runs on a timer,
|
|
327
|
+
* without depending on wall-clock time. `staleMs` of 0 resends every pending relay.
|
|
328
|
+
*/
|
|
329
|
+
retransmitPendingRelays(staleMs = 0) {
|
|
330
|
+
this.syncServer.retransmitPendingRelays(staleMs);
|
|
331
|
+
}
|
|
324
332
|
/** Every blob reference still reachable from live records on the server. */
|
|
325
333
|
getLiveBlobRefs() {
|
|
326
334
|
return this.syncServer.getLiveBlobRefs();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/test-network.ts","../src/test-device.ts","../src/test-server.ts","../src/assertions.ts","../src/protobuf-wire-transport.ts","../src/server-clock-transport.ts","../src/index.ts"],"sourcesContent":["import { mkdtempSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport type { OperationTransform, SchemaDefinition } from '@korajs/core'\nimport { createServerTransportPair } from '@korajs/server/internal'\nimport type { ChaosConfig } from '@korajs/sync'\nimport { ChaosTransport } from '@korajs/sync'\nimport type { SyncTransport } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\nimport { TestDevice } from './test-device'\nimport { TestServer, type TestServerOptions } from './test-server'\n\n/**\n * Options for creating a test network.\n */\nexport interface TestNetworkOptions {\n\t/** Number of devices to create. Defaults to 2. */\n\tdevices?: number\n\t/** Custom device names. If not provided, uses 'device-0', 'device-1', etc. */\n\tdeviceNames?: string[]\n\t/** Optional chaos transport settings applied to each device link. */\n\tchaos?: ChaosConfig\n\t/**\n\t * Optional wrapper applied to each device's transport pair after the base\n\t * (in-memory, optionally chaos-wrapped) pair is created. Use it to route\n\t * messages through the protobuf wire codec\n\t * ({@link wrapTransportPairWithProtobufWire}) or to inject a controllable\n\t * server clock. Called once per device connection.\n\t */\n\twrapTransport?: (pair: TransportPair) => TransportPair\n\t/** Enable central blob storage on the server (persist + serve uploaded blob bytes). */\n\tblobStorage?: boolean\n\t/** Adjudicate untrusted client operations on the server before materialization. */\n\tvalidateOperation?: import('@korajs/server').OperationValidator\n}\n\n/**\n * A test network with a server and multiple devices.\n */\nexport interface TestNetwork {\n\t/** The test server */\n\tserver: TestServer\n\t/** All devices in the network */\n\tdevices: TestDevice[]\n\t/** Temporary directory for DB files */\n\ttmpDir: string\n\t/** Close all devices and the server, clean up temp files */\n\tclose(): Promise<void>\n}\n\n/**\n * Create a test network with a server and multiple virtual devices.\n *\n * Each device has its own local SQLite store and SyncEngine. Devices\n * communicate with the server via in-memory transports.\n *\n * @param schema - The schema all devices share\n * @param options - Network configuration\n * @returns A test network with server and devices ready for use\n *\n * @example\n * ```typescript\n * const network = await createTestNetwork(schema, { devices: 2 })\n * const [deviceA, deviceB] = network.devices\n *\n * await deviceA.collection('todos').insert({ title: 'Hello' })\n * await deviceA.sync()\n * await deviceB.sync()\n *\n * const todos = await deviceB.getState('todos')\n * expect(todos).toHaveLength(1)\n *\n * await network.close()\n * ```\n */\nexport async function createTestNetwork(\n\tschema: SchemaDefinition,\n\toptions?: TestNetworkOptions,\n): Promise<TestNetwork> {\n\tconst deviceCount = options?.devices ?? 2\n\tconst deviceNames =\n\t\toptions?.deviceNames ?? Array.from({ length: deviceCount }, (_, i) => `device-${i}`)\n\n\t// Create temp directory for DB files\n\tconst tmpDir = mkdtempSync(join(tmpdir(), 'kora-test-'))\n\n\t// Create server\n\tconst server = new TestServer(schema, {\n\t\tblobStorage: options?.blobStorage,\n\t\tvalidateOperation: options?.validateOperation,\n\t})\n\n\t// Create devices\n\tconst devices: TestDevice[] = []\n\tfor (const name of deviceNames) {\n\t\tconst device = new TestDevice({\n\t\t\tname,\n\t\t\tschema,\n\t\t\tserver,\n\t\t\tcreateTransportPair: () => {\n\t\t\t\tconst pair = createServerTransportPair()\n\t\t\t\tconst client = pair.client as unknown as SyncTransport\n\t\t\t\tconst chaos = options?.chaos\n\t\t\t\tconst base: TransportPair = {\n\t\t\t\t\tclient: chaos ? new ChaosTransport(client, chaos) : client,\n\t\t\t\t\tserverTransport: pair.server,\n\t\t\t\t}\n\t\t\t\treturn options?.wrapTransport ? options.wrapTransport(base) : base\n\t\t\t},\n\t\t\ttmpDir,\n\t\t})\n\t\tawait device.open()\n\t\tdevices.push(device)\n\t}\n\n\treturn {\n\t\tserver,\n\t\tdevices,\n\t\ttmpDir,\n\t\tasync close(): Promise<void> {\n\t\t\tfor (const device of devices) {\n\t\t\t\tawait device.close()\n\t\t\t}\n\t\t\tawait server.close()\n\t\t\t// Clean up temp DB files\n\t\t\ttry {\n\t\t\t\tconst { rmSync } = await import('node:fs')\n\t\t\t\trmSync(tmpDir, { recursive: true, force: true })\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t},\n\t}\n}\n\n/**\n * Per-device configuration when devices use different local schemas or sync settings.\n */\nexport interface MixedTestDeviceConfig {\n\tname: string\n\tschema: SchemaDefinition\n\tsyncSchemaVersion?: number\n\toperationTransforms?: OperationTransform[]\n}\n\n/**\n * Create a test network where devices may use different schema versions and transforms.\n * The server uses `serverSchema` for materialization and handshake bounds.\n */\nexport async function createMixedTestNetwork(\n\tserverSchema: SchemaDefinition,\n\tserverOptions: TestServerOptions,\n\tdeviceConfigs: MixedTestDeviceConfig[],\n): Promise<TestNetwork> {\n\tconst tmpDir = mkdtempSync(join(tmpdir(), 'kora-test-'))\n\tconst server = new TestServer(serverSchema, serverOptions)\n\n\tconst devices: TestDevice[] = []\n\tfor (const config of deviceConfigs) {\n\t\tconst device = new TestDevice({\n\t\t\tname: config.name,\n\t\t\tschema: config.schema,\n\t\t\tserver,\n\t\t\tsyncSchemaVersion: config.syncSchemaVersion,\n\t\t\toperationTransforms: config.operationTransforms,\n\t\t\tcreateTransportPair: () => {\n\t\t\t\tconst pair = createServerTransportPair()\n\t\t\t\treturn {\n\t\t\t\t\tclient: pair.client as unknown as SyncTransport,\n\t\t\t\t\tserverTransport: pair.server,\n\t\t\t\t}\n\t\t\t},\n\t\t\ttmpDir,\n\t\t})\n\t\tawait device.open()\n\t\tdevices.push(device)\n\t}\n\n\treturn {\n\t\tserver,\n\t\tdevices,\n\t\ttmpDir,\n\t\tasync close(): Promise<void> {\n\t\t\tfor (const device of devices) {\n\t\t\t\tawait device.close()\n\t\t\t}\n\t\t\tawait server.close()\n\t\t\ttry {\n\t\t\t\tconst { rmSync } = await import('node:fs')\n\t\t\t\trmSync(tmpDir, { recursive: true, force: true })\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t},\n\t}\n}\n","import type { OperationTransform, SchemaDefinition } from '@korajs/core'\nimport type { VersionVector } from '@korajs/core'\nimport type { KoraEventEmitter } from '@korajs/core'\nimport type { BlobRef } from '@korajs/core'\nimport { SimpleEventEmitter } from '@korajs/core/internal'\nimport { MergeEngine } from '@korajs/merge'\nimport {\n\tMemoryBlobStore,\n\tStore,\n\tcreateRemoteChunkProvider,\n\tprepareBlobForSend,\n\tputBlobForTransfer,\n\treceiveBlob,\n\tresolveBlobManifest,\n\tserveBlobChunks,\n} from '@korajs/store'\nimport type {\n\tBlobManifest,\n\tChunkProvider,\n\tCollectionAccessor,\n\tReceiveBlobResult,\n\tStorageAdapter,\n} from '@korajs/store'\nimport { BetterSqlite3Adapter } from '@korajs/store/better-sqlite3'\nimport { SyncEngine } from '@korajs/sync'\nimport type { SyncTransport } from '@korajs/sync'\nimport {\n\tApplyPipeline,\n\tMergeAwareSyncStore,\n\tStoreQueueStorage,\n\tStoreSyncStatePersistence,\n\tcreateSyncEngineChunkPort,\n\twireAuditPersistence,\n\twireBlobUpload,\n} from 'korajs/testing'\nimport type { TestServer } from './test-server'\n\n/**\n * Options for creating a TestDevice.\n */\nexport interface TestDeviceOptions {\n\t/** Unique device name (used for DB file naming) */\n\tname: string\n\t/** Schema definition */\n\tschema: SchemaDefinition\n\t/** Test server to connect to */\n\tserver: TestServer\n\t/** Transport factory — creates a linked client/server transport pair */\n\tcreateTransportPair: () => {\n\t\tclient: SyncTransport\n\t\tserverTransport: import('@korajs/server').ServerTransport\n\t}\n\t/** Optional directory for temp DB files */\n\ttmpDir: string\n\t/** Client handshake schema version. Defaults to `schema.version`. */\n\tsyncSchemaVersion?: number\n\t/** Transforms applied to inbound operations before local apply. */\n\toperationTransforms?: OperationTransform[]\n}\n\n/**\n * A virtual device in a test network.\n * Each device has its own Store (with real SQLite), SyncEngine, and MergeEngine.\n * Provides high-level methods for syncing, disconnecting, and inspecting state.\n */\nexport class TestDevice {\n\treadonly name: string\n\treadonly store: Store\n\treadonly emitter: KoraEventEmitter & { clear(): void }\n\t/** Content-addressed blob store for out-of-band blob bytes (chunks + full blobs). */\n\treadonly blobStore = new MemoryBlobStore()\n\n\tprivate readonly schema: SchemaDefinition\n\tprivate readonly server: TestServer\n\tprivate readonly mergeEngine: MergeEngine\n\tprivate readonly createTransportPair: TestDeviceOptions['createTransportPair']\n\tprivate readonly adapter: StorageAdapter\n\tprivate readonly dbPath: string\n\tprivate readonly syncSchemaVersion: number\n\tprivate readonly operationTransforms: OperationTransform[]\n\n\tprivate applyPipeline: ApplyPipeline | null = null\n\tprivate syncEngine: SyncEngine | null = null\n\tprivate blobChunkProvider: ChunkProvider | null = null\n\tprivate currentTransport: SyncTransport | null = null\n\tprivate unsubscribeSync: (() => void) | null = null\n\tprivate unsubscribeAudit: (() => void) | null = null\n\tprivate closing = false\n\n\tconstructor(options: TestDeviceOptions) {\n\t\tthis.name = options.name\n\t\tthis.schema = options.schema\n\t\tthis.server = options.server\n\t\tthis.createTransportPair = options.createTransportPair\n\t\tthis.dbPath = `${options.tmpDir}/test-device-${options.name}.db`\n\t\tthis.syncSchemaVersion = options.syncSchemaVersion ?? options.schema.version\n\t\tthis.operationTransforms = options.operationTransforms ?? []\n\n\t\tthis.emitter = new SimpleEventEmitter()\n\t\tthis.mergeEngine = new MergeEngine()\n\t\tthis.adapter = new BetterSqlite3Adapter(this.dbPath)\n\t\tthis.store = new Store({\n\t\t\tschema: options.schema,\n\t\t\tadapter: this.adapter,\n\t\t\temitter: this.emitter,\n\t\t})\n\t}\n\n\t/**\n\t * Open the store (must be called before sync or collection operations).\n\t */\n\tasync open(): Promise<void> {\n\t\tawait this.store.open()\n\t\tthis.applyPipeline = new ApplyPipeline({\n\t\t\tstore: this.store,\n\t\t\tmergeEngine: this.mergeEngine,\n\t\t\temitter: this.emitter,\n\t\t})\n\t\tthis.store.setLocalMutationHandler(this.applyPipeline)\n\t\t// Match production wiring (createApp): merge/constraint traces persist to\n\t\t// `_kora_audit_traces`. Without this, harness devices emit merge events\n\t\t// but the durable audit trail every real app has stays empty — a fidelity\n\t\t// gap that hid from tests until Studio's Merges view made it visible.\n\t\tthis.unsubscribeAudit = wireAuditPersistence(this.store, this.emitter)\n\t}\n\n\t/**\n\t * Connect to the test server and perform initial sync.\n\t * If already connected, flushes any pending operations.\n\t */\n\tasync sync(): Promise<void> {\n\t\tif (this.syncEngine && this.currentTransport?.isConnected()) {\n\t\t\t// Already connected — flush outbound ops, then allow inbound relay to settle\n\t\t\tawait this.waitForPendingOps()\n\t\t\tawait this.waitForSettled()\n\t\t\treturn\n\t\t}\n\n\t\t// Create a new transport pair and connect\n\t\tconst { client, serverTransport } = this.createTransportPair()\n\t\tthis.currentTransport = client\n\n\t\tconst conflictHandler: { fn?: () => void } = {}\n\t\tconst syncStore = new MergeAwareSyncStore(this.store, this.mergeEngine, this.emitter, {\n\t\t\tonMergeConflict: () => conflictHandler.fn?.(),\n\t\t})\n\n\t\tthis.syncEngine = new SyncEngine({\n\t\t\ttransport: client,\n\t\t\tstore: syncStore,\n\t\t\tqueueStorage: new StoreQueueStorage(this.adapter),\n\t\t\tsyncState: new StoreSyncStatePersistence(this.store),\n\t\t\tconfig: {\n\t\t\t\turl: 'ws://test-network',\n\t\t\t\tschemaVersion: this.syncSchemaVersion,\n\t\t\t\toperationTransforms:\n\t\t\t\t\tthis.operationTransforms.length > 0 ? this.operationTransforms : undefined,\n\t\t\t},\n\t\t\temitter: this.emitter,\n\t\t})\n\t\tconflictHandler.fn = () => this.syncEngine?.recordConflict()\n\n\t\t// Bind the blob chunk channel to this connection: serve chunks this device\n\t\t// holds, and prepare a provider to pull chunks it needs. Both ride the same\n\t\t// sync socket; the request/response handlers are disjoint by message type.\n\t\tconst chunkPort = createSyncEngineChunkPort(this.syncEngine)\n\t\tserveBlobChunks(chunkPort, this.blobStore)\n\t\tthis.blobChunkProvider = createRemoteChunkProvider(chunkPort)\n\t\t// Mirror createApp: auto-upload blob bytes to the server as ops sync, so\n\t\t// blobs stay available after this device disconnects.\n\t\twireBlobUpload(this.emitter, this.syncEngine, this.blobStore)\n\n\t\t// Wire local mutations to sync outbound queue\n\t\tconst engine = this.syncEngine\n\t\tthis.unsubscribeSync = this.emitter.on('operation:created', (event) => {\n\t\t\tif (!this.closing && this.syncEngine === engine && this.currentTransport?.isConnected()) {\n\t\t\t\t// Catch async errors from push racing with disconnect during teardown\n\t\t\t\tthis.syncEngine.pushOperation(event.operation).catch(() => {})\n\t\t\t}\n\t\t})\n\n\t\t// Register server-side connection\n\t\tthis.server.handleConnection(serverTransport)\n\n\t\t// Start sync engine (connects, handshakes, exchanges deltas)\n\t\tawait this.syncEngine.start()\n\n\t\t// Wait for sync messages to propagate (in-memory transport is synchronous\n\t\t// but some processing is async)\n\t\tawait this.waitForSettled()\n\t}\n\n\t/**\n\t * Disconnect from the test server.\n\t */\n\tasync disconnect(): Promise<void> {\n\t\tif (this.unsubscribeSync) {\n\t\t\tthis.unsubscribeSync()\n\t\t\tthis.unsubscribeSync = null\n\t\t}\n\t\tif (this.syncEngine) {\n\t\t\tawait this.syncEngine.stop()\n\t\t\tthis.syncEngine = null\n\t\t}\n\t\tthis.blobChunkProvider = null\n\t\tthis.currentTransport = null\n\t}\n\n\t/**\n\t * Reconnect to the test server after a disconnect.\n\t */\n\tasync reconnect(): Promise<void> {\n\t\tawait this.sync()\n\t}\n\n\t/**\n\t * Get a collection accessor for performing CRUD operations.\n\t */\n\tcollection(name: string): CollectionAccessor {\n\t\treturn this.store.collection(name)\n\t}\n\n\t/**\n\t * Get all records from a collection (convenience method).\n\t */\n\tasync getState(collectionName: string): Promise<Record<string, unknown>[]> {\n\t\tconst accessor = this.store.collection(collectionName)\n\t\treturn accessor.where({}).exec()\n\t}\n\n\t/**\n\t * Get the device's node ID.\n\t */\n\tgetNodeId(): string {\n\t\treturn this.store.getNodeId()\n\t}\n\n\t/** Exposes the sync engine for integration tests (e.g. doc channel, chaos). */\n\tgetSyncEngine(): SyncEngine | null {\n\t\treturn this.syncEngine\n\t}\n\n\t/**\n\t * Operations the server rejected for this device that have not been reconciled.\n\t * Empty when the device has never connected.\n\t */\n\tasync getRejectedOperations(): Promise<import('@korajs/sync').RejectedOperation[]> {\n\t\tif (!this.syncEngine) return []\n\t\treturn this.syncEngine.getRejectedOperations()\n\t}\n\n\t/**\n\t * Stage a blob's bytes into this device's blob store, splitting them into\n\t * content-addressed chunks this device can then serve to peers over the sync\n\t * connection. Returns the manifest (blob hash + ordered chunk hashes) needed\n\t * to pull the blob elsewhere.\n\t */\n\tasync stageBlob(bytes: Uint8Array, options?: { chunkSize?: number }): Promise<BlobManifest> {\n\t\tconst { manifest } = await prepareBlobForSend(bytes, this.blobStore, options)\n\t\treturn manifest\n\t}\n\n\t/**\n\t * Store a blob for transfer the way `app.blobs.put` does: stage chunks, store\n\t * the full blob, and store the manifest as its own content-addressed object.\n\t * The returned reference carries a `manifestHash`, so a peer can pull the bytes\n\t * knowing only the reference.\n\t */\n\tasync putBlob(\n\t\tbytes: Uint8Array,\n\t\toptions?: { chunkSize?: number; mimeType?: string; filename?: string },\n\t): Promise<{ ref: BlobRef; manifest: BlobManifest }> {\n\t\treturn putBlobForTransfer(this.blobStore, bytes, options)\n\t}\n\n\t/**\n\t * Pull a blob's bytes over the live connection knowing only its reference: the\n\t * manifest is resolved by `ref.manifestHash` first, then the chunks are fetched.\n\t */\n\tasync pullBlobByRef(ref: BlobRef): Promise<ReceiveBlobResult> {\n\t\tif (!this.blobChunkProvider) {\n\t\t\tthrow new Error('Cannot pull a blob before the device has connected (call sync() first)')\n\t\t}\n\t\tconst manifest = await resolveBlobManifest(this.blobChunkProvider, ref)\n\t\treturn receiveBlob(manifest, this.blobChunkProvider, {\n\t\t\tchunkStore: this.blobStore,\n\t\t\tblobStore: this.blobStore,\n\t\t})\n\t}\n\n\t/**\n\t * Pull a blob's bytes from peers over the live sync connection. Requests only\n\t * the chunks this device is missing, reassembles them, and verifies integrity\n\t * against the manifest's blob hash before storing the full blob locally.\n\t */\n\tasync pullBlob(manifest: BlobManifest): Promise<ReceiveBlobResult> {\n\t\tif (!this.blobChunkProvider) {\n\t\t\tthrow new Error('Cannot pull a blob before the device has connected (call sync() first)')\n\t\t}\n\t\treturn receiveBlob(manifest, this.blobChunkProvider, {\n\t\t\tchunkStore: this.blobStore,\n\t\t\tblobStore: this.blobStore,\n\t\t})\n\t}\n\n\t/** Read fully-assembled blob bytes from this device's store, or null if absent. */\n\tasync getBlobBytes(hash: string): Promise<Uint8Array | null> {\n\t\treturn this.blobStore.get(hash)\n\t}\n\n\t/**\n\t * Get the device's version vector.\n\t */\n\tgetVersionVector(): VersionVector {\n\t\treturn this.store.getVersionVector()\n\t}\n\n\t/**\n\t * Check if the device is currently connected to the server.\n\t */\n\tisConnected(): boolean {\n\t\treturn this.currentTransport?.isConnected() ?? false\n\t}\n\n\t/**\n\t * Close the device, releasing all resources.\n\t */\n\tasync close(): Promise<void> {\n\t\tthis.closing = true\n\t\tif (this.unsubscribeAudit) {\n\t\t\tthis.unsubscribeAudit()\n\t\t\tthis.unsubscribeAudit = null\n\t\t}\n\t\tawait this.disconnect()\n\t\tawait this.store.close()\n\t\tthis.emitter.clear()\n\t}\n\n\t/**\n\t * Wait for in-flight sync operations to settle.\n\t * In-memory transports are near-synchronous, but apply/relay work is async.\n\t */\n\tprivate async waitForSettled(): Promise<void> {\n\t\tfor (let i = 0; i < 15; i++) {\n\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 20))\n\t\t}\n\t}\n\n\t/**\n\t * Wait for all pending outbound operations to be acknowledged.\n\t */\n\tprivate async waitForPendingOps(): Promise<void> {\n\t\tif (!this.syncEngine) return\n\t\tconst maxWait = 2000\n\t\tconst start = Date.now()\n\t\twhile (Date.now() - start < maxWait) {\n\t\t\tconst status = this.syncEngine.getStatus()\n\t\t\tif (status.pendingOperations === 0) return\n\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 10))\n\t\t}\n\t}\n}\n","import type { BlobRef, Operation, SchemaDefinition } from '@korajs/core'\nimport { MemoryServerStore } from '@korajs/server'\nimport { KoraSyncServer } from '@korajs/server'\nimport type { OperationValidator, ServerTransport } from '@korajs/server'\nimport { type ContentAddressedBlobStore, createMemoryServerBlobStore } from '@korajs/store'\n\n/**\n * In-memory test server wrapping KoraSyncServer with MemoryServerStore.\n * Handles client connections via memory transports.\n */\nexport interface TestServerOptions {\n\t/** Handshake schema version advertised by the server. Defaults to `schema.version`. */\n\tschemaVersion?: number\n\t/** Inclusive client schema versions accepted at handshake. */\n\tsupportedSchemaVersions?: { min: number; max: number }\n\t/** Enable central blob storage: the server persists and serves uploaded blob bytes. */\n\tblobStorage?: boolean\n\t/** Adjudicate untrusted client operations before materialization. */\n\tvalidateOperation?: OperationValidator\n}\n\nexport class TestServer {\n\treadonly store: MemoryServerStore\n\t/** The server's central blob store, present when `blobStorage` was enabled. */\n\treadonly blobStore: ContentAddressedBlobStore | null\n\tprivate readonly syncServer: KoraSyncServer\n\n\tconstructor(schema: SchemaDefinition, options?: TestServerOptions) {\n\t\tthis.store = new MemoryServerStore()\n\t\tconst schemaVersion = options?.schemaVersion ?? schema.version\n\t\tconst blob = options?.blobStorage ? createMemoryServerBlobStore() : null\n\t\tthis.blobStore = blob?.store ?? null\n\t\tthis.syncServer = new KoraSyncServer({\n\t\t\tstore: this.store,\n\t\t\tschemaVersion,\n\t\t\tsupportedSchemaVersions: options?.supportedSchemaVersions ?? {\n\t\t\t\tmin: schemaVersion,\n\t\t\t\tmax: schemaVersion,\n\t\t\t},\n\t\t\t...(blob ? blob.callbacks : {}),\n\t\t\t...(options?.validateOperation ? { validateOperation: options.validateOperation } : {}),\n\t\t})\n\t\tvoid this.store.setSchema(schema)\n\t}\n\n\t/**\n\t * Register a client connection transport with the server.\n\t * Returns the session ID assigned by the server.\n\t */\n\thandleConnection(transport: ServerTransport): string {\n\t\treturn this.syncServer.handleConnection(transport)\n\t}\n\n\t/**\n\t * Get all operations stored on the server.\n\t */\n\tgetAllOperations(): Operation[] {\n\t\treturn this.store.getAllOperations()\n\t}\n\n\t/**\n\t * Get the number of connected clients.\n\t */\n\tgetConnectionCount(): number {\n\t\treturn this.syncServer.getConnectionCount()\n\t}\n\n\t/** Every blob reference still reachable from live records on the server. */\n\tgetLiveBlobRefs(): Promise<BlobRef[]> {\n\t\treturn this.syncServer.getLiveBlobRefs()\n\t}\n\n\t/**\n\t * Shut down the server and close all sessions.\n\t */\n\tasync close(): Promise<void> {\n\t\tawait this.syncServer.stop()\n\t\tawait this.store.close()\n\t}\n}\n","import type { SchemaDefinition } from '@korajs/core'\nimport type { TestDevice } from './test-device'\n\n/**\n * Result of a convergence check.\n */\nexport interface ConvergenceResult {\n\t/** Whether all devices have converged to the same state */\n\tconverged: boolean\n\t/** Per-collection comparison details (only populated on failure) */\n\tdifferences: CollectionDifference[]\n}\n\n/**\n * Describes a difference in a collection between devices.\n */\nexport interface CollectionDifference {\n\tcollection: string\n\tdeviceA: string\n\tdeviceB: string\n\t/** Records present in deviceA but not deviceB */\n\tmissingInB: string[]\n\t/** Records present in deviceB but not deviceA */\n\tmissingInA: string[]\n\t/** Records present in both but with different values */\n\tfieldDifferences: FieldDifference[]\n}\n\n/**\n * A specific field-level difference between two devices.\n */\nexport interface FieldDifference {\n\trecordId: string\n\tfield: string\n\tvalueInA: unknown\n\tvalueInB: unknown\n}\n\n/**\n * Assert that all devices have converged to identical collection states.\n *\n * Compares every collection across all device pairs. Throws an error\n * with detailed diagnostics if any differences are found.\n *\n * @param devices - The devices to check for convergence\n * @param schema - The schema (used to enumerate collections)\n *\n * @example\n * ```typescript\n * await deviceA.sync()\n * await deviceB.sync()\n * await expectConverged([deviceA, deviceB], schema)\n * ```\n */\nexport async function expectConverged(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n): Promise<void> {\n\tif (devices.length < 2) return\n\n\tconst result = await checkConvergence(devices, schema)\n\tif (!result.converged) {\n\t\tconst details = result.differences\n\t\t\t.map((d) => {\n\t\t\t\tconst parts: string[] = [\n\t\t\t\t\t` Collection \"${d.collection}\" differs between ${d.deviceA} and ${d.deviceB}:`,\n\t\t\t\t]\n\t\t\t\tif (d.missingInB.length > 0) {\n\t\t\t\t\tparts.push(` Missing in ${d.deviceB}: ${d.missingInB.join(', ')}`)\n\t\t\t\t}\n\t\t\t\tif (d.missingInA.length > 0) {\n\t\t\t\t\tparts.push(` Missing in ${d.deviceA}: ${d.missingInA.join(', ')}`)\n\t\t\t\t}\n\t\t\t\tfor (const fd of d.fieldDifferences) {\n\t\t\t\t\tparts.push(\n\t\t\t\t\t\t` Record \"${fd.recordId}\" field \"${fd.field}\": ${JSON.stringify(fd.valueInA)} vs ${JSON.stringify(fd.valueInB)}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn parts.join('\\n')\n\t\t\t})\n\t\t\t.join('\\n')\n\n\t\tthrow new Error(`Devices have not converged:\\n${details}`)\n\t}\n}\n\n/**\n * Poll until all devices converge or timeout. Use after sync in integration tests\n * where relay/apply work may still be in flight under parallel CI load.\n */\nexport async function expectConvergedEventually(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n\toptions?: { timeoutMs?: number; intervalMs?: number },\n): Promise<void> {\n\tif (devices.length < 2) return\n\n\tconst timeoutMs = options?.timeoutMs ?? 5000\n\tconst intervalMs = options?.intervalMs ?? 25\n\tconst deadline = Date.now() + timeoutMs\n\n\twhile (Date.now() < deadline) {\n\t\tconst result = await checkConvergence(devices, schema)\n\t\tif (result.converged) {\n\t\t\treturn\n\t\t}\n\t\tawait new Promise<void>((resolve) => setTimeout(resolve, intervalMs))\n\t}\n\n\tawait expectConverged(devices, schema)\n}\n\n/**\n * Check whether all devices have converged without throwing.\n *\n * @param devices - The devices to check\n * @param schema - The schema (for collection enumeration)\n * @returns Convergence result with details\n */\nexport async function checkConvergence(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n): Promise<ConvergenceResult> {\n\tconst differences: CollectionDifference[] = []\n\tconst collectionNames = Object.keys(schema.collections)\n\n\tfor (let i = 0; i < devices.length - 1; i++) {\n\t\tfor (let j = i + 1; j < devices.length; j++) {\n\t\t\tconst deviceA = devices[i] as TestDevice\n\t\t\tconst deviceB = devices[j] as TestDevice\n\n\t\t\tfor (const collection of collectionNames) {\n\t\t\t\tconst stateA = await deviceA.getState(collection)\n\t\t\t\tconst stateB = await deviceB.getState(collection)\n\n\t\t\t\tconst diff = compareCollectionStates(collection, deviceA.name, deviceB.name, stateA, stateB)\n\n\t\t\t\tif (diff) {\n\t\t\t\t\tdifferences.push(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tconverged: differences.length === 0,\n\t\tdifferences,\n\t}\n}\n\n/**\n * Compare two collection states and return differences if any.\n */\nfunction compareCollectionStates(\n\tcollection: string,\n\tnameA: string,\n\tnameB: string,\n\tstateA: Record<string, unknown>[],\n\tstateB: Record<string, unknown>[],\n): CollectionDifference | null {\n\tconst mapA = new Map(stateA.map((r) => [r.id as string, r]))\n\tconst mapB = new Map(stateB.map((r) => [r.id as string, r]))\n\n\tconst missingInB: string[] = []\n\tconst missingInA: string[] = []\n\tconst fieldDifferences: FieldDifference[] = []\n\n\t// Check records in A\n\tfor (const [id, recordA] of mapA) {\n\t\tconst recordB = mapB.get(id)\n\t\tif (!recordB) {\n\t\t\tmissingInB.push(id)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Compare fields (skip internal fields like _created_at, _updated_at)\n\t\tconst allFields = new Set([\n\t\t\t...Object.keys(recordA).filter((k) => !k.startsWith('_')),\n\t\t\t...Object.keys(recordB).filter((k) => !k.startsWith('_')),\n\t\t])\n\n\t\tfor (const field of allFields) {\n\t\t\tif (field === 'id') continue\n\t\t\tconst valA = recordA[field]\n\t\t\tconst valB = recordB[field]\n\t\t\tif (!deepEqual(valA, valB)) {\n\t\t\t\tfieldDifferences.push({\n\t\t\t\t\trecordId: id,\n\t\t\t\t\tfield,\n\t\t\t\t\tvalueInA: valA,\n\t\t\t\t\tvalueInB: valB,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check records only in B\n\tfor (const id of mapB.keys()) {\n\t\tif (!mapA.has(id)) {\n\t\t\tmissingInA.push(id)\n\t\t}\n\t}\n\n\tif (missingInB.length === 0 && missingInA.length === 0 && fieldDifferences.length === 0) {\n\t\treturn null\n\t}\n\n\treturn {\n\t\tcollection,\n\t\tdeviceA: nameA,\n\t\tdeviceB: nameB,\n\t\tmissingInB,\n\t\tmissingInA,\n\t\tfieldDifferences,\n\t}\n}\n\n/**\n * Deep equality check for comparing field values.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n\tif (a === b) return true\n\tif (a === null || b === null) return false\n\tif (a === undefined || b === undefined) return false\n\tif (typeof a !== typeof b) return false\n\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\tif (a.length !== b.length) return false\n\t\treturn a.every((val, i) => deepEqual(val, b[i]))\n\t}\n\n\tif (typeof a === 'object' && typeof b === 'object') {\n\t\tconst keysA = Object.keys(a as Record<string, unknown>)\n\t\tconst keysB = Object.keys(b as Record<string, unknown>)\n\t\tif (keysA.length !== keysB.length) return false\n\t\treturn keysA.every((key) =>\n\t\t\tdeepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n\t\t)\n\t}\n\n\treturn false\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { MessageSerializer, SyncMessage, SyncTransport, TransportOptions } from '@korajs/sync'\nimport { ProtobufMessageSerializer } from '@korajs/sync'\n\n/**\n * A linked client/server transport pair, as consumed by {@link TestDevice}.\n */\nexport interface TransportPair {\n\tclient: SyncTransport\n\tserverTransport: ServerTransport\n}\n\n/**\n * Message types the protobuf serializer models. Ephemeral messages\n * (awareness-update, yjs-doc-update) are JSON-only in the protocol, so they are\n * deep-copied instead of forced through the protobuf envelope.\n */\nconst PROTOBUF_WIRE_TYPES: ReadonlySet<SyncMessage['type']> = new Set([\n\t'handshake',\n\t'handshake-response',\n\t'operation-batch',\n\t'acknowledgment',\n\t'error',\n])\n\n/**\n * Round-trips a message through the real protobuf wire codec: object → protobuf\n * bytes → object. This is what makes a transport pair exercise the true wire\n * format rather than passing live object references between the two ends.\n */\nfunction roundTripThroughProtobuf(\n\tmessage: SyncMessage,\n\tserializer: MessageSerializer,\n): SyncMessage {\n\tif (!PROTOBUF_WIRE_TYPES.has(message.type)) {\n\t\t// Not part of the protobuf schema — still copy so no live reference leaks\n\t\t// across the \"wire\".\n\t\treturn structuredClone(message)\n\t}\n\n\tconst encoded = serializer.encode(message)\n\tif (!(encoded instanceof Uint8Array)) {\n\t\tthrow new Error('Expected the protobuf serializer to encode to bytes')\n\t}\n\treturn serializer.decode(encoded)\n}\n\n/**\n * Wraps a client transport so every outbound message crosses the protobuf wire.\n */\nclass ProtobufWireClientTransport implements SyncTransport {\n\tconstructor(\n\t\tprivate readonly inner: SyncTransport,\n\t\tprivate readonly serializer: MessageSerializer,\n\t) {}\n\n\tconnect(url: string, options?: TransportOptions): Promise<void> {\n\t\treturn this.inner.connect(url, options)\n\t}\n\n\tdisconnect(): Promise<void> {\n\t\treturn this.inner.disconnect()\n\t}\n\n\tsend(message: SyncMessage): void {\n\t\tthis.inner.send(roundTripThroughProtobuf(message, this.serializer))\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n}\n\n/**\n * Wraps a server transport so every outbound message crosses the protobuf wire.\n */\nclass ProtobufWireServerTransport implements ServerTransport {\n\tconstructor(\n\t\tprivate readonly inner: ServerTransport,\n\t\tprivate readonly serializer: MessageSerializer,\n\t) {}\n\n\tsend(message: SyncMessage): void {\n\t\tthis.inner.send(roundTripThroughProtobuf(message, this.serializer))\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (code: number, reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.inner.close(code, reason)\n\t}\n}\n\n/**\n * Wraps a transport pair so that every sync message exchanged between the two\n * ends is encoded to protobuf bytes and decoded back — a faithful stand-in for\n * a real network hop that uses the protobuf wire format.\n *\n * Both directions are wrapped, so operations, handshakes, batches, and acks all\n * travel as bytes rather than shared object references.\n *\n * @param pair - The underlying (in-memory) transport pair to wrap\n * @returns A transport pair that routes messages through the protobuf codec\n */\nexport function wrapTransportPairWithProtobufWire(pair: TransportPair): TransportPair {\n\tconst serializer = new ProtobufMessageSerializer()\n\treturn {\n\t\tclient: new ProtobufWireClientTransport(pair.client, serializer),\n\t\tserverTransport: new ProtobufWireServerTransport(pair.serverTransport, serializer),\n\t}\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { SyncMessage } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\n\n/**\n * Wraps a server transport so its handshake responses advertise a caller-chosen\n * wall-clock time, independent of the process `Date.now()`.\n *\n * The sync engine measures clock skew as `serverTime - Date.now()` at handshake.\n * To simulate a device whose clock is fast while the server's clock is correct —\n * both running in one test process — the client mocks `Date.now()` and this\n * wrapper injects the server's true time into the handshake response, so the\n * skew the engine sees is real.\n */\nclass ServerClockServerTransport implements ServerTransport {\n\tconstructor(\n\t\tprivate readonly inner: ServerTransport,\n\t\tprivate readonly serverNow: () => number,\n\t) {}\n\n\tsend(message: SyncMessage): void {\n\t\tif (message.type === 'handshake-response') {\n\t\t\tthis.inner.send({ ...message, serverTime: this.serverNow() })\n\t\t\treturn\n\t\t}\n\t\tthis.inner.send(message)\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (code: number, reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.inner.close(code, reason)\n\t}\n}\n\n/**\n * Wraps a transport pair so the server side reports `serverNow()` as its\n * handshake wall-clock time.\n *\n * @param pair - The underlying transport pair\n * @param serverNow - Returns the server's true current time (ms since epoch)\n * @returns A transport pair whose handshake responses carry the injected time\n */\nexport function wrapTransportPairWithServerClock(\n\tpair: TransportPair,\n\tserverNow: () => number,\n): TransportPair {\n\treturn {\n\t\tclient: pair.client,\n\t\tserverTransport: new ServerClockServerTransport(pair.serverTransport, serverNow),\n\t}\n}\n","// @korajs/test — testing harness for Kora.js\n// Creates virtual device networks for testing sync convergence and conflicts.\n\n// === Factory ===\nexport { createTestNetwork, createMixedTestNetwork } from './test-network'\nexport type {\n\tTestNetwork,\n\tTestNetworkOptions,\n\tMixedTestDeviceConfig,\n} from './test-network'\n\n// === Device ===\nexport { TestDevice } from './test-device'\nexport type { TestDeviceOptions } from './test-device'\n\n// === Server ===\nexport { TestServer } from './test-server'\nexport type { TestServerOptions } from './test-server'\n\n// === Assertions ===\nexport { checkConvergence, expectConverged, expectConvergedEventually } from './assertions'\nexport type {\n\tCollectionDifference,\n\tConvergenceResult,\n\tFieldDifference,\n} from './assertions'\n\n// === Protobuf wire transport (for testing convergence through the real wire format) ===\nexport { wrapTransportPairWithProtobufWire } from './protobuf-wire-transport'\nexport type { TransportPair } from './protobuf-wire-transport'\n\n// === Server-clock transport (for testing clock-skew / rebase integration) ===\nexport { wrapTransportPairWithServerClock } from './server-clock-transport'\n\n// === Re-export ChaosTransport for convenience ===\nexport { ChaosTransport } from '@korajs/sync'\nexport type { ChaosConfig } from '@korajs/sync'\n"],"mappings":";AAAA,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,YAAY;AAErB,SAAS,iCAAiC;AAE1C,SAAS,sBAAsB;;;ACF/B,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAC5B;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AAQP,SAAS,4BAA4B;AACrC,SAAS,kBAAkB;AAE3B;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AA+BA,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,YAAY,IAAI,gBAAgB;AAAA,EAExB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,gBAAsC;AAAA,EACtC,aAAgC;AAAA,EAChC,oBAA0C;AAAA,EAC1C,mBAAyC;AAAA,EACzC,kBAAuC;AAAA,EACvC,mBAAwC;AAAA,EACxC,UAAU;AAAA,EAElB,YAAY,SAA4B;AACvC,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,QAAQ;AACtB,SAAK,sBAAsB,QAAQ;AACnC,SAAK,SAAS,GAAG,QAAQ,MAAM,gBAAgB,QAAQ,IAAI;AAC3D,SAAK,oBAAoB,QAAQ,qBAAqB,QAAQ,OAAO;AACrE,SAAK,sBAAsB,QAAQ,uBAAuB,CAAC;AAE3D,SAAK,UAAU,IAAI,mBAAmB;AACtC,SAAK,cAAc,IAAI,YAAY;AACnC,SAAK,UAAU,IAAI,qBAAqB,KAAK,MAAM;AACnD,SAAK,QAAQ,IAAI,MAAM;AAAA,MACtB,QAAQ,QAAQ;AAAA,MAChB,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,IACf,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC3B,UAAM,KAAK,MAAM,KAAK;AACtB,SAAK,gBAAgB,IAAI,cAAc;AAAA,MACtC,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK;AAAA,MAClB,SAAS,KAAK;AAAA,IACf,CAAC;AACD,SAAK,MAAM,wBAAwB,KAAK,aAAa;AAKrD,SAAK,mBAAmB,qBAAqB,KAAK,OAAO,KAAK,OAAO;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAsB;AAC3B,QAAI,KAAK,cAAc,KAAK,kBAAkB,YAAY,GAAG;AAE5D,YAAM,KAAK,kBAAkB;AAC7B,YAAM,KAAK,eAAe;AAC1B;AAAA,IACD;AAGA,UAAM,EAAE,QAAQ,gBAAgB,IAAI,KAAK,oBAAoB;AAC7D,SAAK,mBAAmB;AAExB,UAAM,kBAAuC,CAAC;AAC9C,UAAM,YAAY,IAAI,oBAAoB,KAAK,OAAO,KAAK,aAAa,KAAK,SAAS;AAAA,MACrF,iBAAiB,MAAM,gBAAgB,KAAK;AAAA,IAC7C,CAAC;AAED,SAAK,aAAa,IAAI,WAAW;AAAA,MAChC,WAAW;AAAA,MACX,OAAO;AAAA,MACP,cAAc,IAAI,kBAAkB,KAAK,OAAO;AAAA,MAChD,WAAW,IAAI,0BAA0B,KAAK,KAAK;AAAA,MACnD,QAAQ;AAAA,QACP,KAAK;AAAA,QACL,eAAe,KAAK;AAAA,QACpB,qBACC,KAAK,oBAAoB,SAAS,IAAI,KAAK,sBAAsB;AAAA,MACnE;AAAA,MACA,SAAS,KAAK;AAAA,IACf,CAAC;AACD,oBAAgB,KAAK,MAAM,KAAK,YAAY,eAAe;AAK3D,UAAM,YAAY,0BAA0B,KAAK,UAAU;AAC3D,oBAAgB,WAAW,KAAK,SAAS;AACzC,SAAK,oBAAoB,0BAA0B,SAAS;AAG5D,mBAAe,KAAK,SAAS,KAAK,YAAY,KAAK,SAAS;AAG5D,UAAM,SAAS,KAAK;AACpB,SAAK,kBAAkB,KAAK,QAAQ,GAAG,qBAAqB,CAAC,UAAU;AACtE,UAAI,CAAC,KAAK,WAAW,KAAK,eAAe,UAAU,KAAK,kBAAkB,YAAY,GAAG;AAExF,aAAK,WAAW,cAAc,MAAM,SAAS,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC9D;AAAA,IACD,CAAC;AAGD,SAAK,OAAO,iBAAiB,eAAe;AAG5C,UAAM,KAAK,WAAW,MAAM;AAI5B,UAAM,KAAK,eAAe;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AACjC,QAAI,KAAK,iBAAiB;AACzB,WAAK,gBAAgB;AACrB,WAAK,kBAAkB;AAAA,IACxB;AACA,QAAI,KAAK,YAAY;AACpB,YAAM,KAAK,WAAW,KAAK;AAC3B,WAAK,aAAa;AAAA,IACnB;AACA,SAAK,oBAAoB;AACzB,SAAK,mBAAmB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAA2B;AAChC,UAAM,KAAK,KAAK;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAkC;AAC5C,WAAO,KAAK,MAAM,WAAW,IAAI;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,gBAA4D;AAC1E,UAAM,WAAW,KAAK,MAAM,WAAW,cAAc;AACrD,WAAO,SAAS,MAAM,CAAC,CAAC,EAAE,KAAK;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAoB;AACnB,WAAO,KAAK,MAAM,UAAU;AAAA,EAC7B;AAAA;AAAA,EAGA,gBAAmC;AAClC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,wBAA6E;AAClF,QAAI,CAAC,KAAK,WAAY,QAAO,CAAC;AAC9B,WAAO,KAAK,WAAW,sBAAsB;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAU,OAAmB,SAAyD;AAC3F,UAAM,EAAE,SAAS,IAAI,MAAM,mBAAmB,OAAO,KAAK,WAAW,OAAO;AAC5E,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QACL,OACA,SACoD;AACpD,WAAO,mBAAmB,KAAK,WAAW,OAAO,OAAO;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,KAA0C;AAC7D,QAAI,CAAC,KAAK,mBAAmB;AAC5B,YAAM,IAAI,MAAM,wEAAwE;AAAA,IACzF;AACA,UAAM,WAAW,MAAM,oBAAoB,KAAK,mBAAmB,GAAG;AACtE,WAAO,YAAY,UAAU,KAAK,mBAAmB;AAAA,MACpD,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,IACjB,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAAS,UAAoD;AAClE,QAAI,CAAC,KAAK,mBAAmB;AAC5B,YAAM,IAAI,MAAM,wEAAwE;AAAA,IACzF;AACA,WAAO,YAAY,UAAU,KAAK,mBAAmB;AAAA,MACpD,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,IACjB,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,aAAa,MAA0C;AAC5D,WAAO,KAAK,UAAU,IAAI,IAAI;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAkC;AACjC,WAAO,KAAK,MAAM,iBAAiB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,cAAuB;AACtB,WAAO,KAAK,kBAAkB,YAAY,KAAK;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC5B,SAAK,UAAU;AACf,QAAI,KAAK,kBAAkB;AAC1B,WAAK,iBAAiB;AACtB,WAAK,mBAAmB;AAAA,IACzB;AACA,UAAM,KAAK,WAAW;AACtB,UAAM,KAAK,MAAM,MAAM;AACvB,SAAK,QAAQ,MAAM;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,iBAAgC;AAC7C,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC5B,YAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,IAC7D;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,oBAAmC;AAChD,QAAI,CAAC,KAAK,WAAY;AACtB,UAAM,UAAU;AAChB,UAAM,QAAQ,KAAK,IAAI;AACvB,WAAO,KAAK,IAAI,IAAI,QAAQ,SAAS;AACpC,YAAM,SAAS,KAAK,WAAW,UAAU;AACzC,UAAI,OAAO,sBAAsB,EAAG;AACpC,YAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,IAC7D;AAAA,EACD;AACD;;;ACxWA,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAE/B,SAAyC,mCAAmC;AAiBrE,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,EACQ;AAAA,EAEjB,YAAY,QAA0B,SAA6B;AAClE,SAAK,QAAQ,IAAI,kBAAkB;AACnC,UAAM,gBAAgB,SAAS,iBAAiB,OAAO;AACvD,UAAM,OAAO,SAAS,cAAc,4BAA4B,IAAI;AACpE,SAAK,YAAY,MAAM,SAAS;AAChC,SAAK,aAAa,IAAI,eAAe;AAAA,MACpC,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,yBAAyB,SAAS,2BAA2B;AAAA,QAC5D,KAAK;AAAA,QACL,KAAK;AAAA,MACN;AAAA,MACA,GAAI,OAAO,KAAK,YAAY,CAAC;AAAA,MAC7B,GAAI,SAAS,oBAAoB,EAAE,mBAAmB,QAAQ,kBAAkB,IAAI,CAAC;AAAA,IACtF,CAAC;AACD,SAAK,KAAK,MAAM,UAAU,MAAM;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,WAAoC;AACpD,WAAO,KAAK,WAAW,iBAAiB,SAAS;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAgC;AAC/B,WAAO,KAAK,MAAM,iBAAiB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC5B,WAAO,KAAK,WAAW,mBAAmB;AAAA,EAC3C;AAAA;AAAA,EAGA,kBAAsC;AACrC,WAAO,KAAK,WAAW,gBAAgB;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC5B,UAAM,KAAK,WAAW,KAAK;AAC3B,UAAM,KAAK,MAAM,MAAM;AAAA,EACxB;AACD;;;AFJA,eAAsB,kBACrB,QACA,SACuB;AACvB,QAAM,cAAc,SAAS,WAAW;AACxC,QAAM,cACL,SAAS,eAAe,MAAM,KAAK,EAAE,QAAQ,YAAY,GAAG,CAAC,GAAG,MAAM,UAAU,CAAC,EAAE;AAGpF,QAAM,SAAS,YAAY,KAAK,OAAO,GAAG,YAAY,CAAC;AAGvD,QAAM,SAAS,IAAI,WAAW,QAAQ;AAAA,IACrC,aAAa,SAAS;AAAA,IACtB,mBAAmB,SAAS;AAAA,EAC7B,CAAC;AAGD,QAAM,UAAwB,CAAC;AAC/B,aAAW,QAAQ,aAAa;AAC/B,UAAM,SAAS,IAAI,WAAW;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA,qBAAqB,MAAM;AAC1B,cAAM,OAAO,0BAA0B;AACvC,cAAM,SAAS,KAAK;AACpB,cAAM,QAAQ,SAAS;AACvB,cAAM,OAAsB;AAAA,UAC3B,QAAQ,QAAQ,IAAI,eAAe,QAAQ,KAAK,IAAI;AAAA,UACpD,iBAAiB,KAAK;AAAA,QACvB;AACA,eAAO,SAAS,gBAAgB,QAAQ,cAAc,IAAI,IAAI;AAAA,MAC/D;AAAA,MACA;AAAA,IACD,CAAC;AACD,UAAM,OAAO,KAAK;AAClB,YAAQ,KAAK,MAAM;AAAA,EACpB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,QAAuB;AAC5B,iBAAW,UAAU,SAAS;AAC7B,cAAM,OAAO,MAAM;AAAA,MACpB;AACA,YAAM,OAAO,MAAM;AAEnB,UAAI;AACH,cAAM,EAAE,OAAO,IAAI,MAAM,OAAO,IAAS;AACzC,eAAO,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MAChD,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AACD;AAgBA,eAAsB,uBACrB,cACA,eACA,eACuB;AACvB,QAAM,SAAS,YAAY,KAAK,OAAO,GAAG,YAAY,CAAC;AACvD,QAAM,SAAS,IAAI,WAAW,cAAc,aAAa;AAEzD,QAAM,UAAwB,CAAC;AAC/B,aAAW,UAAU,eAAe;AACnC,UAAM,SAAS,IAAI,WAAW;AAAA,MAC7B,MAAM,OAAO;AAAA,MACb,QAAQ,OAAO;AAAA,MACf;AAAA,MACA,mBAAmB,OAAO;AAAA,MAC1B,qBAAqB,OAAO;AAAA,MAC5B,qBAAqB,MAAM;AAC1B,cAAM,OAAO,0BAA0B;AACvC,eAAO;AAAA,UACN,QAAQ,KAAK;AAAA,UACb,iBAAiB,KAAK;AAAA,QACvB;AAAA,MACD;AAAA,MACA;AAAA,IACD,CAAC;AACD,UAAM,OAAO,KAAK;AAClB,YAAQ,KAAK,MAAM;AAAA,EACpB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,QAAuB;AAC5B,iBAAW,UAAU,SAAS;AAC7B,cAAM,OAAO,MAAM;AAAA,MACpB;AACA,YAAM,OAAO,MAAM;AACnB,UAAI;AACH,cAAM,EAAE,OAAO,IAAI,MAAM,OAAO,IAAS;AACzC,eAAO,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MAChD,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AACD;;;AG7IA,eAAsB,gBACrB,SACA,QACgB;AAChB,MAAI,QAAQ,SAAS,EAAG;AAExB,QAAM,SAAS,MAAM,iBAAiB,SAAS,MAAM;AACrD,MAAI,CAAC,OAAO,WAAW;AACtB,UAAM,UAAU,OAAO,YACrB,IAAI,CAAC,MAAM;AACX,YAAM,QAAkB;AAAA,QACvB,iBAAiB,EAAE,UAAU,qBAAqB,EAAE,OAAO,QAAQ,EAAE,OAAO;AAAA,MAC7E;AACA,UAAI,EAAE,WAAW,SAAS,GAAG;AAC5B,cAAM,KAAK,kBAAkB,EAAE,OAAO,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC,EAAE;AAAA,MACrE;AACA,UAAI,EAAE,WAAW,SAAS,GAAG;AAC5B,cAAM,KAAK,kBAAkB,EAAE,OAAO,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC,EAAE;AAAA,MACrE;AACA,iBAAW,MAAM,EAAE,kBAAkB;AACpC,cAAM;AAAA,UACL,eAAe,GAAG,QAAQ,YAAY,GAAG,KAAK,MAAM,KAAK,UAAU,GAAG,QAAQ,CAAC,OAAO,KAAK,UAAU,GAAG,QAAQ,CAAC;AAAA,QAClH;AAAA,MACD;AACA,aAAO,MAAM,KAAK,IAAI;AAAA,IACvB,CAAC,EACA,KAAK,IAAI;AAEX,UAAM,IAAI,MAAM;AAAA,EAAgC,OAAO,EAAE;AAAA,EAC1D;AACD;AAMA,eAAsB,0BACrB,SACA,QACA,SACgB;AAChB,MAAI,QAAQ,SAAS,EAAG;AAExB,QAAM,YAAY,SAAS,aAAa;AACxC,QAAM,aAAa,SAAS,cAAc;AAC1C,QAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC7B,UAAM,SAAS,MAAM,iBAAiB,SAAS,MAAM;AACrD,QAAI,OAAO,WAAW;AACrB;AAAA,IACD;AACA,UAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,UAAU,CAAC;AAAA,EACrE;AAEA,QAAM,gBAAgB,SAAS,MAAM;AACtC;AASA,eAAsB,iBACrB,SACA,QAC6B;AAC7B,QAAM,cAAsC,CAAC;AAC7C,QAAM,kBAAkB,OAAO,KAAK,OAAO,WAAW;AAEtD,WAAS,IAAI,GAAG,IAAI,QAAQ,SAAS,GAAG,KAAK;AAC5C,aAAS,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AAC5C,YAAM,UAAU,QAAQ,CAAC;AACzB,YAAM,UAAU,QAAQ,CAAC;AAEzB,iBAAW,cAAc,iBAAiB;AACzC,cAAM,SAAS,MAAM,QAAQ,SAAS,UAAU;AAChD,cAAM,SAAS,MAAM,QAAQ,SAAS,UAAU;AAEhD,cAAM,OAAO,wBAAwB,YAAY,QAAQ,MAAM,QAAQ,MAAM,QAAQ,MAAM;AAE3F,YAAI,MAAM;AACT,sBAAY,KAAK,IAAI;AAAA,QACtB;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AAAA,IACN,WAAW,YAAY,WAAW;AAAA,IAClC;AAAA,EACD;AACD;AAKA,SAAS,wBACR,YACA,OACA,OACA,QACA,QAC8B;AAC9B,QAAM,OAAO,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,IAAc,CAAC,CAAC,CAAC;AAC3D,QAAM,OAAO,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,IAAc,CAAC,CAAC,CAAC;AAE3D,QAAM,aAAuB,CAAC;AAC9B,QAAM,aAAuB,CAAC;AAC9B,QAAM,mBAAsC,CAAC;AAG7C,aAAW,CAAC,IAAI,OAAO,KAAK,MAAM;AACjC,UAAM,UAAU,KAAK,IAAI,EAAE;AAC3B,QAAI,CAAC,SAAS;AACb,iBAAW,KAAK,EAAE;AAClB;AAAA,IACD;AAGA,UAAM,YAAY,oBAAI,IAAI;AAAA,MACzB,GAAG,OAAO,KAAK,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC;AAAA,MACxD,GAAG,OAAO,KAAK,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC;AAAA,IACzD,CAAC;AAED,eAAW,SAAS,WAAW;AAC9B,UAAI,UAAU,KAAM;AACpB,YAAM,OAAO,QAAQ,KAAK;AAC1B,YAAM,OAAO,QAAQ,KAAK;AAC1B,UAAI,CAAC,UAAU,MAAM,IAAI,GAAG;AAC3B,yBAAiB,KAAK;AAAA,UACrB,UAAU;AAAA,UACV;AAAA,UACA,UAAU;AAAA,UACV,UAAU;AAAA,QACX,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAGA,aAAW,MAAM,KAAK,KAAK,GAAG;AAC7B,QAAI,CAAC,KAAK,IAAI,EAAE,GAAG;AAClB,iBAAW,KAAK,EAAE;AAAA,IACnB;AAAA,EACD;AAEA,MAAI,WAAW,WAAW,KAAK,WAAW,WAAW,KAAK,iBAAiB,WAAW,GAAG;AACxF,WAAO;AAAA,EACR;AAEA,SAAO;AAAA,IACN;AAAA,IACA,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AAKA,SAAS,UAAU,GAAY,GAAqB;AACnD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO;AACrC,MAAI,MAAM,UAAa,MAAM,OAAW,QAAO;AAC/C,MAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAElC,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AACzC,QAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,WAAO,EAAE,MAAM,CAAC,KAAK,MAAM,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC;AAAA,EAChD;AAEA,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AACnD,UAAM,QAAQ,OAAO,KAAK,CAA4B;AACtD,UAAM,QAAQ,OAAO,KAAK,CAA4B;AACtD,QAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,WAAO,MAAM;AAAA,MAAM,CAAC,QACnB,UAAW,EAA8B,GAAG,GAAI,EAA8B,GAAG,CAAC;AAAA,IACnF;AAAA,EACD;AAEA,SAAO;AACR;;;AC/OA,SAAS,iCAAiC;AAe1C,IAAM,sBAAwD,oBAAI,IAAI;AAAA,EACrE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC;AAOD,SAAS,yBACR,SACA,YACc;AACd,MAAI,CAAC,oBAAoB,IAAI,QAAQ,IAAI,GAAG;AAG3C,WAAO,gBAAgB,OAAO;AAAA,EAC/B;AAEA,QAAM,UAAU,WAAW,OAAO,OAAO;AACzC,MAAI,EAAE,mBAAmB,aAAa;AACrC,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACtE;AACA,SAAO,WAAW,OAAO,OAAO;AACjC;AAKA,IAAM,8BAAN,MAA2D;AAAA,EAC1D,YACkB,OACA,YAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAGlB,QAAQ,KAAa,SAA2C;AAC/D,WAAO,KAAK,MAAM,QAAQ,KAAK,OAAO;AAAA,EACvC;AAAA,EAEA,aAA4B;AAC3B,WAAO,KAAK,MAAM,WAAW;AAAA,EAC9B;AAAA,EAEA,KAAK,SAA4B;AAChC,SAAK,MAAM,KAAK,yBAAyB,SAAS,KAAK,UAAU,CAAC;AAAA,EACnE;AAAA,EAEA,UAAU,SAA+C;AACxD,SAAK,MAAM,UAAU,OAAO;AAAA,EAC7B;AAAA,EAEA,QAAQ,SAAyC;AAChD,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,QAAQ,SAAuC;AAC9C,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,cAAuB;AACtB,WAAO,KAAK,MAAM,YAAY;AAAA,EAC/B;AACD;AAKA,IAAM,8BAAN,MAA6D;AAAA,EAC5D,YACkB,OACA,YAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAGlB,KAAK,SAA4B;AAChC,SAAK,MAAM,KAAK,yBAAyB,SAAS,KAAK,UAAU,CAAC;AAAA,EACnE;AAAA,EAEA,UAAU,SAA+C;AACxD,SAAK,MAAM,UAAU,OAAO;AAAA,EAC7B;AAAA,EAEA,QAAQ,SAAuD;AAC9D,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,QAAQ,SAAuC;AAC9C,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,cAAuB;AACtB,WAAO,KAAK,MAAM,YAAY;AAAA,EAC/B;AAAA,EAEA,MAAM,MAAe,QAAuB;AAC3C,SAAK,MAAM,MAAM,MAAM,MAAM;AAAA,EAC9B;AACD;AAaO,SAAS,kCAAkC,MAAoC;AACrF,QAAM,aAAa,IAAI,0BAA0B;AACjD,SAAO;AAAA,IACN,QAAQ,IAAI,4BAA4B,KAAK,QAAQ,UAAU;AAAA,IAC/D,iBAAiB,IAAI,4BAA4B,KAAK,iBAAiB,UAAU;AAAA,EAClF;AACD;;;AC1HA,IAAM,6BAAN,MAA4D;AAAA,EAC3D,YACkB,OACA,WAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAGlB,KAAK,SAA4B;AAChC,QAAI,QAAQ,SAAS,sBAAsB;AAC1C,WAAK,MAAM,KAAK,EAAE,GAAG,SAAS,YAAY,KAAK,UAAU,EAAE,CAAC;AAC5D;AAAA,IACD;AACA,SAAK,MAAM,KAAK,OAAO;AAAA,EACxB;AAAA,EAEA,UAAU,SAA+C;AACxD,SAAK,MAAM,UAAU,OAAO;AAAA,EAC7B;AAAA,EAEA,QAAQ,SAAuD;AAC9D,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,QAAQ,SAAuC;AAC9C,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC3B;AAAA,EAEA,cAAuB;AACtB,WAAO,KAAK,MAAM,YAAY;AAAA,EAC/B;AAAA,EAEA,MAAM,MAAe,QAAuB;AAC3C,SAAK,MAAM,MAAM,MAAM,MAAM;AAAA,EAC9B;AACD;AAUO,SAAS,iCACf,MACA,WACgB;AAChB,SAAO;AAAA,IACN,QAAQ,KAAK;AAAA,IACb,iBAAiB,IAAI,2BAA2B,KAAK,iBAAiB,SAAS;AAAA,EAChF;AACD;;;AC9BA,SAAS,kBAAAA,uBAAsB;","names":["ChaosTransport"]}
|
|
1
|
+
{"version":3,"sources":["../src/test-network.ts","../src/test-device.ts","../src/test-server.ts","../src/assertions.ts","../src/protobuf-wire-transport.ts","../src/server-clock-transport.ts","../src/index.ts"],"sourcesContent":["import { mkdtempSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport type { OperationTransform, SchemaDefinition } from '@korajs/core'\nimport { createServerTransportPair } from '@korajs/server/internal'\nimport type { ChaosConfig } from '@korajs/sync'\nimport { ChaosTransport } from '@korajs/sync'\nimport type { SyncTransport } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\nimport { TestDevice } from './test-device'\nimport { TestServer, type TestServerOptions } from './test-server'\n\n/**\n * Options for creating a test network.\n */\nexport interface TestNetworkOptions {\n\t/** Number of devices to create. Defaults to 2. */\n\tdevices?: number\n\t/** Custom device names. If not provided, uses 'device-0', 'device-1', etc. */\n\tdeviceNames?: string[]\n\t/** Optional chaos transport settings applied to each device link. */\n\tchaos?: ChaosConfig\n\t/**\n\t * Optional wrapper applied to each device's transport pair after the base\n\t * (in-memory, optionally chaos-wrapped) pair is created. Use it to route\n\t * messages through the protobuf wire codec\n\t * ({@link wrapTransportPairWithProtobufWire}) or to inject a controllable\n\t * server clock. Called once per device connection.\n\t */\n\twrapTransport?: (pair: TransportPair) => TransportPair\n\t/** Enable central blob storage on the server (persist + serve uploaded blob bytes). */\n\tblobStorage?: boolean\n\t/** Adjudicate untrusted client operations on the server before materialization. */\n\tvalidateOperation?: import('@korajs/server').OperationValidator\n}\n\n/**\n * A test network with a server and multiple devices.\n */\nexport interface TestNetwork {\n\t/** The test server */\n\tserver: TestServer\n\t/** All devices in the network */\n\tdevices: TestDevice[]\n\t/** Temporary directory for DB files */\n\ttmpDir: string\n\t/** Close all devices and the server, clean up temp files */\n\tclose(): Promise<void>\n}\n\n/**\n * Create a test network with a server and multiple virtual devices.\n *\n * Each device has its own local SQLite store and SyncEngine. Devices\n * communicate with the server via in-memory transports.\n *\n * @param schema - The schema all devices share\n * @param options - Network configuration\n * @returns A test network with server and devices ready for use\n *\n * @example\n * ```typescript\n * const network = await createTestNetwork(schema, { devices: 2 })\n * const [deviceA, deviceB] = network.devices\n *\n * await deviceA.collection('todos').insert({ title: 'Hello' })\n * await deviceA.sync()\n * await deviceB.sync()\n *\n * const todos = await deviceB.getState('todos')\n * expect(todos).toHaveLength(1)\n *\n * await network.close()\n * ```\n */\nexport async function createTestNetwork(\n\tschema: SchemaDefinition,\n\toptions?: TestNetworkOptions,\n): Promise<TestNetwork> {\n\tconst deviceCount = options?.devices ?? 2\n\tconst deviceNames =\n\t\toptions?.deviceNames ?? Array.from({ length: deviceCount }, (_, i) => `device-${i}`)\n\n\t// Create temp directory for DB files\n\tconst tmpDir = mkdtempSync(join(tmpdir(), 'kora-test-'))\n\n\t// Create server\n\tconst server = new TestServer(schema, {\n\t\tblobStorage: options?.blobStorage,\n\t\tvalidateOperation: options?.validateOperation,\n\t})\n\n\t// Create devices\n\tconst devices: TestDevice[] = []\n\tfor (const name of deviceNames) {\n\t\tconst device = new TestDevice({\n\t\t\tname,\n\t\t\tschema,\n\t\t\tserver,\n\t\t\tcreateTransportPair: () => {\n\t\t\t\tconst pair = createServerTransportPair()\n\t\t\t\tconst client = pair.client as unknown as SyncTransport\n\t\t\t\tconst chaos = options?.chaos\n\t\t\t\tconst base: TransportPair = {\n\t\t\t\t\tclient: chaos ? new ChaosTransport(client, chaos) : client,\n\t\t\t\t\tserverTransport: pair.server,\n\t\t\t\t}\n\t\t\t\treturn options?.wrapTransport ? options.wrapTransport(base) : base\n\t\t\t},\n\t\t\ttmpDir,\n\t\t})\n\t\tawait device.open()\n\t\tdevices.push(device)\n\t}\n\n\treturn {\n\t\tserver,\n\t\tdevices,\n\t\ttmpDir,\n\t\tasync close(): Promise<void> {\n\t\t\tfor (const device of devices) {\n\t\t\t\tawait device.close()\n\t\t\t}\n\t\t\tawait server.close()\n\t\t\t// Clean up temp DB files\n\t\t\ttry {\n\t\t\t\tconst { rmSync } = await import('node:fs')\n\t\t\t\trmSync(tmpDir, { recursive: true, force: true })\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t},\n\t}\n}\n\n/**\n * Per-device configuration when devices use different local schemas or sync settings.\n */\nexport interface MixedTestDeviceConfig {\n\tname: string\n\tschema: SchemaDefinition\n\tsyncSchemaVersion?: number\n\toperationTransforms?: OperationTransform[]\n}\n\n/**\n * Create a test network where devices may use different schema versions and transforms.\n * The server uses `serverSchema` for materialization and handshake bounds.\n */\nexport async function createMixedTestNetwork(\n\tserverSchema: SchemaDefinition,\n\tserverOptions: TestServerOptions,\n\tdeviceConfigs: MixedTestDeviceConfig[],\n): Promise<TestNetwork> {\n\tconst tmpDir = mkdtempSync(join(tmpdir(), 'kora-test-'))\n\tconst server = new TestServer(serverSchema, serverOptions)\n\n\tconst devices: TestDevice[] = []\n\tfor (const config of deviceConfigs) {\n\t\tconst device = new TestDevice({\n\t\t\tname: config.name,\n\t\t\tschema: config.schema,\n\t\t\tserver,\n\t\t\tsyncSchemaVersion: config.syncSchemaVersion,\n\t\t\toperationTransforms: config.operationTransforms,\n\t\t\tcreateTransportPair: () => {\n\t\t\t\tconst pair = createServerTransportPair()\n\t\t\t\treturn {\n\t\t\t\t\tclient: pair.client as unknown as SyncTransport,\n\t\t\t\t\tserverTransport: pair.server,\n\t\t\t\t}\n\t\t\t},\n\t\t\ttmpDir,\n\t\t})\n\t\tawait device.open()\n\t\tdevices.push(device)\n\t}\n\n\treturn {\n\t\tserver,\n\t\tdevices,\n\t\ttmpDir,\n\t\tasync close(): Promise<void> {\n\t\t\tfor (const device of devices) {\n\t\t\t\tawait device.close()\n\t\t\t}\n\t\t\tawait server.close()\n\t\t\ttry {\n\t\t\t\tconst { rmSync } = await import('node:fs')\n\t\t\t\trmSync(tmpDir, { recursive: true, force: true })\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t},\n\t}\n}\n","import type { OperationTransform, SchemaDefinition } from '@korajs/core'\nimport type { VersionVector } from '@korajs/core'\nimport type { KoraEventEmitter } from '@korajs/core'\nimport type { BlobRef } from '@korajs/core'\nimport { SimpleEventEmitter } from '@korajs/core/internal'\nimport { MergeEngine } from '@korajs/merge'\nimport {\n\tMemoryBlobStore,\n\tStore,\n\tcreateRemoteChunkProvider,\n\tprepareBlobForSend,\n\tputBlobForTransfer,\n\treceiveBlob,\n\tresolveBlobManifest,\n\tserveBlobChunks,\n} from '@korajs/store'\nimport type {\n\tBlobManifest,\n\tChunkProvider,\n\tCollectionAccessor,\n\tReceiveBlobResult,\n\tStorageAdapter,\n} from '@korajs/store'\nimport { BetterSqlite3Adapter } from '@korajs/store/better-sqlite3'\nimport { SyncEngine } from '@korajs/sync'\nimport type { SyncTransport } from '@korajs/sync'\nimport {\n\tApplyPipeline,\n\tMergeAwareSyncStore,\n\tStoreQueueStorage,\n\tStoreSyncStatePersistence,\n\tcreateSyncEngineChunkPort,\n\twireAuditPersistence,\n\twireBlobUpload,\n} from 'korajs/testing'\nimport type { TestServer } from './test-server'\n\n/**\n * Options for creating a TestDevice.\n */\nexport interface TestDeviceOptions {\n\t/** Unique device name (used for DB file naming) */\n\tname: string\n\t/** Schema definition */\n\tschema: SchemaDefinition\n\t/** Test server to connect to */\n\tserver: TestServer\n\t/** Transport factory — creates a linked client/server transport pair */\n\tcreateTransportPair: () => {\n\t\tclient: SyncTransport\n\t\tserverTransport: import('@korajs/server').ServerTransport\n\t}\n\t/** Optional directory for temp DB files */\n\ttmpDir: string\n\t/** Client handshake schema version. Defaults to `schema.version`. */\n\tsyncSchemaVersion?: number\n\t/** Transforms applied to inbound operations before local apply. */\n\toperationTransforms?: OperationTransform[]\n}\n\n/**\n * A virtual device in a test network.\n * Each device has its own Store (with real SQLite), SyncEngine, and MergeEngine.\n * Provides high-level methods for syncing, disconnecting, and inspecting state.\n */\nexport class TestDevice {\n\treadonly name: string\n\treadonly store: Store\n\treadonly emitter: KoraEventEmitter & { clear(): void }\n\t/** Content-addressed blob store for out-of-band blob bytes (chunks + full blobs). */\n\treadonly blobStore = new MemoryBlobStore()\n\n\tprivate readonly schema: SchemaDefinition\n\tprivate readonly server: TestServer\n\tprivate readonly mergeEngine: MergeEngine\n\tprivate readonly createTransportPair: TestDeviceOptions['createTransportPair']\n\tprivate readonly adapter: StorageAdapter\n\tprivate readonly dbPath: string\n\tprivate readonly syncSchemaVersion: number\n\tprivate readonly operationTransforms: OperationTransform[]\n\n\tprivate applyPipeline: ApplyPipeline | null = null\n\tprivate syncEngine: SyncEngine | null = null\n\tprivate blobChunkProvider: ChunkProvider | null = null\n\tprivate currentTransport: SyncTransport | null = null\n\tprivate unsubscribeSync: (() => void) | null = null\n\tprivate unsubscribeAudit: (() => void) | null = null\n\tprivate closing = false\n\n\tconstructor(options: TestDeviceOptions) {\n\t\tthis.name = options.name\n\t\tthis.schema = options.schema\n\t\tthis.server = options.server\n\t\tthis.createTransportPair = options.createTransportPair\n\t\tthis.dbPath = `${options.tmpDir}/test-device-${options.name}.db`\n\t\tthis.syncSchemaVersion = options.syncSchemaVersion ?? options.schema.version\n\t\tthis.operationTransforms = options.operationTransforms ?? []\n\n\t\tthis.emitter = new SimpleEventEmitter()\n\t\tthis.mergeEngine = new MergeEngine()\n\t\tthis.adapter = new BetterSqlite3Adapter(this.dbPath)\n\t\tthis.store = new Store({\n\t\t\tschema: options.schema,\n\t\t\tadapter: this.adapter,\n\t\t\temitter: this.emitter,\n\t\t})\n\t}\n\n\t/**\n\t * Open the store (must be called before sync or collection operations).\n\t */\n\tasync open(): Promise<void> {\n\t\tawait this.store.open()\n\t\tthis.applyPipeline = new ApplyPipeline({\n\t\t\tstore: this.store,\n\t\t\tmergeEngine: this.mergeEngine,\n\t\t\temitter: this.emitter,\n\t\t})\n\t\tthis.store.setLocalMutationHandler(this.applyPipeline)\n\t\t// Match production wiring (createApp): merge/constraint traces persist to\n\t\t// `_kora_audit_traces`. Without this, harness devices emit merge events\n\t\t// but the durable audit trail every real app has stays empty — a fidelity\n\t\t// gap that hid from tests until Studio's Merges view made it visible.\n\t\tthis.unsubscribeAudit = wireAuditPersistence(this.store, this.emitter)\n\t}\n\n\t/**\n\t * Connect to the test server and perform initial sync.\n\t * If already connected, flushes any pending operations.\n\t */\n\tasync sync(): Promise<void> {\n\t\tif (this.syncEngine && this.currentTransport?.isConnected()) {\n\t\t\t// Already connected — flush outbound ops, then allow inbound relay to settle\n\t\t\tawait this.waitForPendingOps()\n\t\t\tawait this.waitForSettled()\n\t\t\treturn\n\t\t}\n\n\t\t// Create a new transport pair and connect\n\t\tconst { client, serverTransport } = this.createTransportPair()\n\t\tthis.currentTransport = client\n\n\t\tconst conflictHandler: { fn?: () => void } = {}\n\t\tconst syncStore = new MergeAwareSyncStore(this.store, this.mergeEngine, this.emitter, {\n\t\t\tonMergeConflict: () => conflictHandler.fn?.(),\n\t\t})\n\n\t\tthis.syncEngine = new SyncEngine({\n\t\t\ttransport: client,\n\t\t\tstore: syncStore,\n\t\t\tqueueStorage: new StoreQueueStorage(this.adapter),\n\t\t\tsyncState: new StoreSyncStatePersistence(this.store),\n\t\t\tconfig: {\n\t\t\t\turl: 'ws://test-network',\n\t\t\t\tschemaVersion: this.syncSchemaVersion,\n\t\t\t\toperationTransforms:\n\t\t\t\t\tthis.operationTransforms.length > 0 ? this.operationTransforms : undefined,\n\t\t\t},\n\t\t\temitter: this.emitter,\n\t\t})\n\t\tconflictHandler.fn = () => this.syncEngine?.recordConflict()\n\n\t\t// Bind the blob chunk channel to this connection: serve chunks this device\n\t\t// holds, and prepare a provider to pull chunks it needs. Both ride the same\n\t\t// sync socket; the request/response handlers are disjoint by message type.\n\t\tconst chunkPort = createSyncEngineChunkPort(this.syncEngine)\n\t\tserveBlobChunks(chunkPort, this.blobStore)\n\t\tthis.blobChunkProvider = createRemoteChunkProvider(chunkPort)\n\t\t// Mirror createApp: auto-upload blob bytes to the server as ops sync, so\n\t\t// blobs stay available after this device disconnects.\n\t\twireBlobUpload(this.emitter, this.syncEngine, this.blobStore)\n\n\t\t// Wire local mutations to sync outbound queue\n\t\tconst engine = this.syncEngine\n\t\tthis.unsubscribeSync = this.emitter.on('operation:created', (event) => {\n\t\t\tif (!this.closing && this.syncEngine === engine && this.currentTransport?.isConnected()) {\n\t\t\t\t// Catch async errors from push racing with disconnect during teardown\n\t\t\t\tthis.syncEngine.pushOperation(event.operation).catch(() => {})\n\t\t\t}\n\t\t})\n\n\t\t// Register server-side connection\n\t\tthis.server.handleConnection(serverTransport)\n\n\t\t// Start sync engine (connects, handshakes, exchanges deltas)\n\t\tawait this.syncEngine.start()\n\n\t\t// Wait for sync messages to propagate (in-memory transport is synchronous\n\t\t// but some processing is async)\n\t\tawait this.waitForSettled()\n\t}\n\n\t/**\n\t * Disconnect from the test server.\n\t */\n\tasync disconnect(): Promise<void> {\n\t\tif (this.unsubscribeSync) {\n\t\t\tthis.unsubscribeSync()\n\t\t\tthis.unsubscribeSync = null\n\t\t}\n\t\tif (this.syncEngine) {\n\t\t\tawait this.syncEngine.stop()\n\t\t\tthis.syncEngine = null\n\t\t}\n\t\tthis.blobChunkProvider = null\n\t\tthis.currentTransport = null\n\t}\n\n\t/**\n\t * Reconnect to the test server after a disconnect.\n\t */\n\tasync reconnect(): Promise<void> {\n\t\tawait this.sync()\n\t}\n\n\t/**\n\t * Get a collection accessor for performing CRUD operations.\n\t */\n\tcollection(name: string): CollectionAccessor {\n\t\treturn this.store.collection(name)\n\t}\n\n\t/**\n\t * Get all records from a collection (convenience method).\n\t */\n\tasync getState(collectionName: string): Promise<Record<string, unknown>[]> {\n\t\tconst accessor = this.store.collection(collectionName)\n\t\treturn accessor.where({}).exec()\n\t}\n\n\t/**\n\t * Get the device's node ID.\n\t */\n\tgetNodeId(): string {\n\t\treturn this.store.getNodeId()\n\t}\n\n\t/** Exposes the sync engine for integration tests (e.g. doc channel, chaos). */\n\tgetSyncEngine(): SyncEngine | null {\n\t\treturn this.syncEngine\n\t}\n\n\t/**\n\t * Operations the server rejected for this device that have not been reconciled.\n\t * Empty when the device has never connected.\n\t */\n\tasync getRejectedOperations(): Promise<import('@korajs/sync').RejectedOperation[]> {\n\t\tif (!this.syncEngine) return []\n\t\treturn this.syncEngine.getRejectedOperations()\n\t}\n\n\t/**\n\t * Stage a blob's bytes into this device's blob store, splitting them into\n\t * content-addressed chunks this device can then serve to peers over the sync\n\t * connection. Returns the manifest (blob hash + ordered chunk hashes) needed\n\t * to pull the blob elsewhere.\n\t */\n\tasync stageBlob(bytes: Uint8Array, options?: { chunkSize?: number }): Promise<BlobManifest> {\n\t\tconst { manifest } = await prepareBlobForSend(bytes, this.blobStore, options)\n\t\treturn manifest\n\t}\n\n\t/**\n\t * Store a blob for transfer the way `app.blobs.put` does: stage chunks, store\n\t * the full blob, and store the manifest as its own content-addressed object.\n\t * The returned reference carries a `manifestHash`, so a peer can pull the bytes\n\t * knowing only the reference.\n\t */\n\tasync putBlob(\n\t\tbytes: Uint8Array,\n\t\toptions?: { chunkSize?: number; mimeType?: string; filename?: string },\n\t): Promise<{ ref: BlobRef; manifest: BlobManifest }> {\n\t\treturn putBlobForTransfer(this.blobStore, bytes, options)\n\t}\n\n\t/**\n\t * Pull a blob's bytes over the live connection knowing only its reference: the\n\t * manifest is resolved by `ref.manifestHash` first, then the chunks are fetched.\n\t */\n\tasync pullBlobByRef(ref: BlobRef): Promise<ReceiveBlobResult> {\n\t\tif (!this.blobChunkProvider) {\n\t\t\tthrow new Error('Cannot pull a blob before the device has connected (call sync() first)')\n\t\t}\n\t\tconst manifest = await resolveBlobManifest(this.blobChunkProvider, ref)\n\t\treturn receiveBlob(manifest, this.blobChunkProvider, {\n\t\t\tchunkStore: this.blobStore,\n\t\t\tblobStore: this.blobStore,\n\t\t})\n\t}\n\n\t/**\n\t * Pull a blob's bytes from peers over the live sync connection. Requests only\n\t * the chunks this device is missing, reassembles them, and verifies integrity\n\t * against the manifest's blob hash before storing the full blob locally.\n\t */\n\tasync pullBlob(manifest: BlobManifest): Promise<ReceiveBlobResult> {\n\t\tif (!this.blobChunkProvider) {\n\t\t\tthrow new Error('Cannot pull a blob before the device has connected (call sync() first)')\n\t\t}\n\t\treturn receiveBlob(manifest, this.blobChunkProvider, {\n\t\t\tchunkStore: this.blobStore,\n\t\t\tblobStore: this.blobStore,\n\t\t})\n\t}\n\n\t/** Read fully-assembled blob bytes from this device's store, or null if absent. */\n\tasync getBlobBytes(hash: string): Promise<Uint8Array | null> {\n\t\treturn this.blobStore.get(hash)\n\t}\n\n\t/**\n\t * Get the device's version vector.\n\t */\n\tgetVersionVector(): VersionVector {\n\t\treturn this.store.getVersionVector()\n\t}\n\n\t/**\n\t * Check if the device is currently connected to the server.\n\t */\n\tisConnected(): boolean {\n\t\treturn this.currentTransport?.isConnected() ?? false\n\t}\n\n\t/**\n\t * Close the device, releasing all resources.\n\t */\n\tasync close(): Promise<void> {\n\t\tthis.closing = true\n\t\tif (this.unsubscribeAudit) {\n\t\t\tthis.unsubscribeAudit()\n\t\t\tthis.unsubscribeAudit = null\n\t\t}\n\t\tawait this.disconnect()\n\t\tawait this.store.close()\n\t\tthis.emitter.clear()\n\t}\n\n\t/**\n\t * Wait for in-flight sync operations to settle.\n\t * In-memory transports are near-synchronous, but apply/relay work is async.\n\t */\n\tprivate async waitForSettled(): Promise<void> {\n\t\tfor (let i = 0; i < 15; i++) {\n\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 20))\n\t\t}\n\t}\n\n\t/**\n\t * Wait for all pending outbound operations to be acknowledged.\n\t */\n\tprivate async waitForPendingOps(): Promise<void> {\n\t\tif (!this.syncEngine) return\n\t\tconst maxWait = 2000\n\t\tconst start = Date.now()\n\t\twhile (Date.now() - start < maxWait) {\n\t\t\tconst status = this.syncEngine.getStatus()\n\t\t\tif (status.pendingOperations === 0) return\n\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 10))\n\t\t}\n\t}\n}\n","import type { BlobRef, Operation, SchemaDefinition } from '@korajs/core'\nimport { MemoryServerStore } from '@korajs/server'\nimport { KoraSyncServer } from '@korajs/server'\nimport type { OperationValidator, ServerTransport } from '@korajs/server'\nimport { type ContentAddressedBlobStore, createMemoryServerBlobStore } from '@korajs/store'\n\n/**\n * In-memory test server wrapping KoraSyncServer with MemoryServerStore.\n * Handles client connections via memory transports.\n */\nexport interface TestServerOptions {\n\t/** Handshake schema version advertised by the server. Defaults to `schema.version`. */\n\tschemaVersion?: number\n\t/** Inclusive client schema versions accepted at handshake. */\n\tsupportedSchemaVersions?: { min: number; max: number }\n\t/** Enable central blob storage: the server persists and serves uploaded blob bytes. */\n\tblobStorage?: boolean\n\t/** Adjudicate untrusted client operations before materialization. */\n\tvalidateOperation?: OperationValidator\n}\n\nexport class TestServer {\n\treadonly store: MemoryServerStore\n\t/** The server's central blob store, present when `blobStorage` was enabled. */\n\treadonly blobStore: ContentAddressedBlobStore | null\n\tprivate readonly syncServer: KoraSyncServer\n\n\tconstructor(schema: SchemaDefinition, options?: TestServerOptions) {\n\t\tthis.store = new MemoryServerStore()\n\t\tconst schemaVersion = options?.schemaVersion ?? schema.version\n\t\tconst blob = options?.blobStorage ? createMemoryServerBlobStore() : null\n\t\tthis.blobStore = blob?.store ?? null\n\t\tthis.syncServer = new KoraSyncServer({\n\t\t\tstore: this.store,\n\t\t\tschemaVersion,\n\t\t\tsupportedSchemaVersions: options?.supportedSchemaVersions ?? {\n\t\t\t\tmin: schemaVersion,\n\t\t\t\tmax: schemaVersion,\n\t\t\t},\n\t\t\t...(blob ? blob.callbacks : {}),\n\t\t\t...(options?.validateOperation ? { validateOperation: options.validateOperation } : {}),\n\t\t})\n\t\tvoid this.store.setSchema(schema)\n\t}\n\n\t/**\n\t * Register a client connection transport with the server.\n\t * Returns the session ID assigned by the server.\n\t */\n\thandleConnection(transport: ServerTransport): string {\n\t\treturn this.syncServer.handleConnection(transport)\n\t}\n\n\t/**\n\t * Get all operations stored on the server.\n\t */\n\tgetAllOperations(): Operation[] {\n\t\treturn this.store.getAllOperations()\n\t}\n\n\t/**\n\t * Get the number of connected clients.\n\t */\n\tgetConnectionCount(): number {\n\t\treturn this.syncServer.getConnectionCount()\n\t}\n\n\t/**\n\t * Retransmit relay batches connected clients have not acknowledged. Lets a test\n\t * deterministically trigger the redelivery that the server also runs on a timer,\n\t * without depending on wall-clock time. `staleMs` of 0 resends every pending relay.\n\t */\n\tretransmitPendingRelays(staleMs = 0): void {\n\t\tthis.syncServer.retransmitPendingRelays(staleMs)\n\t}\n\n\t/** Every blob reference still reachable from live records on the server. */\n\tgetLiveBlobRefs(): Promise<BlobRef[]> {\n\t\treturn this.syncServer.getLiveBlobRefs()\n\t}\n\n\t/**\n\t * Shut down the server and close all sessions.\n\t */\n\tasync close(): Promise<void> {\n\t\tawait this.syncServer.stop()\n\t\tawait this.store.close()\n\t}\n}\n","import type { SchemaDefinition } from '@korajs/core'\nimport type { TestDevice } from './test-device'\n\n/**\n * Result of a convergence check.\n */\nexport interface ConvergenceResult {\n\t/** Whether all devices have converged to the same state */\n\tconverged: boolean\n\t/** Per-collection comparison details (only populated on failure) */\n\tdifferences: CollectionDifference[]\n}\n\n/**\n * Describes a difference in a collection between devices.\n */\nexport interface CollectionDifference {\n\tcollection: string\n\tdeviceA: string\n\tdeviceB: string\n\t/** Records present in deviceA but not deviceB */\n\tmissingInB: string[]\n\t/** Records present in deviceB but not deviceA */\n\tmissingInA: string[]\n\t/** Records present in both but with different values */\n\tfieldDifferences: FieldDifference[]\n}\n\n/**\n * A specific field-level difference between two devices.\n */\nexport interface FieldDifference {\n\trecordId: string\n\tfield: string\n\tvalueInA: unknown\n\tvalueInB: unknown\n}\n\n/**\n * Assert that all devices have converged to identical collection states.\n *\n * Compares every collection across all device pairs. Throws an error\n * with detailed diagnostics if any differences are found.\n *\n * @param devices - The devices to check for convergence\n * @param schema - The schema (used to enumerate collections)\n *\n * @example\n * ```typescript\n * await deviceA.sync()\n * await deviceB.sync()\n * await expectConverged([deviceA, deviceB], schema)\n * ```\n */\nexport async function expectConverged(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n): Promise<void> {\n\tif (devices.length < 2) return\n\n\tconst result = await checkConvergence(devices, schema)\n\tif (!result.converged) {\n\t\tconst details = result.differences\n\t\t\t.map((d) => {\n\t\t\t\tconst parts: string[] = [\n\t\t\t\t\t` Collection \"${d.collection}\" differs between ${d.deviceA} and ${d.deviceB}:`,\n\t\t\t\t]\n\t\t\t\tif (d.missingInB.length > 0) {\n\t\t\t\t\tparts.push(` Missing in ${d.deviceB}: ${d.missingInB.join(', ')}`)\n\t\t\t\t}\n\t\t\t\tif (d.missingInA.length > 0) {\n\t\t\t\t\tparts.push(` Missing in ${d.deviceA}: ${d.missingInA.join(', ')}`)\n\t\t\t\t}\n\t\t\t\tfor (const fd of d.fieldDifferences) {\n\t\t\t\t\tparts.push(\n\t\t\t\t\t\t` Record \"${fd.recordId}\" field \"${fd.field}\": ${JSON.stringify(fd.valueInA)} vs ${JSON.stringify(fd.valueInB)}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn parts.join('\\n')\n\t\t\t})\n\t\t\t.join('\\n')\n\n\t\tthrow new Error(`Devices have not converged:\\n${details}`)\n\t}\n}\n\n/**\n * Poll until all devices converge or timeout. Use after sync in integration tests\n * where relay/apply work may still be in flight under parallel CI load.\n */\nexport async function expectConvergedEventually(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n\toptions?: { timeoutMs?: number; intervalMs?: number },\n): Promise<void> {\n\tif (devices.length < 2) return\n\n\tconst timeoutMs = options?.timeoutMs ?? 5000\n\tconst intervalMs = options?.intervalMs ?? 25\n\tconst deadline = Date.now() + timeoutMs\n\n\twhile (Date.now() < deadline) {\n\t\tconst result = await checkConvergence(devices, schema)\n\t\tif (result.converged) {\n\t\t\treturn\n\t\t}\n\t\tawait new Promise<void>((resolve) => setTimeout(resolve, intervalMs))\n\t}\n\n\tawait expectConverged(devices, schema)\n}\n\n/**\n * Check whether all devices have converged without throwing.\n *\n * @param devices - The devices to check\n * @param schema - The schema (for collection enumeration)\n * @returns Convergence result with details\n */\nexport async function checkConvergence(\n\tdevices: TestDevice[],\n\tschema: SchemaDefinition,\n): Promise<ConvergenceResult> {\n\tconst differences: CollectionDifference[] = []\n\tconst collectionNames = Object.keys(schema.collections)\n\n\tfor (let i = 0; i < devices.length - 1; i++) {\n\t\tfor (let j = i + 1; j < devices.length; j++) {\n\t\t\tconst deviceA = devices[i] as TestDevice\n\t\t\tconst deviceB = devices[j] as TestDevice\n\n\t\t\tfor (const collection of collectionNames) {\n\t\t\t\tconst stateA = await deviceA.getState(collection)\n\t\t\t\tconst stateB = await deviceB.getState(collection)\n\n\t\t\t\tconst diff = compareCollectionStates(collection, deviceA.name, deviceB.name, stateA, stateB)\n\n\t\t\t\tif (diff) {\n\t\t\t\t\tdifferences.push(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tconverged: differences.length === 0,\n\t\tdifferences,\n\t}\n}\n\n/**\n * Compare two collection states and return differences if any.\n */\nfunction compareCollectionStates(\n\tcollection: string,\n\tnameA: string,\n\tnameB: string,\n\tstateA: Record<string, unknown>[],\n\tstateB: Record<string, unknown>[],\n): CollectionDifference | null {\n\tconst mapA = new Map(stateA.map((r) => [r.id as string, r]))\n\tconst mapB = new Map(stateB.map((r) => [r.id as string, r]))\n\n\tconst missingInB: string[] = []\n\tconst missingInA: string[] = []\n\tconst fieldDifferences: FieldDifference[] = []\n\n\t// Check records in A\n\tfor (const [id, recordA] of mapA) {\n\t\tconst recordB = mapB.get(id)\n\t\tif (!recordB) {\n\t\t\tmissingInB.push(id)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Compare fields (skip internal fields like _created_at, _updated_at)\n\t\tconst allFields = new Set([\n\t\t\t...Object.keys(recordA).filter((k) => !k.startsWith('_')),\n\t\t\t...Object.keys(recordB).filter((k) => !k.startsWith('_')),\n\t\t])\n\n\t\tfor (const field of allFields) {\n\t\t\tif (field === 'id') continue\n\t\t\tconst valA = recordA[field]\n\t\t\tconst valB = recordB[field]\n\t\t\tif (!deepEqual(valA, valB)) {\n\t\t\t\tfieldDifferences.push({\n\t\t\t\t\trecordId: id,\n\t\t\t\t\tfield,\n\t\t\t\t\tvalueInA: valA,\n\t\t\t\t\tvalueInB: valB,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check records only in B\n\tfor (const id of mapB.keys()) {\n\t\tif (!mapA.has(id)) {\n\t\t\tmissingInA.push(id)\n\t\t}\n\t}\n\n\tif (missingInB.length === 0 && missingInA.length === 0 && fieldDifferences.length === 0) {\n\t\treturn null\n\t}\n\n\treturn {\n\t\tcollection,\n\t\tdeviceA: nameA,\n\t\tdeviceB: nameB,\n\t\tmissingInB,\n\t\tmissingInA,\n\t\tfieldDifferences,\n\t}\n}\n\n/**\n * Deep equality check for comparing field values.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n\tif (a === b) return true\n\tif (a === null || b === null) return false\n\tif (a === undefined || b === undefined) return false\n\tif (typeof a !== typeof b) return false\n\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\tif (a.length !== b.length) return false\n\t\treturn a.every((val, i) => deepEqual(val, b[i]))\n\t}\n\n\tif (typeof a === 'object' && typeof b === 'object') {\n\t\tconst keysA = Object.keys(a as Record<string, unknown>)\n\t\tconst keysB = Object.keys(b as Record<string, unknown>)\n\t\tif (keysA.length !== keysB.length) return false\n\t\treturn keysA.every((key) =>\n\t\t\tdeepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n\t\t)\n\t}\n\n\treturn false\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { MessageSerializer, SyncMessage, SyncTransport, TransportOptions } from '@korajs/sync'\nimport { ProtobufMessageSerializer } from '@korajs/sync'\n\n/**\n * A linked client/server transport pair, as consumed by {@link TestDevice}.\n */\nexport interface TransportPair {\n\tclient: SyncTransport\n\tserverTransport: ServerTransport\n}\n\n/**\n * Message types the protobuf serializer models. Ephemeral messages\n * (awareness-update, yjs-doc-update) are JSON-only in the protocol, so they are\n * deep-copied instead of forced through the protobuf envelope.\n */\nconst PROTOBUF_WIRE_TYPES: ReadonlySet<SyncMessage['type']> = new Set([\n\t'handshake',\n\t'handshake-response',\n\t'operation-batch',\n\t'acknowledgment',\n\t'error',\n])\n\n/**\n * Round-trips a message through the real protobuf wire codec: object → protobuf\n * bytes → object. This is what makes a transport pair exercise the true wire\n * format rather than passing live object references between the two ends.\n */\nfunction roundTripThroughProtobuf(\n\tmessage: SyncMessage,\n\tserializer: MessageSerializer,\n): SyncMessage {\n\tif (!PROTOBUF_WIRE_TYPES.has(message.type)) {\n\t\t// Not part of the protobuf schema — still copy so no live reference leaks\n\t\t// across the \"wire\".\n\t\treturn structuredClone(message)\n\t}\n\n\tconst encoded = serializer.encode(message)\n\tif (!(encoded instanceof Uint8Array)) {\n\t\tthrow new Error('Expected the protobuf serializer to encode to bytes')\n\t}\n\treturn serializer.decode(encoded)\n}\n\n/**\n * Wraps a client transport so every outbound message crosses the protobuf wire.\n */\nclass ProtobufWireClientTransport implements SyncTransport {\n\tconstructor(\n\t\tprivate readonly inner: SyncTransport,\n\t\tprivate readonly serializer: MessageSerializer,\n\t) {}\n\n\tconnect(url: string, options?: TransportOptions): Promise<void> {\n\t\treturn this.inner.connect(url, options)\n\t}\n\n\tdisconnect(): Promise<void> {\n\t\treturn this.inner.disconnect()\n\t}\n\n\tsend(message: SyncMessage): void {\n\t\tthis.inner.send(roundTripThroughProtobuf(message, this.serializer))\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n}\n\n/**\n * Wraps a server transport so every outbound message crosses the protobuf wire.\n */\nclass ProtobufWireServerTransport implements ServerTransport {\n\tconstructor(\n\t\tprivate readonly inner: ServerTransport,\n\t\tprivate readonly serializer: MessageSerializer,\n\t) {}\n\n\tsend(message: SyncMessage): void {\n\t\tthis.inner.send(roundTripThroughProtobuf(message, this.serializer))\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (code: number, reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.inner.close(code, reason)\n\t}\n}\n\n/**\n * Wraps a transport pair so that every sync message exchanged between the two\n * ends is encoded to protobuf bytes and decoded back — a faithful stand-in for\n * a real network hop that uses the protobuf wire format.\n *\n * Both directions are wrapped, so operations, handshakes, batches, and acks all\n * travel as bytes rather than shared object references.\n *\n * @param pair - The underlying (in-memory) transport pair to wrap\n * @returns A transport pair that routes messages through the protobuf codec\n */\nexport function wrapTransportPairWithProtobufWire(pair: TransportPair): TransportPair {\n\tconst serializer = new ProtobufMessageSerializer()\n\treturn {\n\t\tclient: new ProtobufWireClientTransport(pair.client, serializer),\n\t\tserverTransport: new ProtobufWireServerTransport(pair.serverTransport, serializer),\n\t}\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { SyncMessage } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\n\n/**\n * Wraps a server transport so its handshake responses advertise a caller-chosen\n * wall-clock time, independent of the process `Date.now()`.\n *\n * The sync engine measures clock skew as `serverTime - Date.now()` at handshake.\n * To simulate a device whose clock is fast while the server's clock is correct —\n * both running in one test process — the client mocks `Date.now()` and this\n * wrapper injects the server's true time into the handshake response, so the\n * skew the engine sees is real.\n */\nclass ServerClockServerTransport implements ServerTransport {\n\tconstructor(\n\t\tprivate readonly inner: ServerTransport,\n\t\tprivate readonly serverNow: () => number,\n\t) {}\n\n\tsend(message: SyncMessage): void {\n\t\tif (message.type === 'handshake-response') {\n\t\t\tthis.inner.send({ ...message, serverTime: this.serverNow() })\n\t\t\treturn\n\t\t}\n\t\tthis.inner.send(message)\n\t}\n\n\tonMessage(handler: (message: SyncMessage) => void): void {\n\t\tthis.inner.onMessage(handler)\n\t}\n\n\tonClose(handler: (code: number, reason: string) => void): void {\n\t\tthis.inner.onClose(handler)\n\t}\n\n\tonError(handler: (error: Error) => void): void {\n\t\tthis.inner.onError(handler)\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.inner.close(code, reason)\n\t}\n}\n\n/**\n * Wraps a transport pair so the server side reports `serverNow()` as its\n * handshake wall-clock time.\n *\n * @param pair - The underlying transport pair\n * @param serverNow - Returns the server's true current time (ms since epoch)\n * @returns A transport pair whose handshake responses carry the injected time\n */\nexport function wrapTransportPairWithServerClock(\n\tpair: TransportPair,\n\tserverNow: () => number,\n): TransportPair {\n\treturn {\n\t\tclient: pair.client,\n\t\tserverTransport: new ServerClockServerTransport(pair.serverTransport, serverNow),\n\t}\n}\n","// @korajs/test — testing harness for Kora.js\n// Creates virtual device networks for testing sync convergence and conflicts.\n\n// === Factory ===\nexport { createTestNetwork, createMixedTestNetwork } from './test-network'\nexport type {\n\tTestNetwork,\n\tTestNetworkOptions,\n\tMixedTestDeviceConfig,\n} from './test-network'\n\n// === Device ===\nexport { TestDevice } from './test-device'\nexport type { TestDeviceOptions } from './test-device'\n\n// === Server ===\nexport { TestServer } from './test-server'\nexport type { TestServerOptions } from './test-server'\n\n// === Assertions ===\nexport { checkConvergence, expectConverged, expectConvergedEventually } from './assertions'\nexport type {\n\tCollectionDifference,\n\tConvergenceResult,\n\tFieldDifference,\n} from './assertions'\n\n// === Protobuf wire transport (for testing convergence through the real wire format) ===\nexport { wrapTransportPairWithProtobufWire } from './protobuf-wire-transport'\nexport type { TransportPair } from './protobuf-wire-transport'\n\n// === Server-clock transport (for testing clock-skew / rebase integration) ===\nexport { wrapTransportPairWithServerClock } from './server-clock-transport'\n\n// === Re-export ChaosTransport for convenience ===\nexport { ChaosTransport } from '@korajs/sync'\nexport type { ChaosConfig } from '@korajs/sync'\n"],"mappings":"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","names":["ChaosTransport"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@korajs/test",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"description": "Testing harness for Kora.js — create virtual device networks and assert convergence",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"better-sqlite3": "^12.8.0",
|
|
27
|
-
"@korajs/core": "1.0.0-beta.
|
|
28
|
-
"@korajs/store": "1.0.0-beta.
|
|
29
|
-
"@korajs/merge": "1.0.0-beta.
|
|
30
|
-
"@korajs/sync": "1.0.0-beta.
|
|
31
|
-
"@korajs/server": "1.0.0-beta.
|
|
32
|
-
"korajs": "1.0.0-beta.
|
|
27
|
+
"@korajs/core": "1.0.0-beta.5",
|
|
28
|
+
"@korajs/store": "1.0.0-beta.5",
|
|
29
|
+
"@korajs/merge": "1.0.0-beta.5",
|
|
30
|
+
"@korajs/sync": "1.0.0-beta.5",
|
|
31
|
+
"@korajs/server": "1.0.0-beta.5",
|
|
32
|
+
"korajs": "1.0.0-beta.5"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"korajs": "1.0.0-beta.
|
|
35
|
+
"korajs": "1.0.0-beta.5"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"korajs": {
|