clone

a self-hosted sync node for lofi apps: jazz sync over iroh, ticket-gated, with no static ip or cloud needed

feat/connection-observability
lofi-node / testing / mod.ts
mod.ts 2.0K
1/**2 * In-process test helpers for lofi-node consumers.3 *4 * {@link createTestMesh} chains in-memory Jazz servers with direct loopback5 * upstream URLs — no iroh, no native addon, no network beyond 127.0.0.1 — so6 * app test suites can exercise multi-node sync topologies cheaply:7 *8 * ```ts9 * import { createTestMesh } from "@nzip/lofi-node/testing";10 *11 * const mesh = await createTestMesh({ nodes: 2 });12 * // point two clients at mesh.nodes[0].url / mesh.nodes[1].url …13 * await mesh.stop();14 * ```15 *16 * @module17 */1819import { createSyncNode, type SyncNode } from "../src/node.ts";2021/** A running in-process mesh; stop() tears nodes down leaves-first. */22export interface TestMesh {23 /** Node 0 is the root; node i+1 uses node i as its direct upstream. */24 nodes: SyncNode[];25 /** Stop all nodes in reverse creation order. */26 stop(): Promise<void>;27}2829/** Options for {@link createTestMesh}. */30export interface TestMeshOptions {31 /** How many chained nodes to start (default 2). */32 nodes?: number;33 /** Jazz app id shared by the whole mesh (default: a random UUID). */34 appId?: string;35}3637/** Node 0 is the root; node i+1 uses node i as its direct upstream. All nodes38 * share one appId and secrets (they model one user's fleet). */39export async function createTestMesh(options: TestMeshOptions = {}): Promise<TestMesh> {40 const count = options.nodes ?? 2;41 const appId = options.appId ?? crypto.randomUUID();42 const backendSecret = `lofi_backend_test_${crypto.randomUUID().slice(0, 8)}`;43 const adminSecret = `lofi_admin_test_${crypto.randomUUID().slice(0, 8)}`;4445 const nodes: SyncNode[] = [];46 for (let i = 0; i < count; i++) {47 nodes.push(48 await createSyncNode({49 appId,50 backendSecret,51 adminSecret,52 inMemory: true,53 mesh: "off",54 upstream: i === 0 ? "none" : { url: nodes[i - 1].url },55 }),56 );57 }58 return {59 nodes,60 stop: async () => {61 // Leaves first: stop in reverse creation order.62 for (const node of [...nodes].reverse()) await node.stop();63 },64 };65}