@korajs/test 0.1.0
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 +434 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +239 -0
- package/dist/index.d.ts +239 -0
- package/dist/index.js +392 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ChaosTransport: () => import_sync2.ChaosTransport,
|
|
34
|
+
TestDevice: () => TestDevice,
|
|
35
|
+
TestServer: () => TestServer,
|
|
36
|
+
checkConvergence: () => checkConvergence,
|
|
37
|
+
createTestNetwork: () => createTestNetwork,
|
|
38
|
+
expectConverged: () => expectConverged
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/test-network.ts
|
|
43
|
+
var import_node_fs = require("fs");
|
|
44
|
+
var import_node_os = require("os");
|
|
45
|
+
var import_node_path = require("path");
|
|
46
|
+
var import_internal2 = require("@korajs/server/internal");
|
|
47
|
+
|
|
48
|
+
// src/test-device.ts
|
|
49
|
+
var import_internal = require("@korajs/core/internal");
|
|
50
|
+
var import_merge = require("@korajs/merge");
|
|
51
|
+
var import_store = require("@korajs/store");
|
|
52
|
+
var import_better_sqlite3 = require("@korajs/store/better-sqlite3");
|
|
53
|
+
var import_sync = require("@korajs/sync");
|
|
54
|
+
var TestDevice = class {
|
|
55
|
+
name;
|
|
56
|
+
store;
|
|
57
|
+
emitter;
|
|
58
|
+
schema;
|
|
59
|
+
server;
|
|
60
|
+
mergeEngine;
|
|
61
|
+
createTransportPair;
|
|
62
|
+
adapter;
|
|
63
|
+
dbPath;
|
|
64
|
+
syncEngine = null;
|
|
65
|
+
currentTransport = null;
|
|
66
|
+
unsubscribeSync = null;
|
|
67
|
+
closing = false;
|
|
68
|
+
constructor(options) {
|
|
69
|
+
this.name = options.name;
|
|
70
|
+
this.schema = options.schema;
|
|
71
|
+
this.server = options.server;
|
|
72
|
+
this.createTransportPair = options.createTransportPair;
|
|
73
|
+
this.dbPath = `${options.tmpDir}/test-device-${options.name}.db`;
|
|
74
|
+
this.emitter = new import_internal.SimpleEventEmitter();
|
|
75
|
+
this.mergeEngine = new import_merge.MergeEngine();
|
|
76
|
+
this.adapter = new import_better_sqlite3.BetterSqlite3Adapter(this.dbPath);
|
|
77
|
+
this.store = new import_store.Store({
|
|
78
|
+
schema: options.schema,
|
|
79
|
+
adapter: this.adapter,
|
|
80
|
+
emitter: this.emitter
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Open the store (must be called before sync or collection operations).
|
|
85
|
+
*/
|
|
86
|
+
async open() {
|
|
87
|
+
await this.store.open();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Connect to the test server and perform initial sync.
|
|
91
|
+
* If already connected, flushes any pending operations.
|
|
92
|
+
*/
|
|
93
|
+
async sync() {
|
|
94
|
+
if (this.syncEngine && this.currentTransport?.isConnected()) {
|
|
95
|
+
await this.waitForPendingOps();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const { client, serverTransport } = this.createTransportPair();
|
|
99
|
+
this.currentTransport = client;
|
|
100
|
+
const syncStore = this.createMergeAwareSyncStore();
|
|
101
|
+
this.syncEngine = new import_sync.SyncEngine({
|
|
102
|
+
transport: client,
|
|
103
|
+
store: syncStore,
|
|
104
|
+
config: { url: "ws://test-network" },
|
|
105
|
+
emitter: this.emitter
|
|
106
|
+
});
|
|
107
|
+
const engine = this.syncEngine;
|
|
108
|
+
this.unsubscribeSync = this.emitter.on("operation:created", (event) => {
|
|
109
|
+
if (!this.closing && this.syncEngine === engine && this.currentTransport?.isConnected()) {
|
|
110
|
+
this.syncEngine.pushOperation(event.operation).catch(() => {
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
this.server.handleConnection(serverTransport);
|
|
115
|
+
await this.syncEngine.start();
|
|
116
|
+
await this.waitForSettled();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Disconnect from the test server.
|
|
120
|
+
*/
|
|
121
|
+
async disconnect() {
|
|
122
|
+
if (this.unsubscribeSync) {
|
|
123
|
+
this.unsubscribeSync();
|
|
124
|
+
this.unsubscribeSync = null;
|
|
125
|
+
}
|
|
126
|
+
if (this.syncEngine) {
|
|
127
|
+
await this.syncEngine.stop();
|
|
128
|
+
this.syncEngine = null;
|
|
129
|
+
}
|
|
130
|
+
this.currentTransport = null;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Reconnect to the test server after a disconnect.
|
|
134
|
+
*/
|
|
135
|
+
async reconnect() {
|
|
136
|
+
await this.sync();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get a collection accessor for performing CRUD operations.
|
|
140
|
+
*/
|
|
141
|
+
collection(name) {
|
|
142
|
+
return this.store.collection(name);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get all records from a collection (convenience method).
|
|
146
|
+
*/
|
|
147
|
+
async getState(collectionName) {
|
|
148
|
+
const accessor = this.store.collection(collectionName);
|
|
149
|
+
return accessor.where({}).exec();
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get the device's node ID.
|
|
153
|
+
*/
|
|
154
|
+
getNodeId() {
|
|
155
|
+
return this.store.getNodeId();
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get the device's version vector.
|
|
159
|
+
*/
|
|
160
|
+
getVersionVector() {
|
|
161
|
+
return this.store.getVersionVector();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if the device is currently connected to the server.
|
|
165
|
+
*/
|
|
166
|
+
isConnected() {
|
|
167
|
+
return this.currentTransport?.isConnected() ?? false;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Close the device, releasing all resources.
|
|
171
|
+
*/
|
|
172
|
+
async close() {
|
|
173
|
+
this.closing = true;
|
|
174
|
+
await this.disconnect();
|
|
175
|
+
await this.store.close();
|
|
176
|
+
this.emitter.clear();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Create a SyncStore wrapper that interposes merge resolution.
|
|
180
|
+
* Simplified version of MergeAwareSyncStore from the kora meta-package.
|
|
181
|
+
*/
|
|
182
|
+
createMergeAwareSyncStore() {
|
|
183
|
+
const store = this.store;
|
|
184
|
+
const mergeEngine = this.mergeEngine;
|
|
185
|
+
const emitter = this.emitter;
|
|
186
|
+
return {
|
|
187
|
+
getVersionVector() {
|
|
188
|
+
return store.getVersionVector();
|
|
189
|
+
},
|
|
190
|
+
getNodeId() {
|
|
191
|
+
return store.getNodeId();
|
|
192
|
+
},
|
|
193
|
+
async getOperationRange(nodeId, fromSeq, toSeq) {
|
|
194
|
+
return store.getOperationRange(nodeId, fromSeq, toSeq);
|
|
195
|
+
},
|
|
196
|
+
async applyRemoteOperation(op) {
|
|
197
|
+
return store.applyRemoteOperation(op);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Wait for in-flight sync operations to settle.
|
|
203
|
+
* In-memory transports are near-synchronous, so a microtask flush suffices.
|
|
204
|
+
*/
|
|
205
|
+
async waitForSettled() {
|
|
206
|
+
for (let i = 0; i < 5; i++) {
|
|
207
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Wait for all pending outbound operations to be acknowledged.
|
|
212
|
+
*/
|
|
213
|
+
async waitForPendingOps() {
|
|
214
|
+
if (!this.syncEngine) return;
|
|
215
|
+
const maxWait = 2e3;
|
|
216
|
+
const start = Date.now();
|
|
217
|
+
while (Date.now() - start < maxWait) {
|
|
218
|
+
const status = this.syncEngine.getStatus();
|
|
219
|
+
if (status.pendingOperations === 0) return;
|
|
220
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/test-server.ts
|
|
226
|
+
var import_server = require("@korajs/server");
|
|
227
|
+
var import_server2 = require("@korajs/server");
|
|
228
|
+
var TestServer = class {
|
|
229
|
+
store;
|
|
230
|
+
syncServer;
|
|
231
|
+
constructor(schema) {
|
|
232
|
+
this.store = new import_server.MemoryServerStore();
|
|
233
|
+
this.syncServer = new import_server2.KoraSyncServer({
|
|
234
|
+
store: this.store,
|
|
235
|
+
schemaVersion: schema.version
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Register a client connection transport with the server.
|
|
240
|
+
* Returns the session ID assigned by the server.
|
|
241
|
+
*/
|
|
242
|
+
handleConnection(transport) {
|
|
243
|
+
return this.syncServer.handleConnection(transport);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get all operations stored on the server.
|
|
247
|
+
*/
|
|
248
|
+
getAllOperations() {
|
|
249
|
+
return this.store.getAllOperations();
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get the number of connected clients.
|
|
253
|
+
*/
|
|
254
|
+
getConnectionCount() {
|
|
255
|
+
return this.syncServer.getConnectionCount();
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Shut down the server and close all sessions.
|
|
259
|
+
*/
|
|
260
|
+
async close() {
|
|
261
|
+
await this.syncServer.stop();
|
|
262
|
+
await this.store.close();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/test-network.ts
|
|
267
|
+
async function createTestNetwork(schema, options) {
|
|
268
|
+
const deviceCount = options?.devices ?? 2;
|
|
269
|
+
const deviceNames = options?.deviceNames ?? Array.from({ length: deviceCount }, (_, i) => `device-${i}`);
|
|
270
|
+
const tmpDir = (0, import_node_fs.mkdtempSync)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "kora-test-"));
|
|
271
|
+
const server = new TestServer(schema);
|
|
272
|
+
const devices = [];
|
|
273
|
+
for (const name of deviceNames) {
|
|
274
|
+
const device = new TestDevice({
|
|
275
|
+
name,
|
|
276
|
+
schema,
|
|
277
|
+
server,
|
|
278
|
+
createTransportPair: () => {
|
|
279
|
+
const pair = (0, import_internal2.createServerTransportPair)();
|
|
280
|
+
return {
|
|
281
|
+
client: pair.client,
|
|
282
|
+
serverTransport: pair.server
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
tmpDir
|
|
286
|
+
});
|
|
287
|
+
await device.open();
|
|
288
|
+
devices.push(device);
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
server,
|
|
292
|
+
devices,
|
|
293
|
+
tmpDir,
|
|
294
|
+
async close() {
|
|
295
|
+
for (const device of devices) {
|
|
296
|
+
await device.close();
|
|
297
|
+
}
|
|
298
|
+
await server.close();
|
|
299
|
+
try {
|
|
300
|
+
const { rmSync } = await import("fs");
|
|
301
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
302
|
+
} catch {
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/assertions.ts
|
|
309
|
+
async function expectConverged(devices, schema) {
|
|
310
|
+
if (devices.length < 2) return;
|
|
311
|
+
const result = await checkConvergence(devices, schema);
|
|
312
|
+
if (!result.converged) {
|
|
313
|
+
const details = result.differences.map((d) => {
|
|
314
|
+
const parts = [
|
|
315
|
+
` Collection "${d.collection}" differs between ${d.deviceA} and ${d.deviceB}:`
|
|
316
|
+
];
|
|
317
|
+
if (d.missingInB.length > 0) {
|
|
318
|
+
parts.push(` Missing in ${d.deviceB}: ${d.missingInB.join(", ")}`);
|
|
319
|
+
}
|
|
320
|
+
if (d.missingInA.length > 0) {
|
|
321
|
+
parts.push(` Missing in ${d.deviceA}: ${d.missingInA.join(", ")}`);
|
|
322
|
+
}
|
|
323
|
+
for (const fd of d.fieldDifferences) {
|
|
324
|
+
parts.push(
|
|
325
|
+
` Record "${fd.recordId}" field "${fd.field}": ${JSON.stringify(fd.valueInA)} vs ${JSON.stringify(fd.valueInB)}`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
return parts.join("\n");
|
|
329
|
+
}).join("\n");
|
|
330
|
+
throw new Error(`Devices have not converged:
|
|
331
|
+
${details}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function checkConvergence(devices, schema) {
|
|
335
|
+
const differences = [];
|
|
336
|
+
const collectionNames = Object.keys(schema.collections);
|
|
337
|
+
for (let i = 0; i < devices.length - 1; i++) {
|
|
338
|
+
for (let j = i + 1; j < devices.length; j++) {
|
|
339
|
+
const deviceA = devices[i];
|
|
340
|
+
const deviceB = devices[j];
|
|
341
|
+
for (const collection of collectionNames) {
|
|
342
|
+
const stateA = await deviceA.getState(collection);
|
|
343
|
+
const stateB = await deviceB.getState(collection);
|
|
344
|
+
const diff = compareCollectionStates(collection, deviceA.name, deviceB.name, stateA, stateB);
|
|
345
|
+
if (diff) {
|
|
346
|
+
differences.push(diff);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
converged: differences.length === 0,
|
|
353
|
+
differences
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function compareCollectionStates(collection, nameA, nameB, stateA, stateB) {
|
|
357
|
+
const mapA = new Map(stateA.map((r) => [r.id, r]));
|
|
358
|
+
const mapB = new Map(stateB.map((r) => [r.id, r]));
|
|
359
|
+
const missingInB = [];
|
|
360
|
+
const missingInA = [];
|
|
361
|
+
const fieldDifferences = [];
|
|
362
|
+
for (const [id, recordA] of mapA) {
|
|
363
|
+
const recordB = mapB.get(id);
|
|
364
|
+
if (!recordB) {
|
|
365
|
+
missingInB.push(id);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const allFields = /* @__PURE__ */ new Set([
|
|
369
|
+
...Object.keys(recordA).filter((k) => !k.startsWith("_")),
|
|
370
|
+
...Object.keys(recordB).filter((k) => !k.startsWith("_"))
|
|
371
|
+
]);
|
|
372
|
+
for (const field of allFields) {
|
|
373
|
+
if (field === "id") continue;
|
|
374
|
+
const valA = recordA[field];
|
|
375
|
+
const valB = recordB[field];
|
|
376
|
+
if (!deepEqual(valA, valB)) {
|
|
377
|
+
fieldDifferences.push({
|
|
378
|
+
recordId: id,
|
|
379
|
+
field,
|
|
380
|
+
valueInA: valA,
|
|
381
|
+
valueInB: valB
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
for (const id of mapB.keys()) {
|
|
387
|
+
if (!mapA.has(id)) {
|
|
388
|
+
missingInA.push(id);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (missingInB.length === 0 && missingInA.length === 0 && fieldDifferences.length === 0) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
collection,
|
|
396
|
+
deviceA: nameA,
|
|
397
|
+
deviceB: nameB,
|
|
398
|
+
missingInB,
|
|
399
|
+
missingInA,
|
|
400
|
+
fieldDifferences
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function deepEqual(a, b) {
|
|
404
|
+
if (a === b) return true;
|
|
405
|
+
if (a === null || b === null) return false;
|
|
406
|
+
if (a === void 0 || b === void 0) return false;
|
|
407
|
+
if (typeof a !== typeof b) return false;
|
|
408
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
409
|
+
if (a.length !== b.length) return false;
|
|
410
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
411
|
+
}
|
|
412
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
413
|
+
const keysA = Object.keys(a);
|
|
414
|
+
const keysB = Object.keys(b);
|
|
415
|
+
if (keysA.length !== keysB.length) return false;
|
|
416
|
+
return keysA.every(
|
|
417
|
+
(key) => deepEqual(a[key], b[key])
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/index.ts
|
|
424
|
+
var import_sync2 = require("@korajs/sync");
|
|
425
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
426
|
+
0 && (module.exports = {
|
|
427
|
+
ChaosTransport,
|
|
428
|
+
TestDevice,
|
|
429
|
+
TestServer,
|
|
430
|
+
checkConvergence,
|
|
431
|
+
createTestNetwork,
|
|
432
|
+
expectConverged
|
|
433
|
+
});
|
|
434
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/test-network.ts","../src/test-device.ts","../src/test-server.ts","../src/assertions.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 } from './test-network'\nexport type { TestNetwork, TestNetworkOptions } 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'\n\n// === Assertions ===\nexport { checkConvergence, expectConverged } from './assertions'\nexport type {\n\tCollectionDifference,\n\tConvergenceResult,\n\tFieldDifference,\n} from './assertions'\n\n// === Re-export ChaosTransport for convenience ===\nexport { ChaosTransport } from '@korajs/sync'\nexport type { ChaosConfig } from '@korajs/sync'\n","import { mkdtempSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport type { SchemaDefinition } from '@korajs/core'\nimport { createServerTransportPair } from '@korajs/server/internal'\nimport type { SyncTransport } from '@korajs/sync'\nimport { TestDevice } from './test-device'\nimport { TestServer } 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}\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\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\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\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","import type { Operation, SchemaDefinition, VersionVector } from '@korajs/core'\nimport type { KoraEventEmitter } from '@korajs/core'\nimport { SimpleEventEmitter } from '@korajs/core/internal'\nimport { MergeEngine } from '@korajs/merge'\nimport { Store } from '@korajs/store'\nimport type { CollectionAccessor, StorageAdapter } from '@korajs/store'\nimport { BetterSqlite3Adapter } from '@korajs/store/better-sqlite3'\nimport { SyncEngine } from '@korajs/sync'\nimport type { SyncTransport } from '@korajs/sync'\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}\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\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\n\tprivate syncEngine: SyncEngine | null = null\n\tprivate currentTransport: SyncTransport | null = null\n\tprivate unsubscribeSync: (() => 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\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}\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 — wait for pending operations to flush\n\t\t\tawait this.waitForPendingOps()\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\t// Create a MergeAwareSyncStore wrapper\n\t\tconst syncStore = this.createMergeAwareSyncStore()\n\n\t\tthis.syncEngine = new SyncEngine({\n\t\t\ttransport: client,\n\t\t\tstore: syncStore,\n\t\t\tconfig: { url: 'ws://test-network' },\n\t\t\temitter: this.emitter,\n\t\t})\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.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/**\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\tawait this.disconnect()\n\t\tawait this.store.close()\n\t\tthis.emitter.clear()\n\t}\n\n\t/**\n\t * Create a SyncStore wrapper that interposes merge resolution.\n\t * Simplified version of MergeAwareSyncStore from the kora meta-package.\n\t */\n\tprivate createMergeAwareSyncStore(): import('@korajs/sync').SyncStore {\n\t\tconst store = this.store\n\t\tconst mergeEngine = this.mergeEngine\n\t\tconst emitter = this.emitter\n\n\t\treturn {\n\t\t\tgetVersionVector(): VersionVector {\n\t\t\t\treturn store.getVersionVector()\n\t\t\t},\n\t\t\tgetNodeId(): string {\n\t\t\t\treturn store.getNodeId()\n\t\t\t},\n\t\t\tasync getOperationRange(\n\t\t\t\tnodeId: string,\n\t\t\t\tfromSeq: number,\n\t\t\t\ttoSeq: number,\n\t\t\t): Promise<Operation[]> {\n\t\t\t\treturn store.getOperationRange(nodeId, fromSeq, toSeq)\n\t\t\t},\n\t\t\tasync applyRemoteOperation(op: Operation): Promise<import('@korajs/sync').ApplyResult> {\n\t\t\t\t// For the test harness, delegate directly to store.\n\t\t\t\t// Merge resolution happens inside the store's applyRemoteOperation.\n\t\t\t\treturn store.applyRemoteOperation(op)\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Wait for in-flight sync operations to settle.\n\t * In-memory transports are near-synchronous, so a microtask flush suffices.\n\t */\n\tprivate async waitForSettled(): Promise<void> {\n\t\t// Multiple microtask flushes to allow async message processing to complete\n\t\tfor (let i = 0; i < 5; i++) {\n\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 10))\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 { Operation, SchemaDefinition } from '@korajs/core'\nimport { MemoryServerStore } from '@korajs/server'\nimport { KoraSyncServer } from '@korajs/server'\nimport type { ServerTransport } from '@korajs/server'\n\n/**\n * In-memory test server wrapping KoraSyncServer with MemoryServerStore.\n * Handles client connections via memory transports.\n */\nexport class TestServer {\n\treadonly store: MemoryServerStore\n\tprivate readonly syncServer: KoraSyncServer\n\n\tconstructor(schema: SchemaDefinition) {\n\t\tthis.store = new MemoryServerStore()\n\t\tthis.syncServer = new KoraSyncServer({\n\t\t\tstore: this.store,\n\t\t\tschemaVersion: schema.version,\n\t\t})\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 * 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 * 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"],"mappings":"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","names":["import_internal","import_server","import_sync"]}
|