@korajs/test 1.0.0-beta.12 → 1.0.0-beta.13
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 +137 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +155 -77
- package/dist/index.d.ts +155 -77
- package/dist/index.js +138 -11
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -59,6 +59,63 @@ var import_store = require("@korajs/store");
|
|
|
59
59
|
var import_better_sqlite3 = require("@korajs/store/better-sqlite3");
|
|
60
60
|
var import_sync = require("@korajs/sync");
|
|
61
61
|
var import_testing = require("korajs/testing");
|
|
62
|
+
var ReconnectingClientTransport = class {
|
|
63
|
+
constructor(inner, createPair, server) {
|
|
64
|
+
this.inner = inner;
|
|
65
|
+
this.createPair = createPair;
|
|
66
|
+
this.server = server;
|
|
67
|
+
}
|
|
68
|
+
inner;
|
|
69
|
+
createPair;
|
|
70
|
+
server;
|
|
71
|
+
messageHandler = null;
|
|
72
|
+
closeHandler = null;
|
|
73
|
+
errorHandler = null;
|
|
74
|
+
connectedOnce = false;
|
|
75
|
+
async connect(url, options) {
|
|
76
|
+
if (this.connectedOnce && !this.inner.isConnected()) {
|
|
77
|
+
const previous = this.inner;
|
|
78
|
+
previous.onMessage(() => {
|
|
79
|
+
});
|
|
80
|
+
previous.onClose(() => {
|
|
81
|
+
});
|
|
82
|
+
previous.onError(() => {
|
|
83
|
+
});
|
|
84
|
+
const { client, serverTransport } = this.createPair();
|
|
85
|
+
this.inner = client;
|
|
86
|
+
this.wire();
|
|
87
|
+
this.server.handleConnection(serverTransport);
|
|
88
|
+
}
|
|
89
|
+
this.connectedOnce = true;
|
|
90
|
+
await this.inner.connect(url, options);
|
|
91
|
+
}
|
|
92
|
+
disconnect() {
|
|
93
|
+
return this.inner.disconnect();
|
|
94
|
+
}
|
|
95
|
+
send(message) {
|
|
96
|
+
this.inner.send(message);
|
|
97
|
+
}
|
|
98
|
+
onMessage(handler) {
|
|
99
|
+
this.messageHandler = handler;
|
|
100
|
+
this.wire();
|
|
101
|
+
}
|
|
102
|
+
onClose(handler) {
|
|
103
|
+
this.closeHandler = handler;
|
|
104
|
+
this.wire();
|
|
105
|
+
}
|
|
106
|
+
onError(handler) {
|
|
107
|
+
this.errorHandler = handler;
|
|
108
|
+
this.wire();
|
|
109
|
+
}
|
|
110
|
+
isConnected() {
|
|
111
|
+
return this.inner.isConnected();
|
|
112
|
+
}
|
|
113
|
+
wire() {
|
|
114
|
+
this.inner.onMessage((message) => this.messageHandler?.(message));
|
|
115
|
+
this.inner.onClose((reason) => this.closeHandler?.(reason));
|
|
116
|
+
this.inner.onError((error) => this.errorHandler?.(error));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
62
119
|
var TestDevice = class {
|
|
63
120
|
name;
|
|
64
121
|
store;
|
|
@@ -67,12 +124,18 @@ var TestDevice = class {
|
|
|
67
124
|
blobStore = new import_store.MemoryBlobStore();
|
|
68
125
|
schema;
|
|
69
126
|
server;
|
|
127
|
+
encryption;
|
|
128
|
+
batchSize;
|
|
129
|
+
encryptor = null;
|
|
70
130
|
mergeEngine;
|
|
71
131
|
createTransportPair;
|
|
72
132
|
adapter;
|
|
73
133
|
dbPath;
|
|
74
134
|
syncSchemaVersion;
|
|
75
135
|
operationTransforms;
|
|
136
|
+
scopeExit;
|
|
137
|
+
principal;
|
|
138
|
+
reconnectable;
|
|
76
139
|
applyPipeline = null;
|
|
77
140
|
syncEngine = null;
|
|
78
141
|
blobChunkProvider = null;
|
|
@@ -88,13 +151,22 @@ var TestDevice = class {
|
|
|
88
151
|
this.dbPath = `${options.tmpDir}/test-device-${options.name}.db`;
|
|
89
152
|
this.syncSchemaVersion = options.syncSchemaVersion ?? options.schema.version;
|
|
90
153
|
this.operationTransforms = options.operationTransforms ?? [];
|
|
154
|
+
this.scopeExit = options.scopeExit;
|
|
155
|
+
this.principal = options.principal;
|
|
156
|
+
this.reconnectable = options.reconnectable === true;
|
|
157
|
+
this.encryption = options.encryption;
|
|
158
|
+
this.batchSize = options.batchSize;
|
|
159
|
+
(0, import_sync.validateEncryptedRelations)(options.schema, options.encryption?.config);
|
|
91
160
|
this.emitter = new import_internal.SimpleEventEmitter();
|
|
92
161
|
this.mergeEngine = new import_merge.MergeEngine();
|
|
93
162
|
this.adapter = new import_better_sqlite3.BetterSqlite3Adapter(this.dbPath);
|
|
94
163
|
this.store = new import_store.Store({
|
|
95
164
|
schema: options.schema,
|
|
96
165
|
adapter: this.adapter,
|
|
97
|
-
emitter: this.emitter
|
|
166
|
+
emitter: this.emitter,
|
|
167
|
+
materialization: options.legacyMerge === true ? "legacy" : "fold",
|
|
168
|
+
...options.operationTransforms && options.operationTransforms.length > 0 ? { operationTransforms: options.operationTransforms } : {},
|
|
169
|
+
...options.maxOperationBytes !== void 0 ? { maxOperationBytes: options.maxOperationBytes } : {}
|
|
98
170
|
});
|
|
99
171
|
}
|
|
100
172
|
/**
|
|
@@ -109,6 +181,20 @@ var TestDevice = class {
|
|
|
109
181
|
});
|
|
110
182
|
this.store.setLocalMutationHandler(this.applyPipeline);
|
|
111
183
|
this.unsubscribeAudit = (0, import_testing.wireAuditPersistence)(this.store, this.emitter);
|
|
184
|
+
const principal = this.principal?.();
|
|
185
|
+
if (principal) await this.store.bindPrincipal(principal);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The signed-in user changed (RT-42), as an auth binding reports it: like createApp,
|
|
189
|
+
* end the live session and bind the store to the new user before the next write.
|
|
190
|
+
*/
|
|
191
|
+
async authChanged() {
|
|
192
|
+
if (this.syncEngine) {
|
|
193
|
+
await this.syncEngine.refreshPrincipal();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const principal = this.principal?.();
|
|
197
|
+
if (principal) await this.store.bindPrincipal(principal);
|
|
112
198
|
}
|
|
113
199
|
/**
|
|
114
200
|
* Connect to the test server and perform initial sync.
|
|
@@ -120,8 +206,17 @@ var TestDevice = class {
|
|
|
120
206
|
await this.waitForSettled();
|
|
121
207
|
return;
|
|
122
208
|
}
|
|
123
|
-
const
|
|
209
|
+
const created = this.createTransportPair();
|
|
210
|
+
const { serverTransport } = created;
|
|
211
|
+
const client = this.reconnectable ? new ReconnectingClientTransport(created.client, this.createTransportPair, this.server) : created.client;
|
|
124
212
|
this.currentTransport = client;
|
|
213
|
+
if (this.encryption && !this.encryptor) {
|
|
214
|
+
this.encryptor = await import_sync.SyncEncryptor.create(
|
|
215
|
+
this.encryption.config,
|
|
216
|
+
this.encryption.salt,
|
|
217
|
+
this.encryption.iterations
|
|
218
|
+
);
|
|
219
|
+
}
|
|
125
220
|
const conflictHandler = {};
|
|
126
221
|
const syncStore = new import_testing.MergeAwareSyncStore(this.store, this.mergeEngine, this.emitter, {
|
|
127
222
|
onMergeConflict: () => conflictHandler.fn?.()
|
|
@@ -134,9 +229,14 @@ var TestDevice = class {
|
|
|
134
229
|
config: {
|
|
135
230
|
url: "ws://test-network",
|
|
136
231
|
schemaVersion: this.syncSchemaVersion,
|
|
137
|
-
operationTransforms: this.operationTransforms.length > 0 ? this.operationTransforms : void 0
|
|
232
|
+
operationTransforms: this.operationTransforms.length > 0 ? this.operationTransforms : void 0,
|
|
233
|
+
...this.scopeExit ? { scopeExit: this.scopeExit } : {},
|
|
234
|
+
...this.principal ? { principal: async () => this.principal ? this.principal() : null } : {},
|
|
235
|
+
...this.encryption ? { encryption: this.encryption.config } : {},
|
|
236
|
+
...this.batchSize !== void 0 ? { batchSize: this.batchSize } : {}
|
|
138
237
|
},
|
|
139
|
-
emitter: this.emitter
|
|
238
|
+
emitter: this.emitter,
|
|
239
|
+
...this.encryptor ? { encryptor: this.encryptor } : {}
|
|
140
240
|
});
|
|
141
241
|
conflictHandler.fn = () => this.syncEngine?.recordConflict();
|
|
142
242
|
const chunkPort = (0, import_testing.createSyncEngineChunkPort)(this.syncEngine);
|
|
@@ -312,11 +412,13 @@ var import_server2 = require("@korajs/server");
|
|
|
312
412
|
var import_store2 = require("@korajs/store");
|
|
313
413
|
var TestServer = class {
|
|
314
414
|
store;
|
|
415
|
+
/** Resolves once the store has the schema (needed before a SQL store is used). */
|
|
416
|
+
ready;
|
|
315
417
|
/** The server's central blob store, present when `blobStorage` was enabled. */
|
|
316
418
|
blobStore;
|
|
317
419
|
syncServer;
|
|
318
420
|
constructor(schema, options) {
|
|
319
|
-
this.store = new import_server.MemoryServerStore();
|
|
421
|
+
this.store = options?.store ?? new import_server.MemoryServerStore();
|
|
320
422
|
const schemaVersion = options?.schemaVersion ?? schema.version;
|
|
321
423
|
const blob = options?.blobStorage ? (0, import_store2.createMemoryServerBlobStore)() : null;
|
|
322
424
|
this.blobStore = blob?.store ?? null;
|
|
@@ -329,9 +431,15 @@ var TestServer = class {
|
|
|
329
431
|
},
|
|
330
432
|
...options?.operationTransforms ? { operationTransforms: options.operationTransforms } : {},
|
|
331
433
|
...blob ? blob.callbacks : {},
|
|
332
|
-
...options?.validateOperation ? { validateOperation: options.validateOperation } : {}
|
|
434
|
+
...options?.validateOperation ? { validateOperation: options.validateOperation } : {},
|
|
435
|
+
...options?.encryption ? { encryption: options.encryption } : {}
|
|
436
|
+
});
|
|
437
|
+
this.ready = this.store.setSchema(
|
|
438
|
+
schema,
|
|
439
|
+
options?.operationTransforms ? { operationTransforms: options.operationTransforms } : {}
|
|
440
|
+
);
|
|
441
|
+
this.ready.catch(() => {
|
|
333
442
|
});
|
|
334
|
-
void this.store.setSchema(schema);
|
|
335
443
|
}
|
|
336
444
|
/**
|
|
337
445
|
* Register a client connection transport with the server.
|
|
@@ -344,8 +452,21 @@ var TestServer = class {
|
|
|
344
452
|
* Get all operations stored on the server.
|
|
345
453
|
*/
|
|
346
454
|
getAllOperations() {
|
|
455
|
+
if (!(this.store instanceof import_server.MemoryServerStore)) {
|
|
456
|
+
throw new Error(
|
|
457
|
+
"TestServer.getAllOperations() reads the memory store synchronously; use store.getOperationsAfterDelivery() with a SQL store"
|
|
458
|
+
);
|
|
459
|
+
}
|
|
347
460
|
return this.store.getAllOperations();
|
|
348
461
|
}
|
|
462
|
+
/** The trusted server-side write API (route context): writes authored by the server node. */
|
|
463
|
+
getKoraContext() {
|
|
464
|
+
return this.syncServer.getKoraContext();
|
|
465
|
+
}
|
|
466
|
+
/** The node ids the server's stores fold with and advertise in the handshake. */
|
|
467
|
+
get authoritativeNodeIds() {
|
|
468
|
+
return this.syncServer.authoritativeNodeIds;
|
|
469
|
+
}
|
|
349
470
|
/**
|
|
350
471
|
* Get the number of connected clients.
|
|
351
472
|
*/
|
|
@@ -380,8 +501,11 @@ async function createTestNetwork(schema, options) {
|
|
|
380
501
|
const tmpDir = (0, import_node_fs.mkdtempSync)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "kora-test-"));
|
|
381
502
|
const server = new TestServer(schema, {
|
|
382
503
|
blobStorage: options?.blobStorage,
|
|
383
|
-
validateOperation: options?.validateOperation
|
|
504
|
+
validateOperation: options?.validateOperation,
|
|
505
|
+
...options?.serverStore ? { store: options.serverStore } : {},
|
|
506
|
+
...options?.serverEncryption ? { encryption: options.serverEncryption } : {}
|
|
384
507
|
});
|
|
508
|
+
await server.ready;
|
|
385
509
|
const devices = [];
|
|
386
510
|
for (const name of deviceNames) {
|
|
387
511
|
const device = new TestDevice({
|
|
@@ -398,7 +522,10 @@ async function createTestNetwork(schema, options) {
|
|
|
398
522
|
};
|
|
399
523
|
return options?.wrapTransport ? options.wrapTransport(base) : base;
|
|
400
524
|
},
|
|
401
|
-
tmpDir
|
|
525
|
+
tmpDir,
|
|
526
|
+
...options?.legacyMerge ? { legacyMerge: true } : {},
|
|
527
|
+
...options?.encryption ? { encryption: options.encryption } : {},
|
|
528
|
+
...options?.deviceMaxOperationBytes !== void 0 ? { maxOperationBytes: options.deviceMaxOperationBytes } : {}
|
|
402
529
|
});
|
|
403
530
|
await device.open();
|
|
404
531
|
devices.push(device);
|
|
@@ -733,7 +860,7 @@ function createTestAuthBinding(options) {
|
|
|
733
860
|
get state() {
|
|
734
861
|
return state;
|
|
735
862
|
},
|
|
736
|
-
auth: async () => ({ token: state.state === "authenticated" ? state.token : "" }),
|
|
863
|
+
auth: async () => ({ token: state.state === "authenticated" ? state.token ?? "" : "" }),
|
|
737
864
|
resolveSyncState: async () => state,
|
|
738
865
|
resolveUserId: async () => state.state === "authenticated" ? state.userId : void 0,
|
|
739
866
|
resolveNodeId: async () => state.state === "authenticated" ? "test-device" : void 0,
|
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","../src/test-auth.ts","../src/transport-metrics.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\n// === Auth lifecycle and transfer-cost instrumentation ===\nexport { createTestAuthBinding } from './test-auth'\nexport type { TestAuthBinding } from './test-auth'\nexport { createTransportMetrics } from './transport-metrics'\nexport type { TestTransferMetrics } from './transport-metrics'\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, OperationTransform, 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/** Transform accepted legacy operations into the server schema before validation. */\n\toperationTransforms?: OperationTransform[]\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...(options?.operationTransforms ? { operationTransforms: options.operationTransforms } : {}),\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","import type { AuthSyncBinding, AuthSyncState } from '@korajs/core/bindings'\n\n/** Controllable auth binding for lifecycle and authenticated-only sync tests. */\nexport interface TestAuthBinding extends AuthSyncBinding {\n\treadonly state: AuthSyncState\n\tsetLoading(): void\n\tsignIn(userId: string, token?: string): void\n\trefreshToken(token?: string): void\n\tsignOut(): void\n}\n\nfunction testJwt(userId: string, generation: number): string {\n\tconst encode = (value: unknown): string =>\n\t\tBuffer.from(JSON.stringify(value)).toString('base64url')\n\treturn `${encode({ alg: 'none', typ: 'JWT' })}.${encode({ sub: userId, dev: 'test-device', generation })}.test`\n}\n\n/**\n * Create a deterministic auth binding that can transition between loading,\n * signed-out, and multiple authenticated users without a real auth server.\n */\nexport function createTestAuthBinding(options?: {\n\tinitialUserId?: string\n\tanonymous?: boolean\n}): TestAuthBinding {\n\tlet generation = 0\n\tlet state: AuthSyncState = options?.initialUserId\n\t\t? {\n\t\t\t\tstate: 'authenticated',\n\t\t\t\tuserId: options.initialUserId,\n\t\t\t\ttoken: testJwt(options.initialUserId, generation),\n\t\t\t}\n\t\t: { state: 'loading' }\n\tconst listeners = new Set<() => void>()\n\tconst notify = (): void => {\n\t\tfor (const listener of listeners) listener()\n\t}\n\n\treturn {\n\t\tget state() {\n\t\t\treturn state\n\t\t},\n\t\tauth: async () => ({ token: state.state === 'authenticated' ? state.token : '' }),\n\t\tresolveSyncState: async () => state,\n\t\tresolveUserId: async () => (state.state === 'authenticated' ? state.userId : undefined),\n\t\tresolveNodeId: async () => (state.state === 'authenticated' ? 'test-device' : undefined),\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener)\n\t\t\treturn () => listeners.delete(listener)\n\t\t},\n\t\tsetLoading() {\n\t\t\tstate = { state: 'loading' }\n\t\t\tnotify()\n\t\t},\n\t\tsignIn(userId, token) {\n\t\t\tgeneration++\n\t\t\tstate = { state: 'authenticated', userId, token: token ?? testJwt(userId, generation) }\n\t\t\tnotify()\n\t\t},\n\t\trefreshToken(token) {\n\t\t\tif (state.state !== 'authenticated') throw new Error('Cannot refresh while signed out')\n\t\t\tgeneration++\n\t\t\tstate = {\n\t\t\t\t...state,\n\t\t\t\ttoken: token ?? testJwt(state.userId, generation),\n\t\t\t}\n\t\t\tnotify()\n\t\t},\n\t\tsignOut() {\n\t\t\tstate = options?.anonymous\n\t\t\t\t? { state: 'anonymous', mayConnectAnonymously: true }\n\t\t\t\t: { state: 'signed-out', mayConnectAnonymously: false }\n\t\t\tnotify()\n\t\t},\n\t}\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { SyncMessage, SyncTransport } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\n\nexport interface TestTransferMetrics {\n\tconnectionAttempts: number\n\tdisconnections: number\n\tclientMessages: number\n\tserverMessages: number\n\tclientBytes: number\n\tserverBytes: number\n\treset(): void\n}\n\nfunction wireBytes(message: SyncMessage): number {\n\treturn new TextEncoder().encode(JSON.stringify(message)).byteLength\n}\n\n/** Create a transport wrapper and counters for asserting reconnect and transfer cost. */\nexport function createTransportMetrics(): {\n\tmetrics: TestTransferMetrics\n\twrapTransport(pair: TransportPair): TransportPair\n} {\n\tconst metrics: TestTransferMetrics = {\n\t\tconnectionAttempts: 0,\n\t\tdisconnections: 0,\n\t\tclientMessages: 0,\n\t\tserverMessages: 0,\n\t\tclientBytes: 0,\n\t\tserverBytes: 0,\n\t\treset() {\n\t\t\tthis.connectionAttempts = 0\n\t\t\tthis.disconnections = 0\n\t\t\tthis.clientMessages = 0\n\t\t\tthis.serverMessages = 0\n\t\t\tthis.clientBytes = 0\n\t\t\tthis.serverBytes = 0\n\t\t},\n\t}\n\n\treturn {\n\t\tmetrics,\n\t\twrapTransport(pair) {\n\t\t\tconst client: SyncTransport = {\n\t\t\t\t...pair.client,\n\t\t\t\tasync connect(url, options) {\n\t\t\t\t\tmetrics.connectionAttempts++\n\t\t\t\t\tawait pair.client.connect(url, options)\n\t\t\t\t},\n\t\t\t\tasync disconnect() {\n\t\t\t\t\tmetrics.disconnections++\n\t\t\t\t\tawait pair.client.disconnect()\n\t\t\t\t},\n\t\t\t\tsend(message) {\n\t\t\t\t\tmetrics.clientMessages++\n\t\t\t\t\tmetrics.clientBytes += wireBytes(message)\n\t\t\t\t\tpair.client.send(message)\n\t\t\t\t},\n\t\t\t\tonMessage: (handler) => pair.client.onMessage(handler),\n\t\t\t\tonClose: (handler) => pair.client.onClose(handler),\n\t\t\t\tonError: (handler) => pair.client.onError(handler),\n\t\t\t\tisConnected: () => pair.client.isConnected(),\n\t\t\t}\n\t\t\tconst serverTransport: ServerTransport = {\n\t\t\t\t...pair.serverTransport,\n\t\t\t\tsend(message) {\n\t\t\t\t\tmetrics.serverMessages++\n\t\t\t\t\tmetrics.serverBytes += wireBytes(message)\n\t\t\t\t\tpair.serverTransport.send(message)\n\t\t\t\t},\n\t\t\t\tonMessage: (handler) => pair.serverTransport.onMessage(handler),\n\t\t\t\tonClose: (handler) => pair.serverTransport.onClose(handler),\n\t\t\t\tonError: (handler) => pair.serverTransport.onError(handler),\n\t\t\t\tisConnected: () => pair.serverTransport.isConnected(),\n\t\t\t\tclose: (code, reason) => pair.serverTransport.close(code, reason),\n\t\t\t}\n\t\t\treturn { client, serverTransport }\n\t\t},\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;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;AAmBrE,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,SAAS,sBAAsB,EAAE,qBAAqB,QAAQ,oBAAoB,IAAI,CAAC;AAAA,MAC3F,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;AAAA;AAAA;AAAA;AAAA,EAOA,wBAAwB,UAAU,GAAS;AAC1C,SAAK,WAAW,wBAAwB,OAAO;AAAA,EAChD;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;;;AFhBA,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;;;AOxB/B,SAAS,QAAQ,QAAgB,YAA4B;AAC5D,QAAM,SAAS,CAAC,UACf,OAAO,KAAK,KAAK,UAAU,KAAK,CAAC,EAAE,SAAS,WAAW;AACxD,SAAO,GAAG,OAAO,EAAE,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,IAAI,OAAO,EAAE,KAAK,QAAQ,KAAK,eAAe,WAAW,CAAC,CAAC;AACzG;AAMO,SAAS,sBAAsB,SAGlB;AACnB,MAAI,aAAa;AACjB,MAAI,QAAuB,SAAS,gBACjC;AAAA,IACA,OAAO;AAAA,IACP,QAAQ,QAAQ;AAAA,IAChB,OAAO,QAAQ,QAAQ,eAAe,UAAU;AAAA,EACjD,IACC,EAAE,OAAO,UAAU;AACtB,QAAM,YAAY,oBAAI,IAAgB;AACtC,QAAM,SAAS,MAAY;AAC1B,eAAW,YAAY,UAAW,UAAS;AAAA,EAC5C;AAEA,SAAO;AAAA,IACN,IAAI,QAAQ;AACX,aAAO;AAAA,IACR;AAAA,IACA,MAAM,aAAa,EAAE,OAAO,MAAM,UAAU,kBAAkB,MAAM,QAAQ,GAAG;AAAA,IAC/E,kBAAkB,YAAY;AAAA,IAC9B,eAAe,YAAa,MAAM,UAAU,kBAAkB,MAAM,SAAS;AAAA,IAC7E,eAAe,YAAa,MAAM,UAAU,kBAAkB,gBAAgB;AAAA,IAC9E,UAAU,UAAU;AACnB,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM,UAAU,OAAO,QAAQ;AAAA,IACvC;AAAA,IACA,aAAa;AACZ,cAAQ,EAAE,OAAO,UAAU;AAC3B,aAAO;AAAA,IACR;AAAA,IACA,OAAO,QAAQ,OAAO;AACrB;AACA,cAAQ,EAAE,OAAO,iBAAiB,QAAQ,OAAO,SAAS,QAAQ,QAAQ,UAAU,EAAE;AACtF,aAAO;AAAA,IACR;AAAA,IACA,aAAa,OAAO;AACnB,UAAI,MAAM,UAAU,gBAAiB,OAAM,IAAI,MAAM,iCAAiC;AACtF;AACA,cAAQ;AAAA,QACP,GAAG;AAAA,QACH,OAAO,SAAS,QAAQ,MAAM,QAAQ,UAAU;AAAA,MACjD;AACA,aAAO;AAAA,IACR;AAAA,IACA,UAAU;AACT,cAAQ,SAAS,YACd,EAAE,OAAO,aAAa,uBAAuB,KAAK,IAClD,EAAE,OAAO,cAAc,uBAAuB,MAAM;AACvD,aAAO;AAAA,IACR;AAAA,EACD;AACD;;;AC7DA,SAAS,UAAU,SAA8B;AAChD,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,OAAO,CAAC,EAAE;AAC1D;AAGO,SAAS,yBAGd;AACD,QAAM,UAA+B;AAAA,IACpC,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,aAAa;AAAA,IACb,aAAa;AAAA,IACb,QAAQ;AACP,WAAK,qBAAqB;AAC1B,WAAK,iBAAiB;AACtB,WAAK,iBAAiB;AACtB,WAAK,iBAAiB;AACtB,WAAK,cAAc;AACnB,WAAK,cAAc;AAAA,IACpB;AAAA,EACD;AAEA,SAAO;AAAA,IACN;AAAA,IACA,cAAc,MAAM;AACnB,YAAM,SAAwB;AAAA,QAC7B,GAAG,KAAK;AAAA,QACR,MAAM,QAAQ,KAAK,SAAS;AAC3B,kBAAQ;AACR,gBAAM,KAAK,OAAO,QAAQ,KAAK,OAAO;AAAA,QACvC;AAAA,QACA,MAAM,aAAa;AAClB,kBAAQ;AACR,gBAAM,KAAK,OAAO,WAAW;AAAA,QAC9B;AAAA,QACA,KAAK,SAAS;AACb,kBAAQ;AACR,kBAAQ,eAAe,UAAU,OAAO;AACxC,eAAK,OAAO,KAAK,OAAO;AAAA,QACzB;AAAA,QACA,WAAW,CAAC,YAAY,KAAK,OAAO,UAAU,OAAO;AAAA,QACrD,SAAS,CAAC,YAAY,KAAK,OAAO,QAAQ,OAAO;AAAA,QACjD,SAAS,CAAC,YAAY,KAAK,OAAO,QAAQ,OAAO;AAAA,QACjD,aAAa,MAAM,KAAK,OAAO,YAAY;AAAA,MAC5C;AACA,YAAM,kBAAmC;AAAA,QACxC,GAAG,KAAK;AAAA,QACR,KAAK,SAAS;AACb,kBAAQ;AACR,kBAAQ,eAAe,UAAU,OAAO;AACxC,eAAK,gBAAgB,KAAK,OAAO;AAAA,QAClC;AAAA,QACA,WAAW,CAAC,YAAY,KAAK,gBAAgB,UAAU,OAAO;AAAA,QAC9D,SAAS,CAAC,YAAY,KAAK,gBAAgB,QAAQ,OAAO;AAAA,QAC1D,SAAS,CAAC,YAAY,KAAK,gBAAgB,QAAQ,OAAO;AAAA,QAC1D,aAAa,MAAM,KAAK,gBAAgB,YAAY;AAAA,QACpD,OAAO,CAAC,MAAM,WAAW,KAAK,gBAAgB,MAAM,MAAM,MAAM;AAAA,MACjE;AACA,aAAO,EAAE,QAAQ,gBAAgB;AAAA,IAClC;AAAA,EACD;AACD;","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","../src/test-auth.ts","../src/transport-metrics.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, TestDeviceEncryption, TestDeviceServer } 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\n// === Auth lifecycle and transfer-cost instrumentation ===\nexport { createTestAuthBinding } from './test-auth'\nexport type { TestAuthBinding } from './test-auth'\nexport { createTransportMetrics } from './transport-metrics'\nexport type { TestTransferMetrics } from './transport-metrics'\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\t/** Devices use the beta.12 pairwise pipeline (`experimental.legacyMerge`). */\n\tlegacyMerge?: boolean\n\t/**\n\t * The server's store (SQLite, Postgres). Defaults to a fresh memory store. The\n\t * network closes it.\n\t */\n\tserverStore?: import('@korajs/server').ServerStore\n\t/** Every device encrypts its sync traffic with this shared key material. */\n\tencryption?: import('./test-device').TestDeviceEncryption\n\t/** The sync server's encryption policy. */\n\tserverEncryption?: import('@korajs/server').KoraSyncServerConfig['encryption']\n\t/** Every device store's local `maxOperationBytes` (RT-86). */\n\tdeviceMaxOperationBytes?: number\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<import('@korajs/server').ServerStore>\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<import('@korajs/server').ServerStore>(schema, {\n\t\tblobStorage: options?.blobStorage,\n\t\tvalidateOperation: options?.validateOperation,\n\t\t...(options?.serverStore ? { store: options.serverStore } : {}),\n\t\t...(options?.serverEncryption ? { encryption: options.serverEncryption } : {}),\n\t})\n\tawait server.ready\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\t...(options?.legacyMerge ? { legacyMerge: true } : {}),\n\t\t\t...(options?.encryption ? { encryption: options.encryption } : {}),\n\t\t\t...(options?.deviceMaxOperationBytes !== undefined\n\t\t\t\t? { maxOperationBytes: options.deviceMaxOperationBytes }\n\t\t\t\t: {}),\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 { SyncEncryptor, SyncEngine, validateEncryptedRelations } from '@korajs/sync'\nimport type { SyncEncryptionConfig, SyncTransport } from '@korajs/sync'\nimport {\n\tApplyPipeline,\n\tMergeAwareSyncStore,\n\tStoreQueueStorage,\n\tStoreSyncStatePersistence,\n\tcreateSyncEngineChunkPort,\n\twireAuditPersistence,\n\twireBlobUpload,\n} from 'korajs/testing'\n\n/** What a device needs from its server: a way to open a session on it. */\nexport interface TestDeviceServer {\n\thandleConnection(transport: import('@korajs/server').ServerTransport): string\n}\n\n/**\n * End-to-end encryption of a test device's sync traffic. Devices that must read each\n * other's operations share the passphrase AND the key-derivation salt (shared key\n * distribution, ENC-1, is not built yet).\n */\nexport interface TestDeviceEncryption {\n\tconfig: SyncEncryptionConfig\n\tsalt: Uint8Array\n\t/** PBKDF2 iterations (lower in tests for speed). */\n\titerations?: number\n}\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: TestDeviceServer\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/** Schema transforms: the store folds every operation's view (transforms at fold time). */\n\toperationTransforms?: OperationTransform[]\n\t/** The store's local `maxOperationBytes` (RT-86). Default: the store default (256 KiB). */\n\tmaxOperationBytes?: number\n\t/** What the device does with records that leave its scope. Defaults to the engine default ('retain'). */\n\tscopeExit?: 'retain' | 'retract'\n\t/**\n\t * The signed-in user, as an auth-aware app knows it (RT-42). When set, the store binds\n\t * its node to that user at open and on {@link TestDevice.authChanged}, and the sync\n\t * engine never uploads another user's node on this user's session.\n\t */\n\tprincipal?: () => string | null\n\t/**\n\t * Let the sync engine reconnect on its own, as over a real WebSocket: a `connect()`\n\t * after the session closed opens a new transport pair to the server. Off by default\n\t * (a closed pair stays closed until the next {@link TestDevice.sync}).\n\t */\n\treconnectable?: boolean\n\t/**\n\t * Run the device on the beta.12 pairwise pipeline (`experimental.legacyMerge`)\n\t * instead of the W7 fold. Used by the comparison harness.\n\t */\n\tlegacyMerge?: boolean\n\t/** Encrypt sync traffic end to end (protocol v2 envelope). */\n\tencryption?: TestDeviceEncryption\n\t/** Upload batch size of the sync engine (default: the engine's). */\n\tbatchSize?: number\n}\n\ntype TransportPairFactory = TestDeviceOptions['createTransportPair']\n\n/**\n * A client transport that opens a fresh pair to the test server whenever it connects\n * after its current pair closed (a real socket reconnecting). Handlers move with it.\n */\nclass ReconnectingClientTransport implements SyncTransport {\n\tprivate messageHandler: Parameters<SyncTransport['onMessage']>[0] | null = null\n\tprivate closeHandler: Parameters<SyncTransport['onClose']>[0] | null = null\n\tprivate errorHandler: Parameters<SyncTransport['onError']>[0] | null = null\n\tprivate connectedOnce = false\n\n\tconstructor(\n\t\tprivate inner: SyncTransport,\n\t\tprivate readonly createPair: TransportPairFactory,\n\t\tprivate readonly server: TestDeviceServer,\n\t) {}\n\n\tasync connect(url: string, options?: Parameters<SyncTransport['connect']>[1]): Promise<void> {\n\t\tif (this.connectedOnce && !this.inner.isConnected()) {\n\t\t\tconst previous = this.inner\n\t\t\tprevious.onMessage(() => {})\n\t\t\tprevious.onClose(() => {})\n\t\t\tprevious.onError(() => {})\n\t\t\tconst { client, serverTransport } = this.createPair()\n\t\t\tthis.inner = client\n\t\t\tthis.wire()\n\t\t\tthis.server.handleConnection(serverTransport)\n\t\t}\n\t\tthis.connectedOnce = true\n\t\tawait this.inner.connect(url, options)\n\t}\n\n\tdisconnect(): Promise<void> {\n\t\treturn this.inner.disconnect()\n\t}\n\n\tsend(message: Parameters<SyncTransport['send']>[0]): void {\n\t\tthis.inner.send(message)\n\t}\n\n\tonMessage(handler: Parameters<SyncTransport['onMessage']>[0]): void {\n\t\tthis.messageHandler = handler\n\t\tthis.wire()\n\t}\n\n\tonClose(handler: Parameters<SyncTransport['onClose']>[0]): void {\n\t\tthis.closeHandler = handler\n\t\tthis.wire()\n\t}\n\n\tonError(handler: Parameters<SyncTransport['onError']>[0]): void {\n\t\tthis.errorHandler = handler\n\t\tthis.wire()\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.inner.isConnected()\n\t}\n\n\tprivate wire(): void {\n\t\tthis.inner.onMessage((message) => this.messageHandler?.(message))\n\t\tthis.inner.onClose((reason) => this.closeHandler?.(reason))\n\t\tthis.inner.onError((error) => this.errorHandler?.(error))\n\t}\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: TestDeviceServer\n\tprivate readonly encryption: TestDeviceEncryption | undefined\n\tprivate readonly batchSize: number | undefined\n\tprivate encryptor: SyncEncryptor | null = null\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\tprivate readonly scopeExit: 'retain' | 'retract' | undefined\n\tprivate readonly principal: (() => string | null) | undefined\n\tprivate readonly reconnectable: boolean\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\t\tthis.scopeExit = options.scopeExit\n\t\tthis.principal = options.principal\n\t\tthis.reconnectable = options.reconnectable === true\n\t\tthis.encryption = options.encryption\n\t\tthis.batchSize = options.batchSize\n\t\t// Like createApp: encryption may not seal a foreign key the server must enforce.\n\t\tvalidateEncryptedRelations(options.schema, options.encryption?.config)\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\tmaterialization: options.legacyMerge === true ? 'legacy' : 'fold',\n\t\t\t...(options.operationTransforms && options.operationTransforms.length > 0\n\t\t\t\t? { operationTransforms: options.operationTransforms }\n\t\t\t\t: {}),\n\t\t\t...(options.maxOperationBytes !== undefined\n\t\t\t\t? { maxOperationBytes: options.maxOperationBytes }\n\t\t\t\t: {}),\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\t// Like createApp with an auth binding: the node is bound to the signed-in user\n\t\t// before the first local write (RT-42).\n\t\tconst principal = this.principal?.()\n\t\tif (principal) await this.store.bindPrincipal(principal)\n\t}\n\n\t/**\n\t * The signed-in user changed (RT-42), as an auth binding reports it: like createApp,\n\t * end the live session and bind the store to the new user before the next write.\n\t */\n\tasync authChanged(): Promise<void> {\n\t\tif (this.syncEngine) {\n\t\t\tawait this.syncEngine.refreshPrincipal()\n\t\t\treturn\n\t\t}\n\t\tconst principal = this.principal?.()\n\t\tif (principal) await this.store.bindPrincipal(principal)\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 created = this.createTransportPair()\n\t\tconst { serverTransport } = created\n\t\tconst client = this.reconnectable\n\t\t\t? new ReconnectingClientTransport(created.client, this.createTransportPair, this.server)\n\t\t\t: created.client\n\t\tthis.currentTransport = client\n\n\t\tif (this.encryption && !this.encryptor) {\n\t\t\tthis.encryptor = await SyncEncryptor.create(\n\t\t\t\tthis.encryption.config,\n\t\t\t\tthis.encryption.salt,\n\t\t\t\tthis.encryption.iterations,\n\t\t\t)\n\t\t}\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\t...(this.scopeExit ? { scopeExit: this.scopeExit } : {}),\n\t\t\t\t...(this.principal\n\t\t\t\t\t? { principal: async () => (this.principal ? this.principal() : null) }\n\t\t\t\t\t: {}),\n\t\t\t\t...(this.encryption ? { encryption: this.encryption.config } : {}),\n\t\t\t\t...(this.batchSize !== undefined ? { batchSize: this.batchSize } : {}),\n\t\t\t},\n\t\t\temitter: this.emitter,\n\t\t\t...(this.encryptor ? { encryptor: this.encryptor } : {}),\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, OperationTransform, SchemaDefinition } from '@korajs/core'\nimport { MemoryServerStore } from '@korajs/server'\nimport { KoraSyncServer } from '@korajs/server'\nimport type {\n\tKoraSyncServerConfig,\n\tOperationValidator,\n\tServerStore,\n\tServerTransport,\n} 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<S extends ServerStore = MemoryServerStore> {\n\t/**\n\t * The server store (SQLite, Postgres, a memory store with options). Defaults to a\n\t * fresh {@link MemoryServerStore}. Await {@link TestServer.ready} before use: the\n\t * schema is set asynchronously.\n\t */\n\tstore?: S\n\t/** End-to-end encryption policy of the sync server (protocol v2). */\n\tencryption?: KoraSyncServerConfig['encryption']\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/** Transform accepted legacy operations into the server schema before validation. */\n\toperationTransforms?: OperationTransform[]\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<S extends ServerStore = MemoryServerStore> {\n\treadonly store: S\n\t/** Resolves once the store has the schema (needed before a SQL store is used). */\n\treadonly ready: Promise<void>\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<S>) {\n\t\t// Without a store option S is its default, MemoryServerStore.\n\t\tthis.store = options?.store ?? (new MemoryServerStore() as ServerStore as S)\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...(options?.operationTransforms ? { operationTransforms: options.operationTransforms } : {}),\n\t\t\t...(blob ? blob.callbacks : {}),\n\t\t\t...(options?.validateOperation ? { validateOperation: options.validateOperation } : {}),\n\t\t\t...(options?.encryption ? { encryption: options.encryption } : {}),\n\t\t})\n\t\tthis.ready = this.store.setSchema(\n\t\t\tschema,\n\t\t\toptions?.operationTransforms ? { operationTransforms: options.operationTransforms } : {},\n\t\t)\n\t\tthis.ready.catch(() => {})\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\tif (!(this.store instanceof MemoryServerStore)) {\n\t\t\tthrow new Error(\n\t\t\t\t'TestServer.getAllOperations() reads the memory store synchronously; use store.getOperationsAfterDelivery() with a SQL store',\n\t\t\t)\n\t\t}\n\t\treturn this.store.getAllOperations()\n\t}\n\n\t/** The trusted server-side write API (route context): writes authored by the server node. */\n\tgetKoraContext(): ReturnType<KoraSyncServer['getKoraContext']> {\n\t\treturn this.syncServer.getKoraContext()\n\t}\n\n\t/** The node ids the server's stores fold with and advertise in the handshake. */\n\tget authoritativeNodeIds(): string[] {\n\t\treturn this.syncServer.authoritativeNodeIds\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","import type { AuthSyncBinding, AuthSyncState } from '@korajs/core/bindings'\n\n/** Controllable auth binding for lifecycle and authenticated-only sync tests. */\nexport interface TestAuthBinding extends AuthSyncBinding {\n\treadonly state: AuthSyncState\n\tsetLoading(): void\n\tsignIn(userId: string, token?: string): void\n\trefreshToken(token?: string): void\n\tsignOut(): void\n}\n\nfunction testJwt(userId: string, generation: number): string {\n\tconst encode = (value: unknown): string =>\n\t\tBuffer.from(JSON.stringify(value)).toString('base64url')\n\treturn `${encode({ alg: 'none', typ: 'JWT' })}.${encode({ sub: userId, dev: 'test-device', generation })}.test`\n}\n\n/**\n * Create a deterministic auth binding that can transition between loading,\n * signed-out, and multiple authenticated users without a real auth server.\n */\nexport function createTestAuthBinding(options?: {\n\tinitialUserId?: string\n\tanonymous?: boolean\n}): TestAuthBinding {\n\tlet generation = 0\n\tlet state: AuthSyncState = options?.initialUserId\n\t\t? {\n\t\t\t\tstate: 'authenticated',\n\t\t\t\tuserId: options.initialUserId,\n\t\t\t\ttoken: testJwt(options.initialUserId, generation),\n\t\t\t}\n\t\t: { state: 'loading' }\n\tconst listeners = new Set<() => void>()\n\tconst notify = (): void => {\n\t\tfor (const listener of listeners) listener()\n\t}\n\n\treturn {\n\t\tget state() {\n\t\t\treturn state\n\t\t},\n\t\tauth: async () => ({ token: state.state === 'authenticated' ? (state.token ?? '') : '' }),\n\t\tresolveSyncState: async () => state,\n\t\tresolveUserId: async () => (state.state === 'authenticated' ? state.userId : undefined),\n\t\tresolveNodeId: async () => (state.state === 'authenticated' ? 'test-device' : undefined),\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener)\n\t\t\treturn () => listeners.delete(listener)\n\t\t},\n\t\tsetLoading() {\n\t\t\tstate = { state: 'loading' }\n\t\t\tnotify()\n\t\t},\n\t\tsignIn(userId, token) {\n\t\t\tgeneration++\n\t\t\tstate = { state: 'authenticated', userId, token: token ?? testJwt(userId, generation) }\n\t\t\tnotify()\n\t\t},\n\t\trefreshToken(token) {\n\t\t\tif (state.state !== 'authenticated') throw new Error('Cannot refresh while signed out')\n\t\t\tgeneration++\n\t\t\tstate = {\n\t\t\t\t...state,\n\t\t\t\ttoken: token ?? testJwt(state.userId, generation),\n\t\t\t}\n\t\t\tnotify()\n\t\t},\n\t\tsignOut() {\n\t\t\tstate = options?.anonymous\n\t\t\t\t? { state: 'anonymous', mayConnectAnonymously: true }\n\t\t\t\t: { state: 'signed-out', mayConnectAnonymously: false }\n\t\t\tnotify()\n\t\t},\n\t}\n}\n","import type { ServerTransport } from '@korajs/server'\nimport type { SyncMessage, SyncTransport } from '@korajs/sync'\nimport type { TransportPair } from './protobuf-wire-transport'\n\nexport interface TestTransferMetrics {\n\tconnectionAttempts: number\n\tdisconnections: number\n\tclientMessages: number\n\tserverMessages: number\n\tclientBytes: number\n\tserverBytes: number\n\treset(): void\n}\n\nfunction wireBytes(message: SyncMessage): number {\n\treturn new TextEncoder().encode(JSON.stringify(message)).byteLength\n}\n\n/** Create a transport wrapper and counters for asserting reconnect and transfer cost. */\nexport function createTransportMetrics(): {\n\tmetrics: TestTransferMetrics\n\twrapTransport(pair: TransportPair): TransportPair\n} {\n\tconst metrics: TestTransferMetrics = {\n\t\tconnectionAttempts: 0,\n\t\tdisconnections: 0,\n\t\tclientMessages: 0,\n\t\tserverMessages: 0,\n\t\tclientBytes: 0,\n\t\tserverBytes: 0,\n\t\treset() {\n\t\t\tthis.connectionAttempts = 0\n\t\t\tthis.disconnections = 0\n\t\t\tthis.clientMessages = 0\n\t\t\tthis.serverMessages = 0\n\t\t\tthis.clientBytes = 0\n\t\t\tthis.serverBytes = 0\n\t\t},\n\t}\n\n\treturn {\n\t\tmetrics,\n\t\twrapTransport(pair) {\n\t\t\tconst client: SyncTransport = {\n\t\t\t\t...pair.client,\n\t\t\t\tasync connect(url, options) {\n\t\t\t\t\tmetrics.connectionAttempts++\n\t\t\t\t\tawait pair.client.connect(url, options)\n\t\t\t\t},\n\t\t\t\tasync disconnect() {\n\t\t\t\t\tmetrics.disconnections++\n\t\t\t\t\tawait pair.client.disconnect()\n\t\t\t\t},\n\t\t\t\tsend(message) {\n\t\t\t\t\tmetrics.clientMessages++\n\t\t\t\t\tmetrics.clientBytes += wireBytes(message)\n\t\t\t\t\tpair.client.send(message)\n\t\t\t\t},\n\t\t\t\tonMessage: (handler) => pair.client.onMessage(handler),\n\t\t\t\tonClose: (handler) => pair.client.onClose(handler),\n\t\t\t\tonError: (handler) => pair.client.onError(handler),\n\t\t\t\tisConnected: () => pair.client.isConnected(),\n\t\t\t}\n\t\t\tconst serverTransport: ServerTransport = {\n\t\t\t\t...pair.serverTransport,\n\t\t\t\tsend(message) {\n\t\t\t\t\tmetrics.serverMessages++\n\t\t\t\t\tmetrics.serverBytes += wireBytes(message)\n\t\t\t\t\tpair.serverTransport.send(message)\n\t\t\t\t},\n\t\t\t\tonMessage: (handler) => pair.serverTransport.onMessage(handler),\n\t\t\t\tonClose: (handler) => pair.serverTransport.onClose(handler),\n\t\t\t\tonError: (handler) => pair.serverTransport.onError(handler),\n\t\t\t\tisConnected: () => pair.serverTransport.isConnected(),\n\t\t\t\tclose: (code, reason) => pair.serverTransport.close(code, reason),\n\t\t\t}\n\t\t\treturn { client, serverTransport }\n\t\t},\n\t}\n}\n"],"mappings":"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","names":["import_internal","import_sync","import_server","import_store","import_sync","import_sync"]}
|