{"version":3,"file":"index.mjs","names":[],"sources":["../../src/testing-transport/chaosLink.ts"],"sourcesContent":["import { createInMemoryChannelPair, type IInMemoryServerEndpoint } from \"@nice-code/wire\";\nimport type {\n  IDuplexCarrier,\n  IDuplexCarrierSource,\n} from \"../ActionRuntime/Transport/Carrier/Carrier.types\";\n\ntype TFrame = string | ArrayBuffer | Uint8Array;\n\n/**\n * The chaos controls for one {@link chaosLink} — everything a resilience test does to the link.\n * All controls apply to the *current* connection and persist across redials (a link that redials\n * while `hold()` is active comes up held too).\n */\nexport interface IChaosLinkControls {\n  /**\n   * Stop delivering frames in BOTH directions while keeping the link \"open\" — the half-open\n   * socket: `isOpen()` stays true, sends vanish. This is the case a browser's offline mode\n   * creates and the staleness probe / wire keepalive exist to detect.\n   */\n  hold(): void;\n  /** Deliver every held frame in order and resume normal delivery (heal without a drop). */\n  releaseHeld(): void;\n  /** Discard the held frames (lost on the wire) and resume delivery — a lossy blip. */\n  dropHeld(): void;\n  /**\n   * Force-close the current connection — both ends observe a real close (unlike `hold()`), so\n   * the connector's keep-alive redials and the acceptor sees the drop.\n   */\n  closeLink(): void;\n  /**\n   * While `true`, every new dial fails — an unreachable server. Combine with {@link closeLink}\n   * for \"drop, and stay down\": the keep-alive backoff loop keeps failing until you allow dials\n   * again.\n   */\n  refuseDials(refuse: boolean): void;\n  /** Genuine dials so far — a keep-alive redial is observable as an increment. */\n  readonly dialCount: number;\n  /** Frames currently held by {@link hold} (both directions), for assertions. */\n  readonly heldCount: number;\n}\n\nexport interface IChaosLink {\n  /** The connector end — pass as a `carrier` in one of `connectChannel`'s `transports`. */\n  carrier: IDuplexCarrierSource;\n  /**\n   * Acceptor-side wiring, called once per dial (so a redial re-wires naturally). Use the endpoint\n   * itself as the acceptor's connection key:\n   *\n   * ```ts\n   * const acceptor = createChannelAcceptor<IInMemoryServerEndpoint>({\n   *   send: (conn, frame) => conn.send(frame),\n   *   // ...\n   * });\n   * link.onConnection((endpoint) => {\n   *   endpoint.onMessage((frame) => acceptor.receive(endpoint, frame));\n   *   endpoint.onClose(() => acceptor.drop(endpoint));\n   * });\n   * ```\n   *\n   * Must be registered before the first dial — a dial with no acceptor wiring would silently\n   * swallow the handshake, so it throws instead.\n   */\n  onConnection(handler: (endpoint: IInMemoryServerEndpoint, dialIndex: number) => void): void;\n  chaos: IChaosLinkControls;\n}\n\n/**\n * A reconnectable in-memory link with chaos controls — the transport the library's own resilience\n * suites use, exported so consumers can verify \"my app survives a flaky link\" without\n * rediscovering it (resilience-surface §2). Each dial mints a fresh in-process pair (no sockets),\n * so the keep-alive redial, `reconnectLink()`, and the staleness-probe escalation all behave\n * exactly as they do over a real carrier — but the test controls the weather: hold frames\n * (half-open link), drop held frames (loss), force a close, or refuse redials (sustained outage).\n *\n * For chaos over a *real* WebSocket instead, see `wsCarrier`'s `createWebSocket` option and\n * `connector.debug.dropLink()`.\n */\nexport function chaosLink(): IChaosLink {\n  let holding = false;\n  let refusing = false;\n  let held: (() => void)[] = [];\n  let dialCount = 0;\n  let connectionHandler: ((endpoint: IInMemoryServerEndpoint, dialIndex: number) => void) | null =\n    null;\n  let currentClose: (() => void) | null = null;\n\n  /** Deliver now, or park the delivery thunk while holding (FIFO on release). */\n  const deliver = (thunk: () => void): void => {\n    if (holding) held.push(thunk);\n    else thunk();\n  };\n\n  const chaos: IChaosLinkControls = {\n    hold: () => {\n      holding = true;\n    },\n    releaseHeld: () => {\n      holding = false;\n      const queue = held;\n      held = [];\n      for (const thunk of queue) thunk();\n    },\n    dropHeld: () => {\n      holding = false;\n      held = [];\n    },\n    closeLink: () => {\n      currentClose?.();\n    },\n    refuseDials: (refuse) => {\n      refusing = refuse;\n    },\n    get dialCount() {\n      return dialCount;\n    },\n    get heldCount() {\n      return held.length;\n    },\n  };\n\n  const carrier: IDuplexCarrierSource = {\n    carrierLabel: \"chaos-mem\",\n    getCacheKey: () => [\"chaos-mem\"],\n    open: (): IDuplexCarrier => {\n      if (connectionHandler == null) {\n        throw new Error(\n          \"chaosLink: register onConnection(...) before connecting — a dial with no acceptor \" +\n            \"wiring would silently swallow the handshake.\",\n        );\n      }\n      if (refusing) {\n        throw new Error(\"chaosLink: dial refused (chaos.refuseDials(true) is active)\");\n      }\n      const { clientChannel, serverEndpoint } = createInMemoryChannelPair();\n      const dialIndex = dialCount++;\n      currentClose = () => serverEndpoint.close();\n\n      // Both directions pass through the hold gate; the wrapped ends otherwise delegate.\n      const gatedClient: IDuplexCarrier = {\n        ...clientChannel,\n        send: (frame) => deliver(() => clientChannel.send(frame)),\n      };\n      const gatedEndpoint: IInMemoryServerEndpoint = {\n        ...serverEndpoint,\n        send: (frame: TFrame) => deliver(() => serverEndpoint.send(frame)),\n      };\n      connectionHandler(gatedEndpoint, dialIndex);\n      return gatedClient;\n    },\n  };\n\n  return {\n    carrier,\n    onConnection: (handler) => {\n      connectionHandler = handler;\n    },\n    chaos,\n  };\n}\n"],"mappings":";;;;;;;;;;;;;AA6EA,SAAgB,YAAwB;CACtC,IAAI,UAAU;CACd,IAAI,WAAW;CACf,IAAI,OAAuB,CAAC;CAC5B,IAAI,YAAY;CAChB,IAAI,oBACF;CACF,IAAI,eAAoC;;CAGxC,MAAM,WAAW,UAA4B;EAC3C,IAAI,SAAS,KAAK,KAAK,KAAK;OACvB,MAAM;CACb;CA6DA,OAAO;EACL,SAAA;GA/BA,cAAc;GACd,mBAAmB,CAAC,WAAW;GAC/B,YAA4B;IAC1B,IAAI,qBAAqB,MACvB,MAAM,IAAI,MACR,gIAEF;IAEF,IAAI,UACF,MAAM,IAAI,MAAM,6DAA6D;IAE/E,MAAM,EAAE,eAAe,mBAAmB,0BAA0B;IACpE,MAAM,YAAY;IAClB,qBAAqB,eAAe,MAAM;IAG1C,MAAM,cAA8B;KAClC,GAAG;KACH,OAAO,UAAU,cAAc,cAAc,KAAK,KAAK,CAAC;IAC1D;IACA,MAAM,gBAAyC;KAC7C,GAAG;KACH,OAAO,UAAkB,cAAc,eAAe,KAAK,KAAK,CAAC;IACnE;IACA,kBAAkB,eAAe,SAAS;IAC1C,OAAO;GACT;EAIM;EACN,eAAe,YAAY;GACzB,oBAAoB;EACtB;EACA,OAAA;GA/DA,YAAY;IACV,UAAU;GACZ;GACA,mBAAmB;IACjB,UAAU;IACV,MAAM,QAAQ;IACd,OAAO,CAAC;IACR,KAAK,MAAM,SAAS,OAAO,MAAM;GACnC;GACA,gBAAgB;IACd,UAAU;IACV,OAAO,CAAC;GACV;GACA,iBAAiB;IACf,eAAe;GACjB;GACA,cAAc,WAAW;IACvB,WAAW;GACb;GACA,IAAI,YAAY;IACd,OAAO;GACT;GACA,IAAI,YAAY;IACd,OAAO,KAAK;GACd;EAuCI;CACN;AACF"}