@fitzzero/quickdraw-core 5.1.1 → 5.1.3

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.
Files changed (152) hide show
  1. package/README.md +1308 -219
  2. package/dist/{admin-DKwkIh5i.d.ts → admin-CS7JB_UH.d.ts} +2 -2
  3. package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-29cGgrg5.d.ts} +6 -4
  4. package/dist/{auth-BhWqdyb-.d.ts → auth-DtdYjrZ9.d.ts} +51 -21
  5. package/dist/{caller-CXTFswBO.d.ts → caller-BTwGl7vm.d.ts} +296 -33
  6. package/dist/chunk-2FDSKLBO.js +82 -0
  7. package/dist/chunk-2FDSKLBO.js.map +1 -0
  8. package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
  9. package/dist/chunk-4D2DOGAW.js.map +1 -0
  10. package/dist/{chunk-OECZPRPW.js → chunk-64I6MYBC.js} +162 -775
  11. package/dist/chunk-64I6MYBC.js.map +1 -0
  12. package/dist/chunk-7473HK24.js +17 -0
  13. package/dist/chunk-7473HK24.js.map +1 -0
  14. package/dist/chunk-7Y5ROPXL.js +33 -0
  15. package/dist/chunk-7Y5ROPXL.js.map +1 -0
  16. package/dist/chunk-A2RBQD66.js +7 -0
  17. package/dist/chunk-A2RBQD66.js.map +1 -0
  18. package/dist/chunk-AK23XSWD.js +16 -0
  19. package/dist/chunk-AK23XSWD.js.map +1 -0
  20. package/dist/chunk-ASAMUGHG.js +651 -0
  21. package/dist/chunk-ASAMUGHG.js.map +1 -0
  22. package/dist/{chunk-B2P7AZ25.js → chunk-CYE4UBOB.js} +6 -4
  23. package/dist/chunk-CYE4UBOB.js.map +1 -0
  24. package/dist/chunk-DKE6QO26.js +136 -0
  25. package/dist/chunk-DKE6QO26.js.map +1 -0
  26. package/dist/chunk-DNP65TD5.js +832 -0
  27. package/dist/chunk-DNP65TD5.js.map +1 -0
  28. package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
  29. package/dist/chunk-DUBU3BB3.js.map +1 -0
  30. package/dist/chunk-GYLWEJHW.js +19 -0
  31. package/dist/chunk-GYLWEJHW.js.map +1 -0
  32. package/dist/{chunk-SHEGLZEP.js → chunk-I3JCFS7H.js} +34 -13
  33. package/dist/chunk-I3JCFS7H.js.map +1 -0
  34. package/dist/{chunk-X5CWPX7H.js → chunk-IIQ3WMJK.js} +13 -2
  35. package/dist/chunk-IIQ3WMJK.js.map +1 -0
  36. package/dist/{chunk-RZTP4IEW.js → chunk-JVTONQFI.js} +6339 -4226
  37. package/dist/chunk-JVTONQFI.js.map +1 -0
  38. package/dist/{chunk-QEKVYG5F.js → chunk-KKQANRLG.js} +2 -2
  39. package/dist/chunk-KKQANRLG.js.map +1 -0
  40. package/dist/{chunk-CDKG4TFA.js → chunk-MPQAT55F.js} +38 -205
  41. package/dist/chunk-MPQAT55F.js.map +1 -0
  42. package/dist/chunk-NO4M6PO5.js +101 -0
  43. package/dist/chunk-NO4M6PO5.js.map +1 -0
  44. package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
  45. package/dist/chunk-NW6U45HX.js.map +1 -0
  46. package/dist/chunk-NX2V6SIL.js +202 -0
  47. package/dist/chunk-NX2V6SIL.js.map +1 -0
  48. package/dist/{chunk-ZIVPHYRG.js → chunk-NZHA4TZD.js} +72 -175
  49. package/dist/chunk-NZHA4TZD.js.map +1 -0
  50. package/dist/{chunk-2PWMUVC7.js → chunk-OCZJ4EYV.js} +518 -27
  51. package/dist/chunk-OCZJ4EYV.js.map +1 -0
  52. package/dist/{chunk-REX5YPHX.js → chunk-PHCURHC7.js} +76 -21
  53. package/dist/chunk-PHCURHC7.js.map +1 -0
  54. package/dist/{chunk-4YO5H5ON.js → chunk-Q4OGQM5I.js} +4 -10
  55. package/dist/chunk-Q4OGQM5I.js.map +1 -0
  56. package/dist/chunk-QSU47XEX.js +13 -0
  57. package/dist/chunk-QSU47XEX.js.map +1 -0
  58. package/dist/{chunk-DD4SKCNO.js → chunk-RZXJTDH5.js} +13 -2
  59. package/dist/chunk-RZXJTDH5.js.map +1 -0
  60. package/dist/{chunk-Z2LZ7IZ7.js → chunk-SN3OZVEQ.js} +5 -31
  61. package/dist/chunk-SN3OZVEQ.js.map +1 -0
  62. package/dist/{chunk-4DIQHASK.js → chunk-SQ5VUG77.js} +28 -8
  63. package/dist/chunk-SQ5VUG77.js.map +1 -0
  64. package/dist/chunk-STH5DLQP.js +1192 -0
  65. package/dist/chunk-STH5DLQP.js.map +1 -0
  66. package/dist/{chunk-QCHKWIRS.js → chunk-VZFJTKIW.js} +13 -13
  67. package/dist/{chunk-QCHKWIRS.js.map → chunk-VZFJTKIW.js.map} +1 -1
  68. package/dist/{chunk-VSQBHPWH.js → chunk-X4ZU4QH5.js} +54 -22
  69. package/dist/chunk-X4ZU4QH5.js.map +1 -0
  70. package/dist/chunk-XD7SRDUY.js +188 -0
  71. package/dist/chunk-XD7SRDUY.js.map +1 -0
  72. package/dist/chunk-XH2IZVLT.js +62 -0
  73. package/dist/chunk-XH2IZVLT.js.map +1 -0
  74. package/dist/chunk-Z7I6SONO.js +825 -0
  75. package/dist/chunk-Z7I6SONO.js.map +1 -0
  76. package/dist/cli/quickdraw-docs.js +132 -112
  77. package/dist/cli/quickdraw-docs.js.map +1 -1
  78. package/dist/client/core.d.ts +387 -0
  79. package/dist/client/core.js +109 -0
  80. package/dist/client/index.d.ts +52 -203
  81. package/dist/client/index.js +59 -218
  82. package/dist/client/index.js.map +1 -1
  83. package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-CUUNJwWs.d.ts} +3 -3
  84. package/dist/{connection-Cr1BU07W.d.ts → connection-2EeQ_Et0.d.ts} +22 -11
  85. package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Bx5HOcYK.d.ts} +67 -17
  86. package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-DAMANT79.d.ts} +46 -13
  87. package/dist/{envelope-Cp3YcVxC.d.ts → envelope-BMBaxppV.d.ts} +216 -10
  88. package/dist/{http-Djj2Inuu.d.ts → http-YzLAar3w.d.ts} +76 -60
  89. package/dist/index.d.ts +8 -7
  90. package/dist/index.js +82 -25
  91. package/dist/index.js.map +1 -1
  92. package/dist/{infer-Teo1xpsd.d.ts → infer-Br62cnyW.d.ts} +82 -11
  93. package/dist/logger-Yz93KzdV.d.ts +30 -0
  94. package/dist/{metrics-BdBu5o8G.d.ts → metrics-CTVpw0gu.d.ts} +2 -2
  95. package/dist/origins-kvpxnVcW.d.ts +8 -0
  96. package/dist/parser.js +2 -1
  97. package/dist/prisma/index.d.ts +14 -7
  98. package/dist/prisma/index.js +279 -70
  99. package/dist/prisma/index.js.map +1 -1
  100. package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
  101. package/dist/rateLimit-5RKBFLUR.js.map +1 -0
  102. package/dist/remote-N7OUAGOW.js +15 -0
  103. package/dist/remote-N7OUAGOW.js.map +1 -0
  104. package/dist/server/auth/index.d.ts +82 -33
  105. package/dist/server/auth/index.js +277 -270
  106. package/dist/server/auth/index.js.map +1 -1
  107. package/dist/server/express/index.d.ts +9 -2
  108. package/dist/server/express/index.js +1 -1
  109. package/dist/server/index.d.ts +268 -84
  110. package/dist/server/index.js +664 -418
  111. package/dist/server/index.js.map +1 -1
  112. package/dist/server/mcp/index.d.ts +89 -27
  113. package/dist/server/mcp/index.js +281 -62
  114. package/dist/server/mcp/index.js.map +1 -1
  115. package/dist/server/otel.d.ts +3 -3
  116. package/dist/{sharing-DVa8W-yR.d.ts → sharing-BrsmlDv_.d.ts} +3 -3
  117. package/dist/testing/client.d.ts +14 -14
  118. package/dist/testing/client.js +19 -10
  119. package/dist/testing/client.js.map +1 -1
  120. package/dist/testing/index.d.ts +50 -13
  121. package/dist/testing/index.js +113 -21
  122. package/dist/testing/index.js.map +1 -1
  123. package/dist/testing/mock.d.ts +5 -5
  124. package/dist/testing/mock.js +17 -9
  125. package/dist/tracked-BR5b2O-U.d.ts +43 -0
  126. package/dist/{types-Cu0vmx7c.d.ts → types-CLRCQGnO.d.ts} +4 -3
  127. package/dist/types-tMagHbjy.d.ts +151 -0
  128. package/dist/utils/index.d.ts +16 -4
  129. package/dist/utils/index.js +6 -3
  130. package/package.json +5 -1
  131. package/dist/chunk-2PWMUVC7.js.map +0 -1
  132. package/dist/chunk-4DIQHASK.js.map +0 -1
  133. package/dist/chunk-4YO5H5ON.js.map +0 -1
  134. package/dist/chunk-B2P7AZ25.js.map +0 -1
  135. package/dist/chunk-CDKG4TFA.js.map +0 -1
  136. package/dist/chunk-DD4SKCNO.js.map +0 -1
  137. package/dist/chunk-H23KEFBA.js.map +0 -1
  138. package/dist/chunk-OECZPRPW.js.map +0 -1
  139. package/dist/chunk-QEKVYG5F.js.map +0 -1
  140. package/dist/chunk-REX5YPHX.js.map +0 -1
  141. package/dist/chunk-RT6OILOI.js.map +0 -1
  142. package/dist/chunk-RZTP4IEW.js.map +0 -1
  143. package/dist/chunk-SHEGLZEP.js.map +0 -1
  144. package/dist/chunk-TBMKJ26R.js.map +0 -1
  145. package/dist/chunk-VSQBHPWH.js.map +0 -1
  146. package/dist/chunk-X5CWPX7H.js.map +0 -1
  147. package/dist/chunk-YSWH776Y.js +0 -13
  148. package/dist/chunk-YSWH776Y.js.map +0 -1
  149. package/dist/chunk-Z2LZ7IZ7.js.map +0 -1
  150. package/dist/chunk-ZIVPHYRG.js.map +0 -1
  151. package/dist/logger-BY28acW-.d.ts +0 -16
  152. /package/dist/{rateLimit-ZMIZJUZF.js.map → client/core.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/peer/rtc.ts","../src/client/peer/hub.ts","../src/client/peer/link.ts"],"sourcesContent":["// The direct path of a peer link (RFC 0003 section 12.5): WebRTC data\n// channels between the two clients. Both sides make the same two channels,\n// negotiated with fixed ids, so neither waits for `ondatachannel`: `rel`\n// (id 0, ordered, reliable) and `fast` (id 1, unordered,\n// `maxRetransmits: 0`). The offerer sends its offer, the answerer its\n// answer, and each its ICE candidates as they come; a candidate that arrives\n// before the remote description is set waits for it. The path opens once\n// both channels are open, and fails when ICE fails, when it stays\n// disconnected for `DISCONNECTED_GRACE_MS`, when a channel closes (which it\n// tells apart: the peer closing its link closes its channels too), or when\n// a step of the negotiation throws. A `fast` message is dropped, never\n// queued, while its channel holds more than `FAST_BACKLOG_BYTES` unsent: an\n// unreliable channel's buffer still grows under congestion.\n//\n// The WebRTC types here are structural, the part a link uses, so a React\n// Native or Node implementation (react-native-webrtc, a polyfill) fits.\n//\n// React-free.\n\nimport type {\n PeerCandidate,\n PeerDescription,\n PeerLabel,\n PeerSignal,\n} from \"../../protocol/envelope\";\n\n/** How long ICE may stay `disconnected` before the direct path counts as failed. */\nexport const DISCONNECTED_GRACE_MS = 5000;\n\n/** A `fast` message is dropped while its channel holds more than this many bytes unsent. */\nexport const FAST_BACKLOG_BYTES = 32_768;\n\n/** Candidates kept for a remote description not set yet. */\nconst MAX_EARLY_CANDIDATES = 64;\n\n/** A STUN or TURN server, as `RTCIceServer` takes it. */\nexport interface PeerIceServer {\n urls: string | string[];\n username?: string;\n credential?: string;\n}\n\n/** What a link makes its `RTCPeerConnection` with. */\nexport interface PeerConnectionConfig {\n iceServers: PeerIceServer[];\n}\n\n/** The part of an `RTCDataChannel` a link uses. */\nexport interface RtcChannel {\n readonly readyState: string;\n readonly bufferedAmount: number;\n binaryType: string;\n onopen: ((event: unknown) => void) | null;\n onclose: ((event: unknown) => void) | null;\n onmessage: ((event: { readonly data: unknown }) => void) | null;\n send(data: string): void;\n close(): void;\n}\n\n/** A candidate as an `RTCPeerConnectionIceEvent` carries it. */\ninterface RtcCandidate {\n readonly candidate: string;\n readonly sdpMid?: string | null;\n readonly sdpMLineIndex?: number | null;\n readonly usernameFragment?: string | null;\n}\n\n/** A session description as `createOffer` and `createAnswer` give it. */\ninterface RtcDescription {\n readonly type: string;\n readonly sdp?: string;\n}\n\n/** The part of an `RTCPeerConnection` a link uses. */\nexport interface RtcConnection {\n readonly iceConnectionState: string;\n onicecandidate: ((event: { readonly candidate: RtcCandidate | null }) => void) | null;\n oniceconnectionstatechange: ((event: unknown) => void) | null;\n createDataChannel(\n label: string,\n init: { negotiated: true; id: number; ordered: boolean; maxRetransmits?: number },\n ): RtcChannel;\n createOffer(): Promise<RtcDescription>;\n createAnswer(): Promise<RtcDescription>;\n setLocalDescription(description: RtcDescription): Promise<void>;\n setRemoteDescription(description: PeerDescription): Promise<void>;\n addIceCandidate(candidate: PeerCandidate): Promise<void>;\n close(): void;\n}\n\n/** What the direct path tells its link. */\nexport interface DirectEvents {\n /** A signal for the peer: the offer or the answer, or a candidate. */\n signal(sig: PeerSignal): void;\n /** Both channels opened. */\n opened(): void;\n /**\n * The path failed, before or after it opened; it is closed already.\n * `closed` when a channel closed, as it does when the peer closes its\n * link: the peer's bye may be on its way.\n */\n failed(closed: boolean): void;\n /** A message arrived on a channel. */\n message(data: unknown, label: PeerLabel): void;\n}\n\n/** One link's direct path. */\nexport interface DirectPath {\n /** True once both channels are open, until the path closes. */\n readonly open: boolean;\n /** Starts as the offerer: makes the offer and signals it. */\n offer(): void;\n /** Applies the peer's offer and signals the answer. */\n answer(offer: PeerDescription): void;\n /** Applies the peer's answer. */\n accept(answer: PeerDescription): void;\n /** Adds a candidate of the peer's. */\n candidate(ice: PeerCandidate): void;\n /** Sends a message's text; false when the path is not open or `fast` is backed up. */\n send(label: PeerLabel, text: string): boolean;\n /** Closes both channels and the connection. */\n close(): void;\n}\n\nfunction candidateOf(candidate: RtcCandidate): PeerCandidate {\n const { sdpMid, sdpMLineIndex, usernameFragment } = candidate;\n return {\n candidate: candidate.candidate,\n ...(sdpMid === undefined ? {} : { sdpMid }),\n ...(sdpMLineIndex === undefined ? {} : { sdpMLineIndex }),\n ...(usernameFragment === undefined ? {} : { usernameFragment }),\n };\n}\n\n/** The state one direct path keeps. */\ninterface DirectState {\n readonly rtc: RtcConnection;\n readonly channels: Readonly<Record<PeerLabel, RtcChannel>>;\n readonly events: DirectEvents;\n readonly early: PeerCandidate[];\n open: boolean;\n closed: boolean;\n remoteSet: boolean;\n grace: ReturnType<typeof setTimeout> | undefined;\n}\n\nfunction shut(state: DirectState): void {\n state.closed = true;\n state.open = false;\n clearTimeout(state.grace);\n state.rtc.onicecandidate = null;\n state.rtc.oniceconnectionstatechange = null;\n for (const channel of Object.values(state.channels)) {\n channel.onopen = null;\n channel.onclose = null;\n channel.onmessage = null;\n channel.close();\n }\n state.rtc.close();\n}\n\nfunction fail(state: DirectState, closed = false): void {\n if (!state.closed) {\n shut(state);\n state.events.failed(closed);\n }\n}\n\n/** Runs one step of the negotiation; a step that throws fails the path. */\nfunction step(state: DirectState, work: () => Promise<void>): void {\n work().catch(() => {\n fail(state);\n });\n}\n\nfunction setRemote(state: DirectState, description: PeerDescription): Promise<void> {\n return state.rtc.setRemoteDescription(description).then(() => {\n state.remoteSet = true;\n for (const ice of state.early.splice(0)) {\n state.rtc.addIceCandidate(ice).catch(() => undefined);\n }\n });\n}\n\n/** Signals this side's description, once set, unless the path closed meanwhile. */\nasync function describe(state: DirectState, kind: \"offer\" | \"answer\"): Promise<void> {\n const description = await (kind === \"offer\" ? state.rtc.createOffer() : state.rtc.createAnswer());\n if (state.closed) {\n return;\n }\n await state.rtc.setLocalDescription(description);\n if (!state.closed) {\n state.events.signal({ sdp: { type: kind, sdp: description.sdp ?? \"\" } });\n }\n}\n\nfunction watch(state: DirectState): void {\n const { rtc, channels, events } = state;\n for (const label of [\"rel\", \"fast\"] as const) {\n const channel = channels[label];\n channel.binaryType = \"arraybuffer\";\n channel.onopen = () => {\n if (\n !state.open &&\n channels.rel.readyState === \"open\" &&\n channels.fast.readyState === \"open\"\n ) {\n state.open = true;\n events.opened();\n }\n };\n channel.onclose = () => {\n fail(state, true);\n };\n channel.onmessage = (event) => {\n events.message(event.data, label);\n };\n }\n rtc.onicecandidate = (event) => {\n if (event.candidate !== null) {\n events.signal({ ice: candidateOf(event.candidate) });\n }\n };\n rtc.oniceconnectionstatechange = () => {\n const ice = rtc.iceConnectionState;\n if (ice === \"failed\" || ice === \"closed\") {\n fail(state);\n } else if (ice === \"disconnected\") {\n state.grace ??= setTimeout(() => {\n fail(state);\n }, DISCONNECTED_GRACE_MS);\n } else if (ice === \"connected\" || ice === \"completed\") {\n clearTimeout(state.grace);\n state.grace = undefined;\n }\n };\n}\n\n/** Starts a direct path on `rtc`, its channels made and watched. */\nexport function createDirectPath(rtc: RtcConnection, events: DirectEvents): DirectPath {\n const state: DirectState = {\n rtc,\n channels: {\n rel: rtc.createDataChannel(\"rel\", { negotiated: true, id: 0, ordered: true }),\n fast: rtc.createDataChannel(\"fast\", {\n negotiated: true,\n id: 1,\n ordered: false,\n maxRetransmits: 0,\n }),\n },\n events,\n early: [],\n open: false,\n closed: false,\n remoteSet: false,\n grace: undefined,\n };\n watch(state);\n return {\n get open() {\n return state.open;\n },\n offer: () => {\n step(state, () => describe(state, \"offer\"));\n },\n answer: (offer) => {\n step(state, async () => {\n await setRemote(state, offer);\n await describe(state, \"answer\");\n });\n },\n accept: (answer) => {\n step(state, () => setRemote(state, answer));\n },\n candidate(ice: PeerCandidate): void {\n if (state.closed) {\n return;\n }\n if (!state.remoteSet) {\n if (state.early.length < MAX_EARLY_CANDIDATES) {\n state.early.push(ice);\n }\n return;\n }\n rtc.addIceCandidate(ice).catch(() => undefined);\n },\n send(label: PeerLabel, text: string): boolean {\n const channel = state.channels[label];\n if (!state.open || channel.readyState !== \"open\") {\n return false;\n }\n if (label === \"fast\" && channel.bufferedAmount > FAST_BACKLOG_BYTES) {\n return false;\n }\n try {\n channel.send(text);\n return true;\n } catch {\n return false;\n }\n },\n close: () => {\n if (!state.closed) {\n shut(state);\n }\n },\n };\n}\n","// The peer links of one connection (RFC 0003 section 12.5): one listener\n// each for `qd:peer`, `qd:relay` and `qd:presence` on its socket, routing a\n// frame by its room and peer however many links the connection holds, as\n// `../live/demux.ts` routes live frames. A link belongs to its connection,\n// not to a `QueryClient`, so the hub is per connection, made on first use.\n//\n// - A signal for a link routes to it; one from a peer without a link waits\n// (`PENDING_MS`, at most `MAX_PENDING` peers of `MAX_PENDING_SIGNALS`\n// signals and `MAX_PENDING_CHARS` characters, `MAX_PENDING_TOTAL_CHARS`\n// for all of them) for the app to answer with a link, and the first one\n// is a request (`onPeerRequest`).\n// - A socket that leaves an app room hears `{ room, users: [] }`: the\n// links of that room end (`left`). A disconnect ends every link: a link\n// lives as long as the socket it started on.\n// - The relay keeps within what `qd:hello` announces (`limits.peers`), with\n// room for what the way to the server adds: nine tenths of each rate and\n// half of each burst, per peer and for the connection's bytes, so a `rel`\n// message is dropped here, where `send` says so, rather than silently by\n// the server. A `fast` one is dropped too while the way to the server is\n// backed up (`backedUp`), as a data channel's is: not Socket.IO's\n// volatile, which drops a packet whenever a write is in flight, so that of\n// two sent at once the second was always lost.\n//\n// React-free.\n\nimport { CLIENT_EVENTS, SERVER_EVENTS } from \"../../contract/names\";\nimport type { Failure, Ok, PeerLabel, PeerSignal } from \"../../protocol/envelope\";\nimport { isRecord } from \"../../protocol/guards\";\nimport { isPeerLabel, isRoomName, isSocketId, readSignal } from \"../../protocol/peer\";\nimport { utf8ByteLength } from \"../../protocol/utf8\";\nimport {\n MAX_PEER_LINKS,\n MAX_PEER_MESSAGE_BYTES,\n PEER_RELAY_BURST,\n PEER_RELAY_BYTES_BURST,\n PEER_RELAY_BYTES_PER_SECOND,\n PEER_RELAY_FAST_BACKLOG,\n PEER_RELAY_RATE,\n type HelloPeerLimits,\n} from \"../../protocol/version\";\nimport type { QuickdrawConnection } from \"../connection\";\nimport { throwLater } from \"./core\";\nimport { FAST_BACKLOG_BYTES } from \"./rtc\";\n\n/** How long signals from a peer without a link wait for the app to answer. */\nexport const PENDING_MS = 15_000;\n\n/** Peers whose signals may wait at once. */\nconst MAX_PENDING = 64;\n\n/** Signals one waiting peer may have. */\nconst MAX_PENDING_SIGNALS = 64;\n\n/** Characters the signals of one waiting peer may hold: an offer at its longest, and candidates. */\nconst MAX_PENDING_CHARS = 65_536;\n\n/** Characters the signals of every waiting peer may hold together. */\nconst MAX_PENDING_TOTAL_CHARS = 1_048_576;\n\n/** How long a signal waits for the server's acknowledgement. */\nconst SIGNAL_TIMEOUT_MS = 10_000;\n\n/** The share of each relay rate the client uses, and of each burst. */\nconst RATE_SHARE = 0.9;\nconst BURST_SHARE = 0.5;\n\n/** The limits of a server that announces none: this version's own. */\nexport const DEFAULT_PEER_LIMITS: HelloPeerLimits = Object.freeze({\n maxLinks: MAX_PEER_LINKS,\n maxMessageBytes: MAX_PEER_MESSAGE_BYTES,\n relayRate: PEER_RELAY_RATE,\n relayBurst: PEER_RELAY_BURST,\n relayBytesPerSecond: PEER_RELAY_BYTES_PER_SECOND,\n relayBytesBurst: PEER_RELAY_BYTES_BURST,\n});\n\n/** How a hub reaches a link. */\nexport interface LinkHandle {\n /**\n * A signal from the peer, with its user as the server named it. False when\n * it opens a new link instead (the peer's last bye was lost): the link\n * ended, and the hub takes the signal as a peer without a link.\n */\n signal(sig: PeerSignal, userId: string): boolean;\n /** A relayed message from the peer. */\n relay(label: PeerLabel, text: string): void;\n /** The socket disconnected, or the connection closed. */\n disconnected(): void;\n /** The socket left the link's room. */\n left(): void;\n}\n\n/** A peer that signalled with no link: the first signal of a request. */\nexport interface IncomingPeer {\n readonly room: string;\n readonly from: string;\n readonly userId: string;\n}\n\n/** A token bucket. */\nexport interface Bucket {\n tokens: number;\n refilledAt: number;\n}\n\n/** The peer links of one connection. */\nexport interface PeerHub {\n readonly connection: QuickdrawConnection;\n /** Adds a link; a `TypeError` when one for the room and peer is open. Returns its removal. */\n add(room: string, peer: string, handle: LinkHandle): () => void;\n /** Takes what waits from a peer without a link: its user and signals, in order. */\n take(room: string, peer: string): IncomingPeer & { readonly signals: readonly PeerSignal[] };\n /** Drops what waits from a peer, unanswered. */\n forget(room: string, peer: string): void;\n /** Sends a signal; resolves with the server's acknowledgement, or a failure. */\n signal(room: string, to: string, sig: PeerSignal): Promise<Ok | Failure>;\n /** Sends a relay message within the limits: false when it was dropped. */\n relay(room: string, to: string, label: PeerLabel, text: string, pair: Bucket): boolean;\n /** A fresh bucket for one peer's relay messages. */\n pairBucket(): Bucket;\n /** Calls `listener` for each peer that signals with no link; returns its removal. */\n requests(listener: (incoming: IncomingPeer) => void): () => void;\n /**\n * Calls `ready` once the connection is connected with a hello: with its\n * peer limits, or `undefined` when the server serves no peer links. Calls\n * `gone` instead when the connection is closed or refused first.\n */\n whenReady(ready: (limits: HelloPeerLimits | undefined) => void, gone: () => void): void;\n /** The limits of the server's hello, or this version's own. */\n limits(): HelloPeerLimits;\n}\n\ninterface Pending extends IncomingPeer {\n readonly signals: PeerSignal[];\n readonly at: number;\n /** The characters its signals hold. */\n chars: number;\n}\n\n/** The characters a signal holds, as the pending budgets count them. */\nfunction charsOf(sig: PeerSignal): number {\n if (sig.sdp !== undefined) {\n return sig.sdp.sdp.length;\n }\n const { ice } = sig;\n return ice === undefined\n ? 1\n : ice.candidate.length + (ice.sdpMid?.length ?? 0) + (ice.usernameFragment?.length ?? 0);\n}\n\nconst keyOf = (room: string, peer: string): string => `${room}\\u0000${peer}`;\n\nconst now = (): number => performance.now();\n\n/** Takes `cost` tokens, refilling first; false when too few are left. */\nfunction take(bucket: Bucket, rate: number, burst: number, cost = 1): boolean {\n const at = now();\n bucket.tokens = Math.min(burst, bucket.tokens + ((at - bucket.refilledAt) / 1000) * rate);\n bucket.refilledAt = at;\n if (bucket.tokens < cost) {\n return false;\n }\n bucket.tokens -= cost;\n return true;\n}\n\n/** The peer limits a connection's current hello announces, if it serves peer links. */\nfunction servedLimits(connection: QuickdrawConnection): HelloPeerLimits | undefined {\n const { hello } = connection.getState();\n return hello?.features.includes(\"peer\") === true\n ? (hello.limits.peers ?? DEFAULT_PEER_LIMITS)\n : undefined;\n}\n\nfunction readForward(frame: unknown): (IncomingPeer & { readonly sig: PeerSignal }) | undefined {\n if (!isRecord(frame) || !isRoomName(frame.room) || !isSocketId(frame.from)) {\n return undefined;\n }\n const sig = readSignal(frame.sig);\n const { userId } = frame;\n return sig === undefined || typeof userId !== \"string\" || userId === \"\"\n ? undefined\n : { room: frame.room, from: frame.from, userId, sig };\n}\n\n/** The state of one connection's hub. */\ninterface HubState {\n readonly connection: QuickdrawConnection;\n readonly links: Map<string, LinkHandle>;\n readonly pending: Map<string, Pending>;\n /** The characters every waiting peer's signals hold. */\n pendingChars: number;\n readonly listeners: Set<(incoming: IncomingPeer) => void>;\n readonly bytes: Bucket;\n}\n\n/** Drops what waits from a peer, and returns it. */\nfunction dropPending(state: HubState, key: string): Pending | undefined {\n const entry = state.pending.get(key);\n if (entry !== undefined) {\n state.pending.delete(key);\n state.pendingChars -= entry.chars;\n }\n return entry;\n}\n\n/** Keeps a signal for its waiting peer within the budgets; false when it was dropped. */\nfunction keep(state: HubState, entry: Pending, sig: PeerSignal): boolean {\n const chars = charsOf(sig);\n if (\n entry.signals.length >= MAX_PENDING_SIGNALS ||\n entry.chars + chars > MAX_PENDING_CHARS ||\n state.pendingChars + chars > MAX_PENDING_TOTAL_CHARS\n ) {\n return false;\n }\n entry.signals.push(sig);\n entry.chars += chars;\n state.pendingChars += chars;\n return true;\n}\n\nfunction receiveSignal(state: HubState, frame: unknown): void {\n const forward = readForward(frame);\n if (forward === undefined) {\n return;\n }\n const key = keyOf(forward.room, forward.from);\n const link = state.links.get(key);\n if (link?.signal(forward.sig, forward.userId) === true) {\n return;\n }\n if (forward.sig.bye === true) {\n dropPending(state, key);\n return;\n }\n const at = now();\n for (const [old, entry] of state.pending) {\n if (at - entry.at > PENDING_MS) {\n dropPending(state, old);\n }\n }\n const entry = state.pending.get(key);\n if (entry !== undefined) {\n keep(state, entry, forward.sig);\n return;\n }\n const { room, from, userId } = forward;\n const waiting: Pending = { room, from, userId, signals: [], at, chars: 0 };\n if (state.pending.size >= MAX_PENDING || !keep(state, waiting, forward.sig)) {\n return;\n }\n state.pending.set(key, waiting);\n for (const listener of [...state.listeners]) {\n try {\n listener({ room, from, userId });\n } catch (error) {\n // Reported apart: the other listeners still hear the request.\n throwLater(error);\n }\n }\n}\n\nfunction receiveRelay(state: HubState, frame: unknown): void {\n if (!Array.isArray(frame)) {\n return;\n }\n const [room, from, label, text] = frame as readonly unknown[];\n if (isRoomName(room) && isSocketId(from) && isPeerLabel(label) && typeof text === \"string\") {\n state.links.get(keyOf(room, from))?.relay(label, text);\n }\n}\n\n/** A socket that left a room hears `{ room, users: [] }`: its links there end. */\nfunction receivePresence(state: HubState, frame: unknown): void {\n if (!isRecord(frame) || !isRoomName(frame.room)) {\n return;\n }\n const { users } = frame;\n if (!Array.isArray(users) || users.length > 0) {\n return;\n }\n const prefix = keyOf(frame.room, \"\");\n for (const [key, link] of [...state.links]) {\n if (key.startsWith(prefix)) {\n link.left();\n }\n }\n}\n\nfunction listen(state: HubState): void {\n const { socket } = state.connection;\n socket.on(SERVER_EVENTS.peer, (frame: unknown) => {\n receiveSignal(state, frame);\n });\n socket.on(SERVER_EVENTS.relay, (frame: unknown) => {\n receiveRelay(state, frame);\n });\n socket.on(SERVER_EVENTS.presence, (frame: unknown) => {\n receivePresence(state, frame);\n });\n socket.on(\"disconnect\", () => {\n state.pending.clear();\n state.pendingChars = 0;\n for (const link of [...state.links.values()]) {\n link.disconnected();\n }\n });\n}\n\nfunction signalOf(\n state: HubState,\n room: string,\n to: string,\n sig: PeerSignal,\n): Promise<Ok | Failure> {\n const { socket } = state.connection;\n if (!socket.connected) {\n return Promise.resolve({ ok: false, e: { code: \"INTERNAL\", message: \"Not connected\" } });\n }\n return socket\n .timeout(SIGNAL_TIMEOUT_MS)\n .emitWithAck(CLIENT_EVENTS.peer, { room, to, sig })\n .catch(\n (): Failure => ({ ok: false, e: { code: \"TIMEOUT\", message: \"No answer to a peer signal\" } }),\n );\n}\n\n/** What `backedUp` reads of a socket's engine.io connection. */\ninterface EngineView {\n readonly writeBuffer?: readonly unknown[];\n readonly transport?: { readonly ws?: { readonly bufferedAmount?: unknown } };\n}\n\n/**\n * True while the way to the server is backed up: `PEER_RELAY_FAST_BACKLOG`\n * packets wait for the transport, or the WebSocket holds more than\n * `FAST_BACKLOG_BYTES` unsent (engine.io keeps it as its transport's `ws`).\n */\nfunction backedUp(socket: QuickdrawConnection[\"socket\"]): boolean {\n const engine = (socket as { readonly io?: { readonly engine?: EngineView } }).io?.engine;\n if ((engine?.writeBuffer?.length ?? 0) >= PEER_RELAY_FAST_BACKLOG) {\n return true;\n }\n const buffered = engine?.transport?.ws?.bufferedAmount;\n return typeof buffered === \"number\" && buffered > FAST_BACKLOG_BYTES;\n}\n\nfunction relayOf(\n state: HubState,\n frame: readonly [string, string, PeerLabel, string],\n pair: Bucket,\n): boolean {\n const { socket } = state.connection;\n const limits = servedLimits(state.connection);\n if (!socket.connected || limits === undefined) {\n return false;\n }\n if (frame[2] === \"fast\" && backedUp(socket)) {\n return false;\n }\n if (!take(pair, limits.relayRate * RATE_SHARE, limits.relayBurst * BURST_SHARE)) {\n return false;\n }\n const bytes = utf8ByteLength(frame[3]);\n const budget = limits.relayBytesPerSecond * RATE_SHARE;\n if (!take(state.bytes, budget, limits.relayBytesBurst * BURST_SHARE, bytes)) {\n pair.tokens += 1;\n return false;\n }\n socket.emit(CLIENT_EVENTS.relay, frame);\n return true;\n}\n\nfunction whenReady(\n connection: QuickdrawConnection,\n ready: (limits: HelloPeerLimits | undefined) => void,\n gone: () => void,\n): void {\n let stop: (() => void) | undefined;\n const check = (): boolean => {\n const { status, hello } = connection.getState();\n if (status === \"connected\" && hello !== null) {\n stop?.();\n ready(servedLimits(connection));\n return true;\n }\n if (status === \"idle\" || status === \"disconnected\" || status === \"refused\") {\n stop?.();\n gone();\n return true;\n }\n return false;\n };\n // A microtask later, so a listener added right after the link was made hears its first event.\n queueMicrotask(() => {\n if (!check()) {\n stop = connection.subscribe(() => {\n check();\n });\n }\n });\n}\n\nfunction createHub(connection: QuickdrawConnection): PeerHub {\n const state: HubState = {\n connection,\n links: new Map(),\n pending: new Map(),\n pendingChars: 0,\n listeners: new Set(),\n bytes: { tokens: 0, refilledAt: now() },\n };\n state.bytes.tokens = DEFAULT_PEER_LIMITS.relayBytesBurst * BURST_SHARE;\n listen(state);\n const hub: PeerHub = {\n connection,\n add(room: string, peer: string, handle: LinkHandle): () => void {\n const key = keyOf(room, peer);\n if (state.links.has(key)) {\n throw new TypeError(\n `createPeerLink: this connection has a link to ${peer} in room \"${room}\" already; close it first`,\n );\n }\n state.links.set(key, handle);\n return () => {\n if (state.links.get(key) === handle) {\n state.links.delete(key);\n }\n };\n },\n take(room: string, peer: string) {\n const entry = dropPending(state, keyOf(room, peer));\n const fresh = entry !== undefined && now() - entry.at <= PENDING_MS;\n return {\n room,\n from: peer,\n userId: entry?.userId ?? \"\",\n signals: fresh ? entry.signals : [],\n };\n },\n forget(room: string, peer: string): void {\n dropPending(state, keyOf(room, peer));\n },\n signal: (room, to, sig) => signalOf(state, room, to, sig),\n relay: (room, to, label, text, pair) => relayOf(state, [room, to, label, text], pair),\n pairBucket: () => ({\n tokens: (servedLimits(connection) ?? DEFAULT_PEER_LIMITS).relayBurst * BURST_SHARE,\n refilledAt: now(),\n }),\n requests(listener: (incoming: IncomingPeer) => void): () => void {\n state.listeners.add(listener);\n return () => {\n state.listeners.delete(listener);\n };\n },\n whenReady: (ready, gone) => {\n whenReady(connection, ready, gone);\n },\n limits: () => servedLimits(connection) ?? DEFAULT_PEER_LIMITS,\n };\n return Object.freeze(hub);\n}\n\nconst hubs = new WeakMap<QuickdrawConnection, PeerHub>();\n\n/** The peer links of `connection`, made on first use. */\nexport function hubOf(connection: QuickdrawConnection): PeerHub {\n let hub = hubs.get(connection);\n if (hub === undefined) {\n hub = createHub(connection);\n hubs.set(connection, hub);\n }\n return hub;\n}\n","// `createPeerLink(connection, options)` (RFC 0003 section 12.5): a link to\n// another socket of an app room, `options.to`, whose id the app learned (a\n// method's answer, `onPeerRequest`, the room's host). One side offers;\n// when both do (each learned of the other at once), the side whose socket\n// id sorts after the other's answers instead. The link starts once the\n// connection has a hello naming the `\"peer\"` feature (`unsupported`\n// otherwise), then:\n//\n// - the offerer tries the direct path (`rtc.ts`) when the platform has\n// WebRTC and `relay` is not `\"always\"`, and says `{ relay: true }`\n// instead when it may not; the answerer answers an offer the same way;\n// - a direct path that fails (ICE failed, nothing open within\n// `DIRECT_TIMEOUT_MS`, a path that dropped) gives way to the relay,\n// unless `relay` is `\"never\"`: this side says `{ relay: true }`, the peer\n// moves too and says it back, and the relay carries the link once both\n// said it. A link open on the direct path moves at once: each side reads\n// both paths all along. A path whose channel closed waits `BYE_WAIT_MS`\n// first: a peer that closes its link closes its channels too, and its bye\n// comes through the server, often after; a `{ relay: true }` sent then\n// would reach the peer as a new link's request;\n// - nothing open within `OPEN_TIMEOUT_MS`: `timeout`.\n//\n// Closing from this side signals `bye`, on which the peer's link closes, and\n// takes the direct path down once the server answered it, so the peer\n// hears the bye before its channels close; a link the peer, the server or\n// the socket ended (`bye`, `left`, `disconnected`) sends nothing.\n//\n// React-free.\n\nimport type {\n PeerCandidate,\n PeerDescription,\n PeerLabel,\n PeerSignal,\n} from \"../../protocol/envelope\";\nimport { isRoomName, isSocketId } from \"../../protocol/peer\";\nimport type { HelloPeerLimits } from \"../../protocol/version\";\nimport type { QuickdrawConnection } from \"../connection\";\nimport { createLinkCore, type LinkCore } from \"./core\";\nimport { hubOf, type Bucket, type IncomingPeer, type PeerHub } from \"./hub\";\nimport {\n createDirectPath,\n type DirectPath,\n type PeerConnectionConfig,\n type PeerIceServer,\n type RtcConnection,\n} from \"./rtc\";\nimport type { PeerLink } from \"./types\";\n\n/**\n * When a link uses the server's relay: `auto` when the direct path cannot\n * form or fails, `always` instead of it (no WebRTC at all), `never` (a link\n * whose direct path fails closes).\n */\nexport type RelayMode = \"auto\" | \"always\" | \"never\";\n\n/** Options of {@link createPeerLink}. */\nexport interface PeerLinkOptions {\n /** The app room both sockets are in; the link belongs to it, and ends when this socket leaves it. */\n readonly room: string;\n /** The peer's socket id. */\n readonly to: string;\n /**\n * Whether this side makes the offer. One side of a link does; when both\n * do, the side whose socket id sorts after the other's answers instead.\n */\n readonly offerer: boolean;\n /**\n * STUN and TURN servers. Default Google's and Cloudflare's public STUN\n * servers, and no TURN: a pair that cannot reach each other uses the\n * relay, which is expected, not an error.\n */\n readonly iceServers?: readonly PeerIceServer[];\n /** When the link may use the server's relay. Default `\"auto\"`. */\n readonly relay?: RelayMode;\n /**\n * Makes the link's `RTCPeerConnection`. Default the platform's\n * `RTCPeerConnection` when it has one; React Native passes\n * react-native-webrtc's, Node a WebRTC package's. A link without one uses\n * the relay.\n */\n readonly peerConnection?: (configuration: PeerConnectionConfig) => unknown;\n}\n\n/** The STUN servers a link asks by default. */\nexport const DEFAULT_ICE_SERVERS: readonly PeerIceServer[] = Object.freeze([\n { urls: \"stun:stun.l.google.com:19302\" },\n { urls: \"stun:stun.cloudflare.com:3478\" },\n]);\n\n/** A link that opens on no path within this long closes as `timeout`. */\nexport const OPEN_TIMEOUT_MS = 15_000;\n\n/** A direct path not open within this long gives way to the relay. */\nexport const DIRECT_TIMEOUT_MS = 5000;\n\n/** A direct path whose channel closed waits this long for the peer's bye before it gives way. */\nexport const BYE_WAIT_MS = 2000;\n\ntype Factory = (configuration: PeerConnectionConfig) => unknown;\n\n/** What one link keeps besides its core. */\ninterface Run {\n readonly hub: PeerHub;\n readonly room: string;\n readonly peer: string;\n /** This side offers; it stops when both offered and it yields. */\n offerer: boolean;\n readonly relay: RelayMode;\n readonly factory: Factory | undefined;\n readonly iceServers: PeerIceServer[];\n readonly core: LinkCore;\n pair: Bucket;\n started: boolean;\n /** Signals that arrived before the link started, in order. */\n readonly queued: [PeerSignal, string][];\n /** The peer's candidates that came before the direct path was made. */\n readonly early: PeerCandidate[];\n /** The peer offered: it does so once a link. */\n heardOffer: boolean;\n direct: DirectPath | undefined;\n relaySent: boolean;\n relayHeard: boolean;\n openTimer: ReturnType<typeof setTimeout> | undefined;\n directTimer: ReturnType<typeof setTimeout> | undefined;\n /** Waits for the peer's bye after the direct path's channel closed. */\n byeTimer: ReturnType<typeof setTimeout> | undefined;\n /** Signals waiting out a `RATE_LIMITED` answer, cleared when the link closes. */\n readonly retries: Set<ReturnType<typeof setTimeout>>;\n remove: () => void;\n}\n\n/** The platform's `RTCPeerConnection`, as a factory; `undefined` where there is none (Node). */\nfunction platformFactory(): Factory | undefined {\n const Rtc = (\n globalThis as { RTCPeerConnection?: new (configuration: PeerConnectionConfig) => unknown }\n ).RTCPeerConnection;\n return Rtc === undefined ? undefined : (configuration) => new Rtc(configuration);\n}\n\nfunction canDirect(run: Run): boolean {\n return run.relay !== \"always\" && run.factory !== undefined;\n}\n\n/** How long a rate-limited signal waits before it is sent again: the server's wait, within the open timeout. */\nfunction retryDelay(data: unknown): number {\n const wait = (data as { readonly retryAfterMs?: unknown } | undefined)?.retryAfterMs;\n return typeof wait === \"number\" && wait > 0 ? Math.min(wait, OPEN_TIMEOUT_MS) : 100;\n}\n\n/**\n * Sends a signal; a refusal closes the link, but for a candidate (one lost\n * candidate is no loss). A rate-limited signal is sent again, at most three\n * times, unless the link closed meanwhile.\n */\nfunction signal(run: Run, sig: PeerSignal, attempt = 0): void {\n void run.hub.signal(run.room, run.peer, sig).then((answer) => {\n if (answer.ok || run.core.link.state === \"closed\" || sig.ice !== undefined) {\n return;\n }\n const { code, data } = answer.e;\n if (code === \"RATE_LIMITED\" && attempt < 3) {\n const retry = setTimeout(() => {\n run.retries.delete(retry);\n signal(run, sig, attempt + 1);\n }, retryDelay(data));\n run.retries.add(retry);\n } else if (code !== \"TIMEOUT\") {\n run.core.fail(\"refused\");\n }\n });\n}\n\n/** Takes the direct path off the link, and closes it once `after` settled (at once without). */\nfunction closeDirect(run: Run, after?: Promise<unknown>): void {\n clearTimeout(run.directTimer);\n const { direct } = run;\n run.direct = undefined;\n if (after === undefined) {\n direct?.close();\n } else {\n const close = (): void => {\n direct?.close();\n };\n void after.then(close, close);\n }\n}\n\n/** Moves the link to the relay, saying so once; it carries the link once both sides said it. */\nfunction moveToRelay(run: Run): void {\n clearTimeout(run.byeTimer);\n if (run.relay === \"never\") {\n run.core.fail(\"failed\");\n return;\n }\n closeDirect(run);\n if (!run.relaySent) {\n run.relaySent = true;\n signal(run, { relay: true });\n }\n if (run.relayHeard || run.core.link.state === \"open\") {\n run.core.carry(\"relay\");\n }\n}\n\nfunction directFailed(run: Run): void {\n if (run.relay === \"never\") {\n run.core.fail(run.core.link.state === \"open\" ? \"lost\" : \"failed\");\n } else {\n moveToRelay(run);\n }\n}\n\n/**\n * The direct path's channel closed: the peer may have closed its link, and\n * its bye ends this one. Without one within `BYE_WAIT_MS` the path failed,\n * unless another was made meanwhile.\n */\nfunction awaitBye(run: Run): void {\n clearTimeout(run.byeTimer);\n run.byeTimer = setTimeout(() => {\n run.byeTimer = undefined;\n if (run.direct === undefined) {\n directFailed(run);\n }\n }, BYE_WAIT_MS);\n}\n\n/** Closes a connection whose direct path could not be made; one that throws is unusable already. */\nfunction discard(rtc: RtcConnection | undefined): void {\n try {\n rtc?.close();\n } catch {\n // Nothing more to take down.\n }\n}\n\n/** Starts the direct path, or `undefined` when the factory or the path's channels throw. */\nfunction openDirect(run: Run, factory: Factory): DirectPath | undefined {\n let rtc: RtcConnection | undefined;\n let direct: DirectPath;\n try {\n rtc = factory({ iceServers: run.iceServers }) as RtcConnection;\n direct = createDirectPath(rtc, {\n signal: (sig) => {\n if (run.direct === direct) {\n signal(run, sig);\n }\n },\n opened: () => {\n clearTimeout(run.directTimer);\n run.core.carry(\"direct\");\n },\n failed: (channelClosed) => {\n if (run.direct !== direct) {\n return;\n }\n run.direct = undefined;\n if (channelClosed) {\n clearTimeout(run.directTimer);\n awaitBye(run);\n } else {\n directFailed(run);\n }\n },\n message: (data, label) => {\n run.core.receive(data, label);\n },\n });\n } catch {\n discard(rtc);\n return undefined;\n }\n run.direct = direct;\n for (const ice of run.early.splice(0)) {\n direct.candidate(ice);\n }\n if (run.relay !== \"never\") {\n run.directTimer = setTimeout(() => {\n if (run.direct === direct && !direct.open) {\n directFailed(run);\n }\n }, DIRECT_TIMEOUT_MS);\n }\n return direct;\n}\n\n/**\n * Whether this side, an offerer the peer sent an offer, answers it instead:\n * both sides offered, each having learned of the other at once, and the\n * side whose socket id sorts after the other's yields. The other ignores\n * the offer and waits for the answer. A side that said `{ relay: true }`\n * yields to nothing: the peer moves to the relay when it hears it.\n */\nfunction yields(run: Run): boolean {\n const self = run.hub.connection.socket.id;\n return !run.relaySent && self !== undefined && self > run.peer;\n}\n\n/** The peer's session description: an answer to the offer, or an offer to answer. */\nfunction described(run: Run, sdp: PeerDescription): void {\n if (sdp.type === \"answer\") {\n if (run.offerer) {\n run.direct?.accept(sdp);\n }\n return;\n }\n if (run.offerer) {\n if (!yields(run)) {\n return;\n }\n closeDirect(run);\n run.offerer = false;\n run.heardOffer = true;\n }\n if (run.relaySent || run.direct !== undefined) {\n return;\n }\n const direct =\n canDirect(run) && run.factory !== undefined ? openDirect(run, run.factory) : undefined;\n if (direct === undefined) {\n moveToRelay(run);\n } else {\n direct.answer(sdp);\n }\n}\n\n/** Candidates kept for a direct path not made yet. */\nconst MAX_EARLY_CANDIDATES = 64;\n\n/**\n * Whether `sig` opens a new link rather than continuing this one: the peer\n * offers once a link and says `{ relay: true }` once, and never offers\n * after it, so a second offer, a second `{ relay: true }` or an offer after\n * it means this side missed the bye of the peer's last link.\n */\nfunction restarts(run: Run, sig: PeerSignal): boolean {\n if (run.offerer) {\n return false;\n }\n if (sig.relay === true) {\n return run.relayHeard;\n }\n if (sig.sdp?.type !== \"offer\") {\n return false;\n }\n const again = run.heardOffer || run.relayHeard;\n run.heardOffer = true;\n return again;\n}\n\n/** A signal from the peer; false when it opens a new link, so this one ended and the hub takes it. */\nfunction onSignal(run: Run, sig: PeerSignal, userId: string): boolean {\n if (!run.started) {\n run.queued.push([sig, userId]);\n return true;\n }\n if (restarts(run, sig)) {\n run.core.end(\"bye\");\n return false;\n }\n if (userId !== \"\") {\n run.core.identify(userId);\n }\n if (sig.bye === true) {\n run.core.end(\"bye\");\n } else if (sig.relay === true) {\n run.relayHeard = true;\n moveToRelay(run);\n } else if (sig.sdp !== undefined) {\n described(run, sig.sdp);\n } else if (sig.ice !== undefined) {\n if (run.direct !== undefined) {\n run.direct.candidate(sig.ice);\n } else if (!run.relaySent && run.early.length < MAX_EARLY_CANDIDATES) {\n run.early.push(sig.ice);\n }\n }\n return true;\n}\n\n/** The connection said hello: start, with what waited for the link. */\nfunction start(run: Run, limits: HelloPeerLimits | undefined): void {\n if (run.core.link.state === \"closed\") {\n return;\n }\n if (limits === undefined || (!canDirect(run) && run.relay === \"never\")) {\n run.core.fail(\"unsupported\");\n return;\n }\n run.started = true;\n run.pair = run.hub.pairBucket();\n run.openTimer = setTimeout(() => {\n if (run.core.link.state === \"connecting\") {\n run.core.fail(\"timeout\");\n }\n }, OPEN_TIMEOUT_MS);\n const waiting = run.hub.take(run.room, run.peer);\n if (run.offerer) {\n const direct =\n canDirect(run) && run.factory !== undefined ? openDirect(run, run.factory) : undefined;\n if (direct === undefined) {\n moveToRelay(run);\n } else {\n direct.offer();\n }\n }\n for (const [sig, userId] of [\n ...waiting.signals.map((waited) => [waited, waiting.userId] as const),\n ...run.queued.splice(0),\n ]) {\n if (!onSignal(run, sig, userId)) {\n return;\n }\n }\n}\n\nfunction sendOn(run: Run, label: PeerLabel, text: string): boolean {\n if (run.direct?.open === true) {\n return run.direct.send(label, text);\n }\n return run.relaySent && run.hub.relay(run.room, run.peer, label, text, run.pair);\n}\n\nfunction closed(run: Run, fromHere: boolean): void {\n clearTimeout(run.openTimer);\n clearTimeout(run.byeTimer);\n for (const retry of run.retries) {\n clearTimeout(retry);\n }\n run.retries.clear();\n run.remove();\n // Signals the server took hold a place for the pair there, a refused link's\n // too: the bye frees it. A link that never started sent none. The direct\n // path closes once the server answered the bye, so the peer hears the bye\n // first and does not take its channels closing for a lost path.\n const bye =\n fromHere && run.started ? run.hub.signal(run.room, run.peer, { bye: true }) : undefined;\n closeDirect(run, bye);\n}\n\nconst RELAY_MODES: readonly string[] = [\"auto\", \"always\", \"never\"];\n\nfunction checkOptions(options: PeerLinkOptions): void {\n if (!isRoomName(options.room)) {\n throw new TypeError(\"createPeerLink: room must name an app room, of 1 to 256 characters\");\n }\n if (!isSocketId(options.to)) {\n throw new TypeError(\"createPeerLink: to must be the peer's socket id\");\n }\n if (typeof options.offerer !== \"boolean\") {\n throw new TypeError(\n \"createPeerLink: offerer must be true on the side that offers, false on the other\",\n );\n }\n if (options.relay !== undefined && !RELAY_MODES.includes(options.relay)) {\n throw new TypeError('createPeerLink: relay must be \"auto\", \"always\" or \"never\"');\n }\n if (options.peerConnection !== undefined && typeof options.peerConnection !== \"function\") {\n throw new TypeError(\"createPeerLink: peerConnection must make an RTCPeerConnection\");\n }\n}\n\n/**\n * Opens a link to socket `options.to` in app room `options.room`, over\n * WebRTC data channels when it can and the server's relay when it cannot.\n * The server must serve peer links (`createServer({ peers: true })`), and\n * both sockets must be in the room. Throws a `TypeError` for bad options,\n * or when the connection has a link to that peer in that room already.\n *\n * @example\n * const link = createPeerLink(connection, { room: \"match:7\", to: rivalSocketId, offerer: true });\n * link.on(\"message\", (data, label) => applyRemote(data));\n * link.on(\"open\", () => link.send(\"rel\", { hello: \"rival\" }));\n */\nexport function createPeerLink(\n connection: QuickdrawConnection,\n options: PeerLinkOptions,\n): PeerLink {\n checkOptions(options);\n const hub = hubOf(connection);\n const run: Run = {\n hub,\n room: options.room,\n peer: options.to,\n offerer: options.offerer,\n relay: options.relay ?? \"auto\",\n factory: options.peerConnection ?? platformFactory(),\n iceServers: (options.iceServers ?? DEFAULT_ICE_SERVERS).map((server) => ({ ...server })),\n core: createLinkCore({\n room: options.room,\n peer: options.to,\n maxMessageBytes: () => hub.limits().maxMessageBytes,\n hooks: {\n send: (label, text) => sendOn(run, label, text),\n closed: (_reason, fromHere) => {\n closed(run, fromHere);\n },\n },\n }),\n pair: hub.pairBucket(),\n started: false,\n queued: [],\n early: [],\n heardOffer: false,\n direct: undefined,\n relaySent: false,\n relayHeard: false,\n openTimer: undefined,\n directTimer: undefined,\n byeTimer: undefined,\n retries: new Set(),\n remove: () => undefined,\n };\n run.remove = hub.add(run.room, run.peer, {\n signal: (sig, userId) => onSignal(run, sig, userId),\n relay: (label, text) => {\n run.core.receive(text, label);\n },\n disconnected: () => {\n run.core.end(\"disconnected\");\n },\n left: () => {\n run.core.end(\"left\");\n },\n });\n hub.whenReady(\n (limits) => {\n start(run, limits);\n },\n () => {\n run.core.end(\"disconnected\");\n },\n );\n return run.core.link;\n}\n\n/** A socket of the room signalled this one with no link to it: a link it asks for. */\nexport interface PeerRequest {\n readonly room: string;\n /** The peer's socket id. */\n readonly from: string;\n /** The peer's user, as the server named it. */\n readonly userId: string;\n /** Answers with a link: `createPeerLink(connection, { room, to: from, offerer: false, ...options })`. */\n accept(options?: Omit<PeerLinkOptions, \"room\" | \"to\" | \"offerer\">): PeerLink;\n /** Refuses: the peer's link closes, with `bye`. */\n reject(): void;\n}\n\n/**\n * Calls `listener` when a socket of an app room this connection's socket is\n * in signals it with no link to it yet: answer with `request.accept()` (or\n * `createPeerLink` with `offerer: false`) within 15 seconds, or\n * `request.reject()`. Returns its removal.\n */\nexport function onPeerRequest(\n connection: QuickdrawConnection,\n listener: (request: PeerRequest) => void,\n): () => void {\n const hub = hubOf(connection);\n return hub.requests((incoming: IncomingPeer) => {\n listener(\n Object.freeze({\n ...incoming,\n accept: (options = {}) =>\n createPeerLink(connection, {\n ...options,\n room: incoming.room,\n to: incoming.from,\n offerer: false,\n }),\n reject: () => {\n hub.forget(incoming.room, incoming.from);\n void hub.signal(incoming.room, incoming.from, { bye: true });\n },\n }),\n );\n });\n}\n"],"mappings":"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","names":["closed","entry","MAX_EARLY_CANDIDATES"]}
@@ -17,6 +17,34 @@ function storageOf(db) {
17
17
  function modelKey(model) {
18
18
  return model.length === 0 ? model : `${model.charAt(0).toLowerCase()}${model.slice(1)}`;
19
19
  }
20
+ function isLink(value) {
21
+ const link = value ?? {};
22
+ return typeof link.model === "string" && link.model.length > 0 && typeof link.entry === "string" && link.entry.length > 0 && Array.isArray(link.columns) && link.columns.every((column) => typeof column === "string");
23
+ }
24
+ function createLinkRegistry() {
25
+ const links = /* @__PURE__ */ new Map();
26
+ const register = (model, link) => {
27
+ if (typeof model !== "string" || model.length === 0) {
28
+ throw new TypeError("registerLinks: model must be a model name");
29
+ }
30
+ if (!isLink(link)) {
31
+ throw new TypeError(
32
+ `registerLinks: the link of ${model} needs { model, entry, columns }: the junction model, its column holding the entry's id, and the columns to read`
33
+ );
34
+ }
35
+ const key = modelKey(model);
36
+ const junction = modelKey(link.model);
37
+ const known = links.get(key) ?? [];
38
+ const same = known.find((other) => other.model === junction && other.entry === link.entry);
39
+ const columns = Object.freeze([.../* @__PURE__ */ new Set([...same?.columns ?? [], ...link.columns])]);
40
+ const merged = Object.freeze({ model: junction, entry: link.entry, columns });
41
+ links.set(key, Object.freeze([...known.filter((other) => other !== same), merged]));
42
+ };
43
+ return Object.freeze({
44
+ register,
45
+ of: (model) => links.get(modelKey(model)) ?? []
46
+ });
47
+ }
20
48
  function createInterestRegistry(initial = {}) {
21
49
  const columns = /* @__PURE__ */ new Map();
22
50
  const register = (model, added) => {
@@ -47,7 +75,8 @@ export {
47
75
  STORAGE_KEY,
48
76
  storageOf,
49
77
  modelKey,
78
+ createLinkRegistry,
50
79
  createInterestRegistry,
51
80
  ANY_FIELD
52
81
  };
53
- //# sourceMappingURL=chunk-H23KEFBA.js.map
82
+ //# sourceMappingURL=chunk-DUBU3BB3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/storage.ts","../src/server/uow/types.ts"],"sourcesContent":["// The storage adapter (RFC 0003 section 5.4): the one structural interface\n// the server reads the database through, and through which it learns of\n// writes. `trackPrisma` (`./prisma`) is the Prisma implementation; a Drizzle\n// adapter would be a second one. The framework never imports generated\n// Prisma types: rows are plain records, and `where`, `select` and `orderBy`\n// take Prisma's object shapes.\n\nimport type { StatementCount } from \"./uow/unitOfWork\";\nimport type { UnitOfWorkFactory, WriteRecord } from \"./uow/types\";\n\n/** A row as the adapter returns it. */\nexport type StorageRow = Readonly<Record<string, unknown>>;\n\n/** A filter, in Prisma's `where` shape: `{ projectId: \"p1\", status: { in: [\"open\"] } }`. */\nexport type StorageWhere = Readonly<Record<string, unknown>>;\n\n/** What `findMany` reads. */\nexport interface FindManyArgs {\n readonly where?: StorageWhere;\n /** The columns (and relations) to read, in Prisma's `select` shape: `{ id: true, title: true }`. */\n readonly select?: Readonly<Record<string, unknown>>;\n /** Prisma's `orderBy`: `[{ ordinal: \"asc\" }, { id: \"asc\" }]`. */\n readonly orderBy?:\n | Readonly<Record<string, unknown>>\n | readonly Readonly<Record<string, unknown>>[];\n readonly take?: number;\n}\n\n/** What `count` counts. */\nexport interface CountArgs {\n readonly where?: StorageWhere;\n}\n\n/**\n * The database as the server sees it. Models are named as the client names\n * them (`\"task\"`). Reads made inside an open interactive transaction go\n * through that transaction, so they see its writes.\n */\nexport interface StorageAdapter {\n findMany(model: string, args?: FindManyArgs): Promise<StorageRow[]>;\n count(model: string, args?: CountArgs): Promise<number>;\n /**\n * Calls `listener` with every write the adapter records, once it is\n * durable: when it lands in a unit of work, or flushes on its own. Writes\n * inside a transaction arrive when it commits; a rolled-back write never\n * arrives. Returns a function that removes the listener.\n */\n onWrite(listener: (write: WriteRecord) => void): () => void;\n /** Runs `fn` and counts the database statements it issued, the adapter's own reads included. */\n countStatements<T>(fn: () => T | PromiseLike<T>): Promise<StatementCount<T>>;\n /**\n * Declares the columns of `model` whose values the framework needs around\n * a write (scope, membership and owner columns). An update or `updateMany`\n * whose `data` sets one reads its old value first; every write reports\n * their values in `before` and `after`.\n */\n registerInterest(model: string, columns: readonly string[]): void;\n /** The columns registered for `model`. */\n interestOf(model: string): readonly string[];\n /**\n * Declares that rows of `model` are the entries of a `via` collection over\n * the junction `link` names: a `delete` or `deleteMany` of `model` reads\n * the junction rows linking the rows it deletes just before, through the\n * open transaction when there is one, and reports them in each write's\n * `links`, so a flush tells only the scopes a deleted entry was linked to\n * (the 5.1 audit's COLL-3). A link committed between that read and the\n * delete is not read (`prisma/links.ts`). Optional: without it a deleted\n * entry left scopes nobody can name, and every subscribed scope of its\n * collection is told it was removed.\n */\n registerLinks?(model: string, link: LinkInterest): void;\n /** The junctions registered for `model` (`registerLinks`). */\n linksOf?(model: string): readonly LinkInterest[];\n /**\n * True where the caller runs inside an open transaction of this adapter,\n * whose reads see writes that may still roll back. The access cache\n * neither reads nor keeps anything there; an adapter without this method\n * gets no access cache across requests.\n */\n inTransaction?(): boolean;\n /**\n * Whether `column` of `model` may hold null. Collection cursors need it: a\n * keyset over a nullable column must place and compare nulls, and Prisma\n * refuses a null filter or null ordering on a required column. An adapter\n * without it has its order columns treated as never null, except one a\n * cursor shows holding null.\n */\n nullable?(model: string, column: string): Promise<boolean>;\n /**\n * Whether the adapter can key `model`'s writes: false for a model without\n * an `id` column (a table keyed by a composite `@@id`), whose writes are\n * never tracked. `createServer` asks it, at start, for every model an\n * access policy, a membership table or a `via` junction depends on, and\n * names each it cannot track (the `untracked-access-model` warning). An\n * adapter without it is not asked.\n */\n trackable?(model: string): Promise<boolean>;\n /** The units of work that record this adapter's writes; the dispatcher uses them. */\n readonly unitOfWork: UnitOfWorkFactory;\n}\n\n/** The property a tracked database client carries its storage adapter under. */\nexport const STORAGE_KEY = \"$quickdrawStorage\";\n\nfunction isStorageAdapter(value: unknown): value is StorageAdapter {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Partial<Record<keyof StorageAdapter, unknown>>;\n return (\n typeof candidate.findMany === \"function\" &&\n typeof candidate.count === \"function\" &&\n typeof candidate.countStatements === \"function\" &&\n typeof candidate.unitOfWork === \"object\" &&\n candidate.unitOfWork !== null\n );\n}\n\n/**\n * The storage adapter of a tracked database client (`trackPrisma(prisma)`),\n * or `undefined` for any other value. A client extended again after\n * `trackPrisma` still carries it.\n */\nexport function storageOf(db: unknown): StorageAdapter | undefined {\n if ((typeof db !== \"object\" && typeof db !== \"function\") || db === null) {\n return undefined;\n }\n const storage: unknown = Reflect.get(db, STORAGE_KEY);\n return isStorageAdapter(storage) ? storage : undefined;\n}\n\n/** Normalizes a model name to the client's spelling: `\"TaskLabel\"` and `\"taskLabel\"` are both `\"taskLabel\"`. */\nexport function modelKey(model: string): string {\n return model.length === 0 ? model : `${model.charAt(0).toLowerCase()}${model.slice(1)}`;\n}\n\n/**\n * A junction whose rows link an entry model's rows to the scopes of a `via`\n * collection (`registerLinks`): its model, as the client names it, the\n * column holding the entry's id, and the columns a delete reads with it (the\n * scope column, and the role columns of a members table's rule).\n */\nexport interface LinkInterest {\n readonly model: string;\n readonly entry: string;\n readonly columns: readonly string[];\n}\n\n/** The junctions registered per entry model, as `registerLinks` records them. */\nexport interface LinkRegistry {\n register(model: string, link: LinkInterest): void;\n of(model: string): readonly LinkInterest[];\n}\n\nfunction isLink(value: unknown): value is LinkInterest {\n const link = (value ?? {}) as Partial<Record<keyof LinkInterest, unknown>>;\n return (\n typeof link.model === \"string\" &&\n link.model.length > 0 &&\n typeof link.entry === \"string\" &&\n link.entry.length > 0 &&\n Array.isArray(link.columns) &&\n link.columns.every((column) => typeof column === \"string\")\n );\n}\n\n/**\n * A link registry. Two registrations of one entry model over the same\n * junction and entry column (two `via` collections over one junction) are\n * one link, their columns merged, so a delete reads that junction once.\n */\nexport function createLinkRegistry(): LinkRegistry {\n const links = new Map<string, readonly LinkInterest[]>();\n const register = (model: string, link: LinkInterest): void => {\n if (typeof model !== \"string\" || model.length === 0) {\n throw new TypeError(\"registerLinks: model must be a model name\");\n }\n if (!isLink(link)) {\n throw new TypeError(\n `registerLinks: the link of ${model} needs { model, entry, columns }: the junction model, its column holding the entry's id, and the columns to read`,\n );\n }\n const key = modelKey(model);\n const junction = modelKey(link.model);\n const known = links.get(key) ?? [];\n const same = known.find((other) => other.model === junction && other.entry === link.entry);\n const columns = Object.freeze([...new Set([...(same?.columns ?? []), ...link.columns])]);\n const merged = Object.freeze({ model: junction, entry: link.entry, columns });\n links.set(key, Object.freeze([...known.filter((other) => other !== same), merged]));\n };\n return Object.freeze({\n register,\n of: (model: string): readonly LinkInterest[] => links.get(modelKey(model)) ?? [],\n });\n}\n\n/** The columns registered per model, as `registerInterest` records them. */\nexport interface InterestRegistry {\n register(model: string, columns: readonly string[]): void;\n of(model: string): readonly string[];\n}\n\n/** An interest registry, seeded with `initial` (`{ task: [\"projectId\", \"status\"] }`). */\nexport function createInterestRegistry(\n initial: Readonly<Record<string, readonly string[]>> = {},\n): InterestRegistry {\n const columns = new Map<string, readonly string[]>();\n const register = (model: string, added: readonly string[]): void => {\n if (typeof model !== \"string\" || model.length === 0) {\n throw new TypeError(\"registerInterest: model must be a model name\");\n }\n if (!Array.isArray(added) || added.some((column) => typeof column !== \"string\")) {\n throw new TypeError(`registerInterest: the columns of ${model} must be an array of names`);\n }\n const key = modelKey(model);\n const merged = new Set([...(columns.get(key) ?? []), ...added]);\n merged.delete(\"id\");\n columns.set(key, Object.freeze([...merged]));\n };\n for (const [model, added] of Object.entries(initial)) {\n register(model, added);\n }\n return Object.freeze({\n register,\n of: (model: string) => columns.get(modelKey(model)) ?? [],\n });\n}\n","// The unit of work seam (RFC 0003 section 5.1). Every method call runs its\n// handler inside a unit of work, which the tracked database client records\n// writes into. After the response is sent, the dispatcher flushes it, and\n// the flush turns those writes into entity frames, collection deltas and\n// change signals (section 5.3).\n//\n// The dispatcher's default (`untracked.ts`) awaits the handler inside its\n// scope and records nothing. A tracked database client (`trackPrisma` on\n// `./prisma`) brings the tracked implementation (`unitOfWork.ts`), which the\n// dispatcher picks up from the client without touching the pipeline.\n\nimport type { Logger } from \"../../contract/logger\";\nimport type { MethodKind } from \"../../contract/methods\";\nimport type { TouchOptions } from \"../context\";\nimport type { DevWarnings } from \"../devWarnings\";\nimport type { Transport } from \"../types\";\nimport type { FlushSink } from \"./flushSink\";\n\n/**\n * The `fields` of a write whose changed fields are unknown, such as a\n * `ctx.touch`: any field may have changed. No column can have this name.\n */\nexport const ANY_FIELD = \"*\";\n\n/**\n * One write a unit of work recorded: `{ model, id, op, fields, before? }`\n * (RFC 0003 section 5.1), plus `after`. The values in `before` and `after`\n * are those of the model's interested columns: the scope, membership and\n * owner columns later cards register with `registerInterest`.\n */\nexport interface WriteRecord {\n /** The database model, named as the client names it: `\"task\"`, `\"taskLabel\"`. */\n readonly model: string;\n /** The written row's id. */\n readonly id: string;\n readonly op: \"create\" | \"update\" | \"delete\";\n /**\n * The fields the write set; `[ANY_FIELD]` when they are unknown. An update\n * whose data held a nested write, which may set a foreign key through a\n * relation, lists every interested column of the model besides its keys.\n */\n readonly fields: readonly string[];\n /**\n * The row before the unit of work wrote it, for its interested columns: an\n * update's old values of the interested columns it changed (read first,\n * narrowly), or of every interested column when its data held a nested\n * write, or a delete's values. A row the unit created has none.\n */\n readonly before?: Readonly<Record<string, unknown>>;\n /** The interested columns' values after the write, when the write returned them. */\n readonly after?: Readonly<Record<string, unknown>>;\n /**\n * On a delete of a model whose junction links are registered\n * (`StorageAdapter.registerLinks`): per junction model, the junction rows\n * that linked the row just before the delete (possibly none), with the\n * registered columns. Absent when the links were not read.\n */\n readonly links?: Readonly<Record<string, readonly Readonly<Record<string, unknown>>[]>>;\n /**\n * Set on a `create` an `upsert` recorded without reading whether its row\n * existed (its `update` set no interested column and held no nested\n * write, or the model has no interested columns): the row may have been\n * updated rather than created. Only merging reads it: a row created and\n * deleted in one unit is dropped from the flush only when its create is\n * certain.\n */\n readonly mayHaveExisted?: true;\n}\n\n/**\n * Where a unit of work's writes come from. A method call's unit names the\n * call's service, method and kind; a `qd.run` block's unit does not.\n */\nexport interface UnitOfWorkScope {\n readonly service?: string;\n readonly method?: string;\n readonly kind?: MethodKind;\n readonly requestId: string;\n readonly transport: Transport;\n /** Where the unit's writes go when it flushes. */\n readonly sink: FlushSink;\n /**\n * The development warnings of the dispatcher whose method call this unit\n * runs: a warning raised inside the call goes there, so only a strict\n * dispatcher's own calls throw. Without them, the attached dispatcher's.\n */\n readonly warnings?: DevWarnings;\n /**\n * A unit of its own even where another unit or a transaction is open (a\n * handler's background work, `qd.run(fn, { detached: true })`): its writes\n * flush when it does, never with the unit around it, and its statements\n * are not counted there. Default `false`: a unit begun inside an open\n * one joins it.\n */\n readonly detached?: boolean;\n}\n\n/**\n * The writes of one handler run. The dispatcher calls `run` once, with the\n * handler, and `flush` once, after the handler has settled and the response\n * has been sent, whether the handler succeeded or failed.\n */\nexport interface UnitOfWork {\n /**\n * Runs `fn` inside this unit's scope and awaits its result inside that\n * scope. Prisma promises are lazy: one returned without being awaited would\n * otherwise run outside the scope and escape tracking.\n */\n run<T>(fn: () => T | PromiseLike<T>): Promise<T>;\n /**\n * Hands the recorded writes to the scope's sink. A failure is logged by\n * the dispatcher and never fails the response, which was already sent.\n */\n flush(): Promise<void>;\n /** Database statements the run issued, for the completion record; `undefined` when not counted. */\n readonly sqlStatements: number | undefined;\n}\n\n/** Opens the unit of work of each handler run. A shared query run has one unit for all its callers. */\nexport interface UnitOfWorkFactory {\n begin(scope: UnitOfWorkScope): UnitOfWork;\n /**\n * Records writes the database client cannot see (`ctx.touch`) as if the\n * client had made them where `touch` is called: into the open transaction\n * or unit of work there, or, outside any, flushed on their own. A factory\n * that tracks nothing leaves it out, and `ctx.touch` then does nothing.\n */\n touch?(model: string, ids: readonly string[], options?: TouchOptions): void;\n /**\n * Called by each dispatcher created with this factory: writes made outside\n * any unit of work flush to `sink`, and development warnings raised\n * outside a method call go to the dispatcher's `warnings` (or, without\n * them, to `logger`), never strictly. The dispatcher attached last wins.\n * Returns the function that detaches it again (a server's `close()`\n * calls it), restoring the dispatcher attached before.\n */\n attach?(sink: FlushSink, logger: Logger, warnings?: DevWarnings): (() => void) | void;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,19 @@
1
+ // src/contract/realtime.ts
2
+ var GLOBAL_STREAM = "global";
3
+ var STREAM_MAX_SEED = 1e3;
4
+ var CHANNEL_DEFAULT_RATE = 30;
5
+ function isHostServed(def) {
6
+ return def.serve === "host";
7
+ }
8
+ function isScopedStream(def) {
9
+ return def.scope !== void 0 && def.scope !== GLOBAL_STREAM;
10
+ }
11
+
12
+ export {
13
+ GLOBAL_STREAM,
14
+ STREAM_MAX_SEED,
15
+ CHANNEL_DEFAULT_RATE,
16
+ isHostServed,
17
+ isScopedStream
18
+ };
19
+ //# sourceMappingURL=chunk-GYLWEJHW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contract/realtime.ts"],"sourcesContent":["// Streams, channels and typed room events in a contract (RFC 0003 section\n// 12.5). A stream is server-to-client append-only data a subscriber receives\n// with a seed of recent items; a channel is fire-and-forget client-to-server\n// input (cursors, typing) behind a per-socket token bucket; an event is a\n// typed custom frame a handler sends to a room. 4.1 declared channels on the\n// service class (4.1 `src/server/BaseService.ts:915-942`) and typed room\n// events through the augmentable `QuickdrawEventMap`; here all three live in\n// the contract, so the client is typed from it.\n//\n// Methods, collections, streams, channels and events share one namespace:\n// the client exposes each as `qd.<service>.<name>`.\n\nimport type { AccessLevel } from \"./access\";\nimport type { AnyContract } from \"./defineContract\";\nimport type { StandardSchemaV1 } from \"./standardSchema\";\n\n/** The `scope` of a stream with one feed for the whole service: the default. */\nexport const GLOBAL_STREAM = \"global\";\n\n/** The most items a stream may keep per scope as the seed for new subscribers. */\nexport const STREAM_MAX_SEED = 1000;\n\n/** A channel's sustained rate when it declares none: messages per second per socket. */\nexport const CHANNEL_DEFAULT_RATE = 30;\n\n/**\n * The app room a stream's subscriber must be in (`access: { room }`): for a\n * global stream the room's name, or `{ prefix }` for any room whose name\n * starts with it; for a scoped stream a function of the scope\n * (`(worldId) => \\`world:${worldId}\\``), since a name or a prefix would let\n * a socket in that room read every scope (`defineContract` refuses them\n * there).\n */\nexport type StreamRoom = string | RoomPrefix | ((scope: string) => string | null | undefined);\n\n/**\n * Who may subscribe to a stream (RFC 0003 section 4.1's forms, as data). A\n * scoped stream's scope value is the row an `entry` or `scope` form is about:\n * `{ entry: L }` needs level `L` on the row of this service whose id is the\n * scope, `{ scope: L, of }` level `L` on that row of `of`'s service.\n * `{ room }` opens it to the sockets in an app room a method joined them to\n * (signed in or not), as a channel's `requires: { room }` does its messages:\n * a socket that leaves the room, or is taken out of it, leaves the feed too.\n * A scoped stream computes that room from its scope; a name or `{ prefix }`\n * serves a global stream only. A stream that declares none is closed: no\n * client may subscribe.\n */\nexport type StreamAccess =\n | \"public\"\n | \"authenticated\"\n | {\n readonly service: AccessLevel;\n readonly entry?: undefined;\n readonly scope?: undefined;\n readonly of?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly entry: AccessLevel;\n readonly service?: AccessLevel;\n readonly scope?: undefined;\n readonly of?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly scope: AccessLevel;\n readonly of: AnyContract;\n readonly service?: undefined;\n readonly entry?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly room: StreamRoom;\n readonly service?: undefined;\n readonly entry?: undefined;\n readonly scope?: undefined;\n readonly of?: undefined;\n };\n\n/**\n * Who serves a channel or a stream: the server, or the host of the app room\n * it names (host mode, RFC 0003 section 12.5), a client the app chose with\n * `ctx.rooms.setHost`, over its peer links.\n */\nexport type RealtimeServe = \"server\" | \"host\";\n\n// `serve` is typed `RealtimeServe | (string & {})`, not `RealtimeServe`: a\n// literal union there stops TypeScript inferring a contract whose\n// channel's `requires.room` is a function with an unannotated parameter.\n// Editors still offer the two values, and `defineContract` refuses any\n// other.\n\n/** A server-to-client stream of append-only items (RFC 0003 section 12.5). */\nexport interface StreamDef<Item extends StandardSchemaV1 = StandardSchemaV1> {\n /** One item's schema. Pushed items are checked against it before they are sent. */\n readonly item: Item;\n /**\n * What a scope value is (`\"taskId\"`): the stream has one feed per scope.\n * `\"global\"`, the default, makes one feed for the whole service.\n */\n readonly scope?: string;\n /**\n * How many of the latest items the server keeps per scope, in memory on\n * each process, and sends a new subscriber first. Default 0; at most 1,000.\n */\n readonly seed?: number;\n /** Send items volatile: dropped for a client whose connection is backed up. Default `false`. */\n readonly volatile?: boolean;\n /** Who may subscribe. Without it the stream is closed. */\n readonly access?: StreamAccess;\n /**\n * Who pushes the items: `\"server\"`, the default, or `\"host\"`, the host of\n * the room `access: { room }` computes from the scope. The host's client\n * pushes them over its peer links, keeping the latest `seed` per scope for\n * a peer that subscribes later, and the server never sees them. A\n * host-served stream has a scope and `access: { room: (scope) => room }`.\n */\n readonly serve?: RealtimeServe | (string & Record<never, never>);\n /** What the stream carries, in a sentence or two, for the generated docs. */\n readonly describe?: string;\n}\n\n/**\n * Where a channel requirement finds its row id or scope value in the parsed\n * payload: the name of a key holding a string, or a function of the payload.\n */\nexport type PayloadSelector = string | ((payload: never) => string | null | undefined);\n\n/**\n * Any app room whose name starts with `prefix` (`{ prefix: \"world:\" }`, a\n * game of many worlds): the sending socket passes when it is in one, and\n * the channel's handler gets the one it matched as `ctx.room`, so the\n * payload need not repeat which room it is for.\n */\nexport interface RoomPrefix {\n readonly prefix: string;\n}\n\n/**\n * The app room a channel requirement names: the room itself (`\"world\"`, a\n * game's one world), a function of the parsed payload that returns it\n * (`(payload) => \\`lobby:${payload.lobbyId}\\``), or `{ prefix }`, any room\n * whose name starts with it (`{ prefix: \"world:\" }`). Unlike a\n * {@link PayloadSelector}, a string here is the room's name, not a payload\n * key. A name starting with `qd:` or `user:`, or one with the shape of a\n * Socket.IO socket id (20 letters, digits, `_` or `-`), is never an app\n * room: a literal one (or such a prefix) is refused when the contract is\n * defined, and a computed one drops the message.\n */\nexport type RoomSelector = string | ((payload: never) => string | null | undefined) | RoomPrefix;\n\n/**\n * What a channel message requires of the socket that sends it: a live\n * subscription (`qd:sub`) to the row of this service `entity` names, or\n * (`qd:col:sub`) to the scope of `collection` that `scope` names; or that\n * the socket is in the app room `room` names (or, for `{ prefix }`, in any\n * app room whose name starts with it), which a method called over that\n * same socket joined with `ctx.rooms.join` (a room another socket of the\n * user joined does not count, and a reconnected socket is in none until it\n * joins again). The handler gets the room that matched as `ctx.room`. Each\n * is checked in memory against the sending socket's own records, on the\n * node it is connected to. A message whose payload names none is dropped.\n */\nexport type ChannelRequires =\n | {\n readonly entity: PayloadSelector;\n readonly collection?: undefined;\n readonly scope?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly collection: string;\n readonly scope: PayloadSelector;\n readonly entity?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly room: RoomSelector;\n readonly entity?: undefined;\n readonly collection?: undefined;\n readonly scope?: undefined;\n };\n\n/** A client-to-server fire-and-forget channel (RFC 0003 section 12.5). */\nexport interface ChannelDef<Payload extends StandardSchemaV1 = StandardSchemaV1> {\n /** One message's schema. A message that fails it is dropped. */\n readonly payload: Payload;\n /** Messages one socket may send per second, sustained. Default 30. */\n readonly ratePerSecond?: number;\n /** How many messages one socket may send at once. Default twice `ratePerSecond`. */\n readonly burst?: number;\n /**\n * What the sender must already be subscribed to, or the app room it must\n * be in. Without it every signed-in user may send the channel, about any\n * row or room its payload names, and the server warns\n * `channel-without-requires` unless the service declares the channel's\n * `access` (`\"authenticated\"` when any signed-in user may send it).\n */\n readonly requires?: ChannelRequires;\n /**\n * Who handles a message: `\"server\"`, the default, or `\"host\"`, the host of\n * the room `requires: { room }` names. The sender's client sends it over\n * its peer link to that host, whose client checks it as the server checks\n * a channel message; the server never sees it. A host-served channel\n * requires a room and takes no server handler.\n */\n readonly serve?: RealtimeServe | (string & Record<never, never>);\n /** What a message says, in a sentence or two, for the generated docs. */\n readonly describe?: string;\n}\n\n/** A typed custom room event, delivered as `qd:event` (RFC 0003 section 8.3). */\nexport interface EventDef<Payload extends StandardSchemaV1 = StandardSchemaV1> {\n readonly payload: Payload;\n /** What the event announces, in a sentence or two, for the generated docs. */\n readonly describe?: string;\n}\n\n/** True for a channel or a stream the room's host serves (`serve: \"host\"`). */\nexport function isHostServed(def: { readonly serve?: unknown }): boolean {\n return def.serve === \"host\";\n}\n\n/** True when a stream has one feed per scope value; false for a global stream. */\nexport function isScopedStream(def: Pick<StreamDef, \"scope\">): boolean {\n return def.scope !== undefined && def.scope !== GLOBAL_STREAM;\n}\n"],"mappings":";AAiBO,IAAM,gBAAgB;AAGtB,IAAM,kBAAkB;AAGxB,IAAM,uBAAuB;AAoM7B,SAAS,aAAa,KAA4C;AACvE,SAAO,IAAI,UAAU;AACvB;AAGO,SAAS,eAAe,KAAwC;AACrE,SAAO,IAAI,UAAU,UAAa,IAAI,UAAU;AAClD;","names":[]}
@@ -3,7 +3,6 @@ import {
3
3
  adminNamespace,
4
4
  createBinding,
5
5
  createInvalidationCoordinator,
6
- createQuickdrawConnection,
7
6
  indexRowFromItem,
8
7
  inertLiveData,
9
8
  invalidateWith,
@@ -13,20 +12,28 @@ import {
13
12
  searchTargetOf,
14
13
  streamMaxOf,
15
14
  viewPredicate
16
- } from "./chunk-OECZPRPW.js";
15
+ } from "./chunk-64I6MYBC.js";
17
16
  import {
18
- MAX_SUBSCRIBE_IDS,
19
- PROTOCOL_VERSION,
20
- QUICKDRAW_VERSION
21
- } from "./chunk-VSQBHPWH.js";
22
- import {
23
- isScopedStream
24
- } from "./chunk-RT6OILOI.js";
17
+ createQuickdrawConnection
18
+ } from "./chunk-STH5DLQP.js";
25
19
  import {
26
20
  buildCaller,
27
21
  methodKey,
28
22
  methodKeyPrefix
29
- } from "./chunk-X5CWPX7H.js";
23
+ } from "./chunk-IIQ3WMJK.js";
24
+ import {
25
+ QUICKDRAW_VERSION
26
+ } from "./chunk-A2RBQD66.js";
27
+ import {
28
+ MAX_ENTITY_IDS_PER_SOCKET,
29
+ MAX_SCOPES_PER_SOCKET,
30
+ MAX_SUBSCRIBE_IDS,
31
+ MAX_TOPICS_PER_SOCKET,
32
+ PROTOCOL_VERSION
33
+ } from "./chunk-X4ZU4QH5.js";
34
+ import {
35
+ isScopedStream
36
+ } from "./chunk-GYLWEJHW.js";
30
37
 
31
38
  // src/testing/mockClient.ts
32
39
  import {
@@ -474,7 +481,13 @@ var MOCK_LIMITS = Object.freeze({
474
481
  maxQueuedQueries: 64,
475
482
  maxSubscribeIds: MAX_SUBSCRIBE_IDS,
476
483
  callTimeoutMs: 3e4,
477
- subscriptions: Object.freeze({ maxInFlight: 8, maxQueued: 64 })
484
+ subscriptions: Object.freeze({
485
+ maxInFlight: 8,
486
+ maxQueued: 64,
487
+ maxEntityIds: MAX_ENTITY_IDS_PER_SOCKET,
488
+ maxScopes: MAX_SCOPES_PER_SOCKET,
489
+ maxTopics: MAX_TOPICS_PER_SOCKET
490
+ })
478
491
  });
479
492
  function checkUser(owner, userId) {
480
493
  if (userId === void 0 || userId === null || typeof userId === "string" && userId !== "") {
@@ -727,10 +740,18 @@ function useMockQuery(queryClient, stub, queryKey, input, options = {}) {
727
740
  function mockQueryMember(context, target, search) {
728
741
  const stub = context.stub(target);
729
742
  const key = (input) => methodKey(target.service, target.method, input);
743
+ const defaults = target.queryDefaults;
730
744
  const member = withStub(
731
745
  {
732
746
  ...search(stub.invoke),
733
- useQuery: (input, options) => useMockQuery(context.queryClient, stub, key(input), input, options),
747
+ // The caller's options over the method's defaults, as on the typed client.
748
+ useQuery: (input, options) => useMockQuery(
749
+ context.queryClient,
750
+ stub,
751
+ key(input),
752
+ input,
753
+ defaults === void 0 ? options : { ...defaults, ...options }
754
+ ),
734
755
  call: (input) => stub.invoke(input),
735
756
  key,
736
757
  setData: (input, updater) => context.queryClient.setQueryData(key(input), updater),
@@ -849,4 +870,4 @@ function createMockClient(contracts, options = {}) {
849
870
  export {
850
871
  createMockClient
851
872
  };
852
- //# sourceMappingURL=chunk-SHEGLZEP.js.map
873
+ //# sourceMappingURL=chunk-I3JCFS7H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing/mockClient.ts","../src/testing/mockLive.ts","../src/testing/mockAdmin.ts","../src/testing/mockScope.ts","../src/testing/mockCollections.ts","../src/testing/mockRealtime.ts","../src/testing/mockSearch.ts","../src/testing/mockSession.tsx"],"sourcesContent":["// `createMockClient(contracts)` (RFC 0003 section 13): a client of the same\n// type as `createQuickdrawClient`'s whose members are stubs, for component\n// tests and stories that do not care about the transport. Nothing connects:\n// there is no socket and no server, and the members' hooks need no provider\n// above them. 4.1's `createMockSocket` mocked the socket under the hooks instead\n// (4.1 `src/client/testing.tsx:24-41`), which tested a fake transport.\n//\n// - A query member's `useQuery` is TanStack's own `useQuery`, on the mock's\n// `QueryClient` (retries off, results kept fresh), fetching from the\n// method's stub; the admin kit's reads of rows default to `staleTime: 0`\n// as on the typed client, so they ask their stub again on every mount.\n// `call` and `prefetch` answer from the stub too. A mutation's\n// `useMutation` is TanStack's `useMutation` over the stub. Optimistic\n// layers are not shown: the test sets the rows a component reads.\n// - Every method's member carries its stub (`mockResolvedValue`,\n// `mockRejectedValue`, `mockImplementation`, `mockReset`, `calls`). A\n// search kit method's `useSearch` asks its stub at once, with no debounce\n// and no collection cache (`mockSearch.ts`).\n// - `useEntity`, `useEntities` and `useCollection` show what the test sets\n// with `mockRow`, `mockRemoved`, `mockError` and `mockScope`\n// (`mockLive.ts`).\n// - `qd.<service>.admin` holds the same mocked members of the admin kit's\n// methods, and `useAdminServices` asks their `adminMeta` stubs\n// (`mockAdmin.ts`).\n// - `qd.invalidate` invalidates the mock's cache through an invalidation\n// coordinator, as the real client does.\n// - `$Provider` makes the mock the provider of what it renders: the real\n// `useQuickdraw()` and `usePresence(room)` read the mock's session\n// (`$session`) and rooms (`$presence`), with no server (`mockSession.tsx`).\n// - Everything set is forgotten after each test, when the test runner has a\n// global `afterEach` (as Testing Library unmounts after each test), and by\n// `$reset()`.\n\nimport {\n QueryClient,\n useMutation,\n useQuery,\n type QueryKey,\n type Updater,\n} from \"@tanstack/react-query\";\nimport { createBinding, invalidateWith, registerQuery, type Binding } from \"../client/binding\";\nimport type { MethodMutationOptions, MethodQueryOptions } from \"../client/hooks\";\nimport { methodKey, methodKeyPrefix, type MethodQueryKey } from \"../client/keys\";\nimport { buildCaller, type MethodTarget } from \"../client/members\";\nimport type { ContractMap } from \"../contract/infer\";\nimport { createMockStore, mockLiveMembers } from \"./mockLive\";\nimport { mockSearchMember } from \"./mockSearch\";\nimport { mockSessionControls, startingSession } from \"./mockSession\";\nimport type { MethodStub, MockClient, MockClientOptions } from \"./mockTypes\";\n\n/** How a stub answers its calls. */\ntype Answer =\n | { readonly kind: \"pending\" }\n | { readonly kind: \"value\"; readonly value: unknown }\n | { readonly kind: \"error\"; readonly error: unknown }\n | { readonly kind: \"implementation\"; readonly run: (input: unknown) => unknown };\n\n/** A method's stub: its controls, and the call that answers as they say. */\ninterface Stub extends MethodStub<unknown, unknown> {\n invoke(input: unknown): Promise<unknown>;\n /** Forgets its answer and its calls without refetching anything: the test is over. */\n forget(): void;\n}\n\nconst PENDING: Answer = Object.freeze({ kind: \"pending\" });\n\n/** The answer of a call no answer was set for: it never settles. */\nconst NEVER = new Promise<never>(() => {\n // Never settles, as a call the server never answers.\n});\n\n/** A stub that calls `changed` whenever its answer changes. */\nfunction createStub(changed: () => void): Stub {\n let answer = PENDING;\n const calls: unknown[] = [];\n const answerWith = (next: Answer): void => {\n answer = next;\n changed();\n };\n return {\n invoke(input) {\n calls.push(input);\n const current = answer;\n switch (current.kind) {\n case \"value\":\n return Promise.resolve(current.value);\n case \"error\":\n return Promise.reject(current.error);\n case \"implementation\":\n return Promise.resolve().then(() => current.run(input));\n default:\n return NEVER;\n }\n },\n mockResolvedValue: (value) => {\n answerWith({ kind: \"value\", value });\n },\n mockRejectedValue: (error) => {\n answerWith({ kind: \"error\", error });\n },\n mockImplementation: (run) => {\n answerWith({ kind: \"implementation\", run });\n },\n mockReset: () => {\n calls.length = 0;\n answerWith(PENDING);\n },\n forget: () => {\n calls.length = 0;\n answer = PENDING;\n },\n get calls() {\n return [...calls];\n },\n };\n}\n\n/** `member` with `stub`'s controls on it, frozen. */\nfunction withStub<T extends object>(member: T, stub: Stub): T {\n return Object.freeze(\n Object.defineProperties(member, {\n mockResolvedValue: { value: stub.mockResolvedValue, enumerable: true },\n mockRejectedValue: { value: stub.mockRejectedValue, enumerable: true },\n mockImplementation: { value: stub.mockImplementation, enumerable: true },\n mockReset: { value: stub.mockReset, enumerable: true },\n calls: { get: () => stub.calls, enumerable: true },\n }),\n );\n}\n\n/** What the members of one mock client share. */\ninterface MockContext {\n readonly queryClient: QueryClient;\n readonly binding: Binding;\n /** A new stub for the method of `target`. */\n stub(target: MethodTarget): Stub;\n}\n\nfunction useMockQuery(\n queryClient: QueryClient,\n stub: Stub,\n queryKey: MethodQueryKey,\n input: unknown,\n options: MethodQueryOptions<unknown> = {},\n) {\n return useQuery(\n { retry: false, ...options, queryKey, queryFn: () => stub.invoke(input) },\n queryClient,\n );\n}\n\nfunction mockQueryMember(\n context: MockContext,\n target: MethodTarget,\n search: (invoke: Stub[\"invoke\"]) => Readonly<Record<string, unknown>>,\n): object {\n const stub = context.stub(target);\n const key = (input?: unknown): MethodQueryKey => methodKey(target.service, target.method, input);\n const defaults = target.queryDefaults;\n const member = withStub(\n {\n ...search(stub.invoke),\n // The caller's options over the method's defaults, as on the typed client.\n useQuery: (input?: unknown, options?: MethodQueryOptions<unknown>) =>\n useMockQuery(\n context.queryClient,\n stub,\n key(input),\n input,\n defaults === undefined ? options : { ...defaults, ...options },\n ),\n call: (input?: unknown): Promise<unknown> => stub.invoke(input),\n key,\n setData: (input: unknown, updater: Updater<unknown, unknown>): unknown =>\n context.queryClient.setQueryData(key(input), updater),\n prefetch: (queryClient: QueryClient, input?: unknown): Promise<void> =>\n queryClient.prefetchQuery({ queryKey: key(input), queryFn: () => stub.invoke(input) }),\n },\n stub,\n );\n registerQuery(context.binding, member, target);\n return member;\n}\n\nfunction useMockMutation(\n queryClient: QueryClient,\n target: MethodTarget,\n stub: Stub,\n options: MethodMutationOptions<unknown, unknown> = {},\n) {\n return useMutation(\n {\n mutationKey: methodKeyPrefix(target.service, target.method),\n ...options,\n mutationFn: (input: unknown) => stub.invoke(input),\n },\n queryClient,\n );\n}\n\nfunction mockMutationMember(context: MockContext, target: MethodTarget): object {\n const stub = context.stub(target);\n return withStub(\n {\n useMutation: (options?: MethodMutationOptions<unknown, unknown>) =>\n useMockMutation(context.queryClient, target, stub, options),\n call: (input?: unknown): Promise<unknown> => stub.invoke(input),\n },\n stub,\n );\n}\n\n/** The cache of a mock client: retries off, results kept fresh. */\nfunction createMockQueryClient(): QueryClient {\n return new QueryClient({\n defaultOptions: {\n queries: { retry: false, staleTime: Number.POSITIVE_INFINITY, refetchOnWindowFocus: false },\n mutations: { retry: false },\n },\n });\n}\n\n/**\n * Fetches the cached results of a query again with its new answer. Reads\n * still waiting on the old one are cancelled first: TanStack keeps a read\n * in flight for a query that has no data yet, and an unanswered read never\n * ends.\n */\nasync function refetchWithNewAnswer(queryClient: QueryClient, queryKey: QueryKey): Promise<void> {\n await queryClient.cancelQueries({ queryKey });\n await queryClient.invalidateQueries({ queryKey });\n}\n\n/** A new stub per method, fetching a query's cached results again when its answer changes. */\nfunction stubMaker(queryClient: QueryClient, stubs: Stub[]): (target: MethodTarget) => Stub {\n return (target) => {\n const queryKey: QueryKey = methodKeyPrefix(target.service, target.method);\n const stub = createStub(() => {\n if (target.kind === \"query\") {\n void refetchWithNewAnswer(queryClient, queryKey);\n }\n });\n stubs.push(stub);\n return stub;\n };\n}\n\n/**\n * Forgets everything set on a mock: answers, calls, rows, scopes and cached\n * results. `quiet` tells no mounted hook, for the reset after a test, which\n * may run before Testing Library unmounts what the test rendered.\n */\nfunction resetMock(\n queryClient: QueryClient,\n store: ReturnType<typeof createMockStore>,\n stubs: readonly Stub[],\n quiet: boolean,\n): void {\n queryClient.clear();\n store.clear(quiet);\n for (const stub of stubs) {\n if (quiet) {\n stub.forget();\n } else {\n stub.mockReset();\n }\n }\n}\n\n/**\n * Runs `reset` after each test when the test runner has a global\n * `afterEach` (vitest with `globals: true`, jest), as Testing Library\n * registers its cleanup. A runner that takes no hook where the mock is made\n * (inside a test) leaves it to `$reset()`.\n */\nfunction resetAfterEachTest(reset: () => void): void {\n const runnerAfterEach: unknown = (globalThis as { readonly afterEach?: unknown }).afterEach;\n if (typeof runnerAfterEach !== \"function\") {\n return;\n }\n try {\n (runnerAfterEach as (hook: () => void) => void)(reset);\n } catch {\n // No hook can be registered here; `$reset()` still forgets everything.\n }\n}\n\n/**\n * Creates a mock client of `contracts`: the type of\n * `createQuickdrawClient(contracts)`, with every member a stub and no\n * transport behind it, for component tests and stories. A call stays\n * pending until the test sets its answer; a row or a scope stays loading\n * until the test sets it. Its members need no provider; components that read\n * `useQuickdraw()` or `usePresence(room)` render inside `$Provider`, which\n * shows the mock's session (`$session`). Everything set is forgotten after\n * each test (see `resetAfterEach`).\n *\n * @example\n * const qd = createMockClient({ task }, { session: { userId: \"ada\" } });\n * vi.mock(\"../lib/qd\", () => ({ qd }));\n * qd.task.get.mockResolvedValue({ id: \"t1\", title: \"Write the spec\" });\n * qd.task.useEntity.mockRow({ id: \"t1\", title: \"Write the spec\" });\n * qd.task.byProject.mockScope(\"p1\", [{ id: \"t1\", title: \"Write the spec\" }]);\n * qd.$session({ serviceAccess: { taskService: \"Admin\" } });\n * render(<TaskCard id=\"t1\" />, { wrapper: qd.$Provider });\n * expect(qd.task.get.calls).toEqual([{ id: \"t1\" }]);\n */\nexport function createMockClient<const Contracts extends ContractMap>(\n contracts: Contracts & { readonly invalidate?: never },\n options: MockClientOptions = {},\n): MockClient<Contracts> {\n const queryClient = options.queryClient ?? createMockQueryClient();\n const base = startingSession(options);\n const store = createMockStore(base);\n const session = mockSessionControls(store, queryClient, base);\n const binding = createBinding();\n binding.coordinator = session.coordinator;\n const stubs: Stub[] = [];\n const context: MockContext = { queryClient, binding, stub: stubMaker(queryClient, stubs) };\n const client = buildCaller(\n \"createMockClient\",\n contracts,\n (target, definition, contract) =>\n target.kind === \"query\"\n ? mockQueryMember(context, target, (invoke) =>\n mockSearchMember(queryClient, invoke, target, definition, contract),\n )\n : mockMutationMember(context, target),\n [\"invalidate\"],\n mockLiveMembers(store, queryClient),\n );\n Object.defineProperties(client, {\n invalidate: { value: invalidateWith(binding) },\n $queryClient: { value: queryClient },\n $Provider: { value: session.Provider },\n $session: { value: session.setSession },\n $presence: { value: session.setPresence },\n $reset: {\n value: (): void => {\n resetMock(queryClient, store, stubs, false);\n },\n },\n });\n if (options.resetAfterEach !== false) {\n resetAfterEachTest(() => {\n resetMock(queryClient, store, stubs, true);\n });\n }\n return Object.freeze(client) as MockClient<Contracts>;\n}\n","// The live members of a mock client (`mockClient.ts`): `useEntity`,\n// `useEntities` and each collection's `useCollection`, showing what the test\n// set through their controls (`mockRow`, `mockScope`, ...) instead of\n// subscribing over a socket, and the stream, channel and event members\n// (`mockRealtime.ts`). They return what the real hooks return\n// (`client/live/useEntity.ts`, `client/live/useCollection.ts`): a row or a\n// scope nobody set is loading, a view runs the contract's predicate over the\n// index rows made from the items, and a component re-renders when the test\n// changes what it shows.\n\nimport { useSyncExternalStore } from \"react\";\nimport type {\n UseEntitiesResult,\n UseEntityOptions,\n UseEntityResult,\n} from \"../client/live/memberTypes\";\nimport type { AnyContract } from \"../contract/defineContract\";\nimport type { QuickdrawError } from \"../protocol/errors\";\nimport { mockAdminNamespace } from \"./mockAdmin\";\nimport { mockCollectionMember } from \"./mockCollections\";\nimport { mockRealtimeMembers } from \"./mockRealtime\";\nimport type { EntityMock, MockScope, SessionState } from \"./mockTypes\";\n\n/** What one mocked row shows: the row, its removal, or an error. */\ntype RowState =\n | { readonly row: { readonly id: string } }\n | { readonly removed: true }\n | { readonly error: QuickdrawError };\n\n/** What one mocked scope shows: its items, or an error. */\nexport type ScopeState =\n | (MockScope & { readonly items: readonly { readonly id: string }[] })\n | { readonly error: QuickdrawError };\n\n/** The rows, scopes and session of one mock client, and the hooks' views of them. */\nexport interface MockStore {\n readonly subscribe: (listener: () => void) => () => void;\n /** The value `build` makes for `key`, the same object until the store changes. */\n read<T>(key: string, build: () => T): T;\n row(service: string, id: string): RowState | undefined;\n setRow(service: string, id: string, state: RowState): void;\n scope(service: string, collection: string, scope: string): ScopeState | undefined;\n setScope(service: string, collection: string, scope: string, state: ScopeState): void;\n /**\n * A mocked value by key: a feed's items, a channel's sent messages\n * (`mockRealtime.ts`), a room's users (`mockSession.tsx`).\n */\n value(key: string): unknown;\n setValue(key: string, value: unknown): void;\n /** Who the mock acts for (`mockSession.tsx`): the same object until it is set again. */\n session(): SessionState;\n setSession(session: SessionState): void;\n /**\n * Forgets every row, scope and value, and puts back the session the mock\n * started with; `quiet` tells no hook that shows them (they are about to\n * unmount).\n */\n clear(quiet?: boolean): void;\n}\n\nfunction keyOf(...parts: readonly string[]): string {\n return parts.join(\"\\u0000\");\n}\n\n/** Creates the store of one mock client, whose session starts as `base`. */\nexport function createMockStore(base: SessionState): MockStore {\n let session = base;\n const rows = new Map<string, RowState>();\n const scopes = new Map<string, ScopeState>();\n const values = new Map<string, unknown>();\n const views = new Map<string, unknown>();\n const listeners = new Set<() => void>();\n const changed = (): void => {\n views.clear();\n for (const listener of [...listeners]) {\n listener();\n }\n };\n return {\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n read<T>(key: string, build: () => T): T {\n if (!views.has(key)) {\n views.set(key, build());\n }\n return views.get(key) as T;\n },\n row: (service, id) => rows.get(keyOf(service, id)),\n setRow(service, id, state) {\n rows.set(keyOf(service, id), state);\n changed();\n },\n scope: (service, collection, scope) => scopes.get(keyOf(service, collection, scope)),\n setScope(service, collection, scope, state) {\n scopes.set(keyOf(service, collection, scope), state);\n changed();\n },\n value: (key) => values.get(key),\n setValue(key, value) {\n values.set(key, value);\n changed();\n },\n session: () => session,\n setSession(next) {\n session = next;\n changed();\n },\n clear(quiet = false) {\n rows.clear();\n scopes.clear();\n values.clear();\n session = base;\n if (quiet) {\n views.clear();\n } else {\n changed();\n }\n },\n };\n}\n\n/** What `useEntity` shows of a mocked row. */\nfunction rowResult(state: RowState | undefined, active: boolean): UseEntityResult<unknown> {\n if (state === undefined) {\n return { data: undefined, isLoading: active, isRemoved: false, error: null };\n }\n if (\"row\" in state) {\n return { data: state.row, isLoading: false, isRemoved: false, error: null };\n }\n if (\"removed\" in state) {\n return { data: undefined, isLoading: false, isRemoved: true, error: null };\n }\n return { data: undefined, isLoading: false, isRemoved: false, error: state.error };\n}\n\n/** What `useEntities` shows of the mocked rows `ids`. */\nfunction rowsResult(\n store: MockStore,\n service: string,\n ids: readonly string[],\n active: boolean,\n): UseEntitiesResult<unknown> {\n const data: unknown[] = [];\n const byId = new Map<string, unknown>();\n const errors = new Map<string, QuickdrawError>();\n let isLoading = false;\n for (const id of ids) {\n const shows = active && id !== \"\";\n const shown = rowResult(shows ? store.row(service, id) : undefined, shows);\n data.push(shown.data);\n if (shown.data !== undefined) {\n byId.set(id, shown.data);\n }\n if (shown.error !== null) {\n errors.set(id, shown.error);\n }\n isLoading ||= shown.isLoading;\n }\n return { data, byId, isLoading, error: errors.values().next().value ?? null, errors };\n}\n\nfunction useMockRow(\n store: MockStore,\n service: string,\n id: string | null | undefined,\n options: UseEntityOptions = {},\n): UseEntityResult<unknown> {\n const rowId = typeof id === \"string\" ? id : \"\";\n const active = options.enabled !== false && rowId !== \"\";\n const read = (): UseEntityResult<unknown> =>\n store.read(keyOf(\"row\", service, rowId, String(active)), () =>\n rowResult(active ? store.row(service, rowId) : undefined, active),\n );\n return useSyncExternalStore(store.subscribe, read, read);\n}\n\nfunction useMockRows(\n store: MockStore,\n service: string,\n ids: readonly string[],\n options: UseEntityOptions = {},\n): UseEntitiesResult<unknown> {\n const rowIds = ids.map((id) => (typeof id === \"string\" ? id : \"\"));\n const active = options.enabled !== false;\n const read = (): UseEntitiesResult<unknown> =>\n store.read(keyOf(\"rows\", service, String(active), ...rowIds), () =>\n rowsResult(store, service, rowIds, active),\n );\n return useSyncExternalStore(store.subscribe, read, read);\n}\n\n/** The id of a row the test passed to `mockRow`. */\nfunction idOf(row: unknown): string {\n const id: unknown = typeof row === \"object\" && row !== null ? (row as { id?: unknown }).id : \"\";\n if (typeof id !== \"string\" || id === \"\") {\n throw new TypeError(\"mockRow: the row needs a string id\");\n }\n return id;\n}\n\n/** The controls of a mocked service's rows. */\nfunction rowControls(store: MockStore, service: string): EntityMock<unknown> {\n return Object.freeze({\n mockRow(row: unknown) {\n store.setRow(service, idOf(row), { row: row as { readonly id: string } });\n },\n mockRemoved(id: string) {\n store.setRow(service, id, { removed: true });\n },\n mockError(id: string, error: QuickdrawError) {\n store.setRow(service, id, { error });\n },\n });\n}\n\n/**\n * The live members of each contract's service on a mock client, as\n * `buildCaller` takes them: the entity hooks for a contract with an entity,\n * each carrying the controls of the rows it shows, one member per\n * collection, and `admin` for a contract with the admin kit (its `adminMeta`\n * queries run on `queryClient`, the mock's cache).\n */\nexport function mockLiveMembers(\n store: MockStore,\n queryClient: Parameters<typeof mockAdminNamespace>[2],\n): (\n contract: AnyContract,\n methods: Readonly<Record<string, object>>,\n) => Readonly<Record<string, object>> {\n return (contract, methods) => {\n const service = contract.name;\n const members: [string, object][] = [];\n if (contract.entity !== undefined) {\n const controls = rowControls(store, service);\n const useEntity = (id: string | null | undefined, options?: UseEntityOptions) =>\n useMockRow(store, service, id, options);\n const useEntities = (ids: readonly string[], options?: UseEntityOptions) =>\n useMockRows(store, service, ids, options);\n members.push([\"useEntity\", Object.freeze(Object.assign(useEntity, controls))]);\n members.push([\"useEntities\", Object.freeze(Object.assign(useEntities, controls))]);\n }\n for (const [collection, def] of Object.entries(contract.collections)) {\n members.push([collection, mockCollectionMember(store, { service, collection, def })]);\n }\n members.push(...mockRealtimeMembers(store, contract));\n members.push(...Object.entries(mockAdminNamespace(contract, methods, queryClient, store)));\n return Object.freeze(Object.fromEntries(members));\n };\n}\n","// The mock client's `qd.<service>.admin` (`mockClient.ts`): the same\n// namespace as the typed client's (`../client/admin.ts`), holding the mocked\n// members of the admin methods, so `qd.task.admin.adminList.mockResolvedValue`\n// sets the same stub as `qd.task.adminList`. `useAdminServices` on a mock\n// client asks each `adminMeta` stub the session's grants allow (`$session`,\n// `serviceAccess`) at once, on the mock's cache, so a component test lists\n// services with `adminMeta.mockResolvedValue(meta)`.\n\nimport { useQueries, type QueryClient, type UseQueryResult } from \"@tanstack/react-query\";\nimport { useSyncExternalStore } from \"react\";\nimport {\n adminNamespace,\n type AdminMetaQuery,\n type AdminMetaState,\n type UseAdminGrants,\n type UseAdminMeta,\n} from \"../client/admin\";\nimport { methodKey } from \"../client/keys\";\nimport type { AnyContract } from \"../contract/defineContract\";\nimport type { MockStore } from \"./mockLive\";\nimport { sessionIn, useSessionScope } from \"./mockScope\";\n\nfunction statesOf(results: UseQueryResult<unknown>[]): AdminMetaState[] {\n return results.map((result) => ({\n data: result.data as AdminMetaState[\"data\"],\n error: result.error,\n }));\n}\n\n/** The mock client's `admin` member of a contract's service, on the mock's cache, with its session's grants. */\nexport function mockAdminNamespace(\n contract: AnyContract,\n methods: Readonly<Record<string, object>>,\n queryClient: QueryClient,\n store: MockStore,\n): Readonly<Record<string, object>> {\n const useMockAdminMeta: UseAdminMeta = (queries: readonly AdminMetaQuery[], enabled: boolean) =>\n useQueries(\n {\n queries: queries.map((query) => ({\n queryKey: methodKey(query.serviceName, query.method, undefined),\n queryFn: () => query.member.call(),\n enabled,\n retry: false,\n })),\n combine: statesOf,\n },\n queryClient,\n );\n const useMockGrants: UseAdminGrants = () => {\n // Under a `$Provider` with a `session` prop, that session's grants (`mockScope.ts`).\n const scope = useSessionScope(store);\n const grants = () => {\n const session = sessionIn(store, scope);\n return session.isKnown ? session.serviceAccess : null;\n };\n return useSyncExternalStore(store.subscribe, grants, grants);\n };\n return adminNamespace(contract, methods, { useMeta: useMockAdminMeta, useGrants: useMockGrants });\n}\n","// The session `<mock.$Provider session={...}>` gives its subtree (finding\n// F6.2 of the quickdraw-chat migration): a Storybook docs page renders every\n// story at once, each inside the mock's provider, and `$session(...)` is one\n// session for the whole mock, so every story showed the last one set. The\n// prop is laid over the mock's own session (`$session`) field by field for\n// what renders inside that provider: the real `useQuickdraw()` and\n// `usePresence(room)` read it through the provider's connection\n// (`mockSession.tsx`), and the mock's own hooks that depend on who the user\n// is (collection views, the admin grants) read it from this context.\n\nimport { createContext, useContext } from \"react\";\nimport type { MockStore } from \"./mockLive\";\nimport type { SessionState } from \"./mockTypes\";\n\n/** Lays a `$Provider`'s `session` prop over the mock's own session: the same object while that one does not change. */\nexport type SessionScope = (session: SessionState) => SessionState;\n\n/** The scope a `$Provider` with a `session` prop set, for one mock's store. */\nexport interface ScopedSession {\n readonly store: MockStore;\n readonly scope: SessionScope;\n}\n\n/** Where a `$Provider` with a `session` prop puts its scope; `undefined` elsewhere. */\nexport const MockSessionScope = createContext<ScopedSession | undefined>(undefined);\n\n/** The scope that applies to `store` where this renders: one of its own providers' `session` prop. */\nexport function useSessionScope(store: MockStore): SessionScope | undefined {\n const scoped = useContext(MockSessionScope);\n return scoped?.store === store ? scoped.scope : undefined;\n}\n\n/** The session `store` shows under `scope`: its own, or the prop over it. */\nexport function sessionIn(store: MockStore, scope: SessionScope | undefined): SessionState {\n const session = store.session();\n return scope === undefined ? session : scope(session);\n}\n","// The collections of a mock client (`mockLive.ts`): each collection's\n// `useCollection`, showing the scope the test set (`mockScope`) instead of\n// subscribing over a socket, as the real hook returns it\n// (`client/live/useCollection.ts`): a scope nobody set is loading, and a\n// view runs the contract's predicate over the index rows made from the\n// items, for the session's user, or under a `$Provider` with a `session`\n// prop for that session's (`mockScope.ts`).\n\nimport { useSyncExternalStore } from \"react\";\nimport { indexRowFromItem, type IndexRow } from \"../client/live/collectionIndex\";\nimport type { UseCollectionOptions, UseCollectionResult } from \"../client/live/memberTypes\";\nimport { viewPredicate } from \"../client/live/views\";\nimport type { CollectionDef, Viewer } from \"../contract/collections\";\nimport type { QuickdrawError } from \"../protocol/errors\";\nimport type { MockStore, ScopeState } from \"./mockLive\";\nimport { sessionIn, useSessionScope } from \"./mockScope\";\nimport type { MockScope, SessionState } from \"./mockTypes\";\n\nfunction keyOf(...parts: readonly string[]): string {\n return parts.join(\"\\u0000\");\n}\n\nconst idle = (): Promise<void> => Promise.resolve();\n\n/** A mock shows no optimistic additions, so none is ever pending. */\nconst NONE_PENDING: ReadonlySet<string> = new Set();\n\n/** A mocked scope with nothing in it yet. */\nfunction emptyScope(active: boolean): UseCollectionResult<unknown, IndexRow> {\n return {\n items: [],\n pending: NONE_PENDING,\n checking: NONE_PENDING,\n // A mock's mutations add nothing, so nothing is refused either.\n refused: [],\n index: undefined,\n byId: new Map(),\n totalCount: null,\n hasMore: false,\n clamped: false,\n indexTruncated: false,\n isLoading: active,\n isLoadingMore: false,\n error: null,\n loadMore: idle,\n loadItems: idle,\n refresh: idle,\n };\n}\n\n/** What `useCollection` shows of a mocked scope, through `view` for `who`. */\nfunction scopeResult(\n def: CollectionDef,\n state: ScopeState | undefined,\n options: { readonly view: string | undefined; readonly who: Viewer; readonly active: boolean },\n): UseCollectionResult<unknown, IndexRow> {\n const empty = emptyScope(options.active);\n if (state === undefined) {\n return empty;\n }\n if (\"error\" in state) {\n return { ...empty, isLoading: false, error: state.error };\n }\n const selects = viewPredicate(def, options.view);\n const keep = (row: Readonly<Record<string, unknown>>): boolean =>\n selects === undefined || selects(row, options.who);\n const index =\n def.index === undefined\n ? undefined\n : state.items.map((item) => indexRowFromItem(def, item)).filter(keep);\n const members = new Set(index?.map((row) => row.id));\n const items = state.items.filter((item) =>\n index === undefined ? keep(item as Readonly<Record<string, unknown>>) : members.has(item.id),\n );\n return {\n ...empty,\n items,\n index,\n byId: new Map(items.map((item) => [item.id, item])),\n totalCount: state.totalCount ?? state.items.length,\n hasMore: state.hasMore ?? false,\n isLoading: false,\n };\n}\n\n/** One collection of a contract, as its mock member shows it. */\ninterface MockCollectionTarget {\n readonly service: string;\n readonly collection: string;\n readonly def: CollectionDef;\n}\n\n/**\n * Who the views select members for: the session's user once known, as the\n * real views read the hello; under a `$Provider` with a `session` prop,\n * that session's (`mockScope.ts`).\n */\nfunction viewerOf(session: SessionState): Viewer {\n return { userId: session.isKnown ? (session.userId ?? \"\") : \"\" };\n}\n\nfunction useMockScope(\n store: MockStore,\n target: MockCollectionTarget,\n scope: string | null | undefined,\n options: UseCollectionOptions = {},\n): UseCollectionResult<unknown, IndexRow> {\n const value = typeof scope === \"string\" ? scope : \"\";\n const active = options.enabled !== false && value !== \"\";\n const { service, collection, def } = target;\n const sessionScope = useSessionScope(store);\n const read = (): UseCollectionResult<unknown, IndexRow> => {\n const who = viewerOf(sessionIn(store, sessionScope));\n // The viewer is part of the key: two providers' sessions may see one scope's views apart.\n const view = options.view ?? \"\";\n const key = keyOf(\"scope\", service, collection, value, view, String(active), who.userId);\n return store.read(key, () =>\n scopeResult(def, active ? store.scope(service, collection, value) : undefined, {\n view: options.view,\n who,\n active,\n }),\n );\n };\n return useSyncExternalStore(store.subscribe, read, read);\n}\n\n/** `qd.<service>.<collection>` of a mock client. */\nexport function mockCollectionMember(store: MockStore, target: MockCollectionTarget): object {\n return Object.freeze({\n useCollection: (scope: string | null | undefined, options?: UseCollectionOptions) =>\n useMockScope(store, target, scope, options),\n mockScope(scope: string, items: readonly { readonly id: string }[], extra: MockScope = {}) {\n store.setScope(target.service, target.collection, scope, { ...extra, items: [...items] });\n },\n mockError(scope: string, error: QuickdrawError) {\n store.setScope(target.service, target.collection, scope, { error });\n },\n });\n}\n","// The stream, channel and event members of a mock client (`mockClient.ts`,\n// RFC 0003 section 12.5), with no socket behind them:\n//\n// - a stream's `useStream` shows what the test set with `mockItems` (the\n// latest `max` of them) or `mockError`; a feed nobody set is loading;\n// - a channel's `useChannel` is always ready, and its `send` records each\n// payload in the member's `sent`;\n// - an event's `useEvent` registers its handler, and `mockEmit(payload)`\n// calls every mounted one (wrap it in Testing Library's `act`).\n//\n// What the test set is forgotten with the rest of the mock (`$reset`, or\n// after each test).\n\nimport { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport { streamMaxOf, type StreamState } from \"../client/live/streams\";\nimport type { UseEventOptions } from \"../client/live/useEvent\";\nimport type { UseStreamOptions, UseStreamResult } from \"../client/live/useStream\";\nimport type { AnyContract } from \"../contract/defineContract\";\nimport { isScopedStream } from \"../contract/realtime\";\nimport type { QuickdrawError } from \"../protocol/errors\";\nimport type { MockStore } from \"./mockLive\";\n\n/** One feed as the test set it: its items, or why it failed. */\ntype FeedState = { readonly items: readonly unknown[] } | { readonly error: QuickdrawError };\n\nfunction keyOf(...parts: readonly string[]): string {\n return parts.join(\"\\u0000\");\n}\n\n/** What `useStream` shows of a mocked feed. */\nfunction feedResult(state: unknown, active: boolean, max: number): StreamState {\n if (!active || state === undefined) {\n return { items: [], isLoading: active, error: null };\n }\n const feed = state as FeedState;\n if (\"error\" in feed) {\n return { items: [], isLoading: false, error: feed.error };\n }\n const items = feed.items.length > max ? feed.items.slice(feed.items.length - max) : feed.items;\n return { items, isLoading: false, error: null };\n}\n\n/** `qd.<service>.<stream>` of a mock client. */\nfunction mockStreamMember(\n store: MockStore,\n service: string,\n stream: string,\n scoped: boolean,\n): object {\n const feedKey = (scope: unknown): string =>\n keyOf(\"stream\", service, stream, scoped && typeof scope === \"string\" ? scope : \"\");\n const useMockStream = (first?: unknown, second?: UseStreamOptions): UseStreamResult<unknown> => {\n const scope = scoped ? first : undefined;\n const options = (scoped ? second : (first as UseStreamOptions | undefined)) ?? {};\n const active =\n options.enabled !== false && (!scoped || (typeof scope === \"string\" && scope !== \"\"));\n const max = streamMaxOf(options.max);\n const read = (): StreamState =>\n store.read(keyOf(\"view\", feedKey(scope), String(active), String(max)), () =>\n feedResult(store.value(feedKey(scope)), active, max),\n );\n return useSyncExternalStore(store.subscribe, read, read);\n };\n // `(scope, value)` for a scoped stream, `(value)` for a global one.\n const set = (args: readonly unknown[], value: (last: unknown) => FeedState): void => {\n store.setValue(feedKey(scoped ? args[0] : undefined), value(args.at(-1)));\n };\n return Object.freeze({\n useStream: useMockStream,\n mockItems: (...args: unknown[]) => {\n set(args, (items) => ({ items: [...(items as readonly unknown[])] }));\n },\n mockError: (...args: unknown[]) => {\n set(args, (error) => ({ error: error as QuickdrawError }));\n },\n });\n}\n\n/** `qd.<service>.<channel>` of a mock client. */\nfunction mockChannelMember(store: MockStore, service: string, channel: string): object {\n const sentKey = keyOf(\"sent\", service, channel);\n const sent = (): readonly unknown[] =>\n (store.value(sentKey) as readonly unknown[] | undefined) ?? [];\n const result = Object.freeze({\n send: (payload: unknown) => {\n store.setValue(sentKey, [...sent(), payload]);\n },\n isReady: true,\n });\n return Object.freeze(\n Object.defineProperties(\n { useChannel: () => result },\n { sent: { get: () => [...sent()], enumerable: true } },\n ),\n );\n}\n\n/** `qd.<service>.<event>` of a mock client. */\nfunction mockEventMember(): object {\n const handlers = new Set<(payload: unknown) => void>();\n return Object.freeze({\n useEvent: (handler: (payload: unknown) => void, options: UseEventOptions = {}) => {\n const latest = useRef(handler);\n useEffect(() => {\n latest.current = handler;\n });\n const enabled = options.enabled !== false;\n useEffect(() => {\n if (!enabled) {\n return undefined;\n }\n const entry = (payload: unknown): void => {\n latest.current(payload);\n };\n handlers.add(entry);\n return () => {\n handlers.delete(entry);\n };\n }, [enabled]);\n },\n mockEmit: (payload: unknown) => {\n for (const handler of [...handlers]) {\n handler(payload);\n }\n },\n });\n}\n\n/** The stream, channel and event members of one contract's service on a mock client. */\nexport function mockRealtimeMembers(store: MockStore, contract: AnyContract): [string, object][] {\n const service = contract.name;\n return [\n ...Object.entries(contract.streams).map(([stream, def]): [string, object] => [\n stream,\n mockStreamMember(store, service, stream, isScopedStream(def)),\n ]),\n ...Object.keys(contract.channels).map((channel): [string, object] => [\n channel,\n mockChannelMember(store, service, channel),\n ]),\n ...Object.keys(contract.events).map((event): [string, object] => [event, mockEventMember()]),\n ];\n}\n","// The mock client's `useSearch` (`mockClient.ts`), on the member of a method\n// the search kit made: the method's stub answers each query at once, with no\n// debounce, server or collection cache, so a component test sets the results\n// it shows with `mockResolvedValue({ items, nextCursor })` and reads what was\n// searched for in `calls`.\n\nimport { useQuery, type QueryClient } from \"@tanstack/react-query\";\nimport { methodKey } from \"../client/keys\";\nimport { searchCall, searchTargetOf, type SearchTarget } from \"../client/live/searchResults\";\nimport type { UseSearchOptions, UseSearchResult } from \"../client/live/searchTypes\";\nimport type { MethodTarget } from \"../client/members\";\nimport type { AnyContract } from \"../contract/defineContract\";\nimport type { SearchPage } from \"../contract/kits/searchSchemas\";\nimport type { MethodDef } from \"../contract/methods\";\nimport type { QuickdrawError } from \"../protocol/errors\";\n\n/** Answers one call of the stubbed method. */\ntype Invoke = (input: unknown) => Promise<unknown>;\n\nconst NO_RESULTS: readonly never[] = Object.freeze([]);\n\nfunction useMockSearch(\n queryClient: QueryClient,\n invoke: Invoke,\n target: SearchTarget,\n q: string,\n options: UseSearchOptions,\n): UseSearchResult<unknown> {\n const typed = typeof q === \"string\" ? q.trim() : \"\";\n const scope = typeof options.scope === \"string\" ? options.scope : \"\";\n const input = searchCall(typed, scope, options.limit);\n const active = options.enabled !== false && typed.length >= target.minLength;\n const result = useQuery(\n {\n queryKey: methodKey(target.service, target.method, input),\n queryFn: () => invoke(input) as Promise<SearchPage<unknown>>,\n enabled: active,\n retry: false,\n },\n queryClient,\n );\n const page = active ? result.data : undefined;\n return {\n items: page?.items ?? NO_RESULTS,\n hasMore: (page?.nextCursor ?? null) !== null,\n isSearching: active && result.isFetching,\n isLoading: active && result.isFetching && page === undefined,\n error: active ? (result.error as QuickdrawError | null) : null,\n };\n}\n\n/** The mock `useSearch` a query member gets when the search kit made its method; nothing otherwise. */\nexport function mockSearchMember(\n queryClient: QueryClient,\n invoke: Invoke,\n target: MethodTarget,\n definition: MethodDef,\n contract: AnyContract,\n): Readonly<Record<string, unknown>> {\n const search = searchTargetOf(target, definition, contract);\n if (search === undefined) {\n return {};\n }\n return {\n useSearch: (q: string, options: UseSearchOptions = {}) =>\n useMockSearch(queryClient, invoke, search, q, options),\n };\n}\n","// The session of a mock client (`mockClient.ts`, RFC 0003 section 13): who\n// the mock acts for, as the real `useQuickdraw()` and `usePresence(room)`\n// read it under the mock's provider (`mock.$Provider`), with no server and\n// no socket. 4.1 had nothing of the kind, and an app on the first 5.0\n// candidates re-exported `useQuickdraw` from the module it mocked, so that a\n// story or a test could fake the connection state (finding F3.1).\n//\n// - The provider fills the same context `QuickdrawProvider` fills, with a\n// connection that never opens: its state is made from the session (a\n// hello naming `userId` with `serviceAccess`, connected or connecting,\n// known or not), and every hook that reads the context reads it, on a\n// server too (`renderOnServerAs`).\n// - `usePresence(room)` shows the users `$presence(room, users)` set, while\n// the session is known (a socket is in no room before its hello).\n// - The views of the mock's collections select members for the session's\n// user, as the real ones do for the user the hello names.\n// - `<mock.$Provider session={...}>` lays its `session` over the mock's for\n// what renders inside it (finding F6.2): its own connection shows that\n// session, and the mock's hooks read it from `mockScope.ts`, so stories\n// rendered side by side (a Storybook docs page) each show their own.\n//\n// Imports nothing of Testing Library, so a browser bundle (Storybook) can\n// take it: `./testing/mock` is its own entry.\n\nimport { QueryClientProvider, type QueryClient } from \"@tanstack/react-query\";\nimport { useMemo, type ReactElement, type ReactNode } from \"react\";\nimport {\n createQuickdrawConnection,\n type ConnectionState,\n type QuickdrawConnection,\n} from \"../client/connection\";\nimport { QuickdrawContext, renderOnServerAs, type QuickdrawContextValue } from \"../client/context\";\nimport { createInvalidationCoordinator, type InvalidationCoordinator } from \"../client/coordinator\";\nimport { inertLiveData } from \"../client/live/liveData\";\nimport type { PresenceStore } from \"../client/live/presence\";\nimport {\n MAX_ENTITY_IDS_PER_SOCKET,\n MAX_SCOPES_PER_SOCKET,\n MAX_SUBSCRIBE_IDS,\n MAX_TOPICS_PER_SOCKET,\n PROTOCOL_VERSION,\n type HelloFrame,\n} from \"../protocol/version\";\nimport { QUICKDRAW_VERSION } from \"../version\";\nimport type { MockStore } from \"./mockLive\";\nimport { MockSessionScope, type ScopedSession, type SessionScope } from \"./mockScope\";\nimport type { MockClientOptions, MockSession, SessionState } from \"./mockTypes\";\n\n/** The URL of a mock's connection, which never opens. */\nconst MOCK_URL = \"http://quickdraw-mock.invalid\";\n\nconst NOBODY: readonly string[] = Object.freeze([]);\n\n/** The limits a mock's hello announces: the server's defaults. */\nconst MOCK_LIMITS: HelloFrame[\"limits\"] = Object.freeze({\n maxInFlightQueries: 16,\n maxQueuedQueries: 64,\n maxSubscribeIds: MAX_SUBSCRIBE_IDS,\n callTimeoutMs: 30_000,\n subscriptions: Object.freeze({\n maxInFlight: 8,\n maxQueued: 64,\n maxEntityIds: MAX_ENTITY_IDS_PER_SOCKET,\n maxScopes: MAX_SCOPES_PER_SOCKET,\n maxTopics: MAX_TOPICS_PER_SOCKET,\n }),\n});\n\nfunction checkUser(owner: string, userId: unknown): string | null | undefined {\n if (userId === undefined || userId === null || (typeof userId === \"string\" && userId !== \"\")) {\n return userId;\n }\n throw new TypeError(`${owner}: userId must be a non-empty string, or null for an anonymous user`);\n}\n\nfunction checkFlag(owner: string, name: string, value: unknown): boolean | undefined {\n if (value === undefined || typeof value === \"boolean\") {\n return value;\n }\n throw new TypeError(`${owner}: ${name} must be true or false`);\n}\n\n/**\n * `session` over `base`: what it sets replaces what `base` has, field by\n * field; a field it leaves out (or sets to `undefined`) keeps `base`'s.\n * Throws a `TypeError` naming `owner` for a field of the wrong kind.\n */\nexport function sessionOver(\n owner: string,\n base: SessionState,\n session: MockSession | undefined,\n): SessionState {\n if (session === undefined) {\n return base;\n }\n if (typeof session !== \"object\" || session === null) {\n throw new TypeError(\n `${owner}: the session is { userId?, serviceAccess?, isConnected?, isKnown? }`,\n );\n }\n const { serviceAccess } = session;\n if (\n serviceAccess !== undefined &&\n (typeof serviceAccess !== \"object\" || serviceAccess === null)\n ) {\n throw new TypeError(`${owner}: serviceAccess maps service names to access levels`);\n }\n const userId = checkUser(owner, session.userId);\n const isConnected = checkFlag(owner, \"isConnected\", session.isConnected);\n const isKnown = checkFlag(owner, \"isKnown\", session.isKnown);\n return Object.freeze({\n userId: userId === undefined ? base.userId : userId,\n serviceAccess: Object.freeze({ ...(serviceAccess ?? base.serviceAccess) }),\n isConnected: isConnected ?? base.isConnected,\n isKnown: isKnown ?? base.isKnown,\n });\n}\n\n/** The id a mock's hello names its server by: one mock server, never restarted. */\nconst MOCK_SERVER_ID = \"mock\";\n\n/** The connection state a session shows: connected or connecting, with a hello once it is known. */\nfunction stateOf(session: SessionState): ConnectionState {\n const hello: HelloFrame | null = session.isKnown\n ? Object.freeze({\n protocol: PROTOCOL_VERSION,\n server: QUICKDRAW_VERSION,\n serverId: MOCK_SERVER_ID,\n limits: MOCK_LIMITS,\n features: Object.freeze([]),\n userId: session.userId,\n serviceAccess: session.serviceAccess,\n })\n : null;\n return Object.freeze({\n status: session.isConnected ? \"connected\" : \"connecting\",\n reconnecting: !session.isConnected && session.isKnown,\n hello,\n serviceAccess: hello === null ? null : session.serviceAccess,\n refusal: null,\n backoff: Object.freeze({}),\n });\n}\n\n/**\n * The connection of a mock client: a real connection that is never opened\n * (its socket, lane and topics stay idle, so a real client's hook rendered\n * under the mock's provider by mistake waits rather than throws), whose\n * state comes from the mock's session. Opening, closing and new credentials\n * do nothing.\n */\nfunction mockConnection(store: MockStore, sessionOf: () => SessionState): QuickdrawConnection {\n const inert = createQuickdrawConnection({ url: MOCK_URL, transports: [\"websocket\"] });\n let shown: { readonly session: SessionState; readonly state: ConnectionState } | undefined;\n const getState = (): ConnectionState => {\n const session = sessionOf();\n if (shown?.session !== session) {\n shown = { session, state: stateOf(session) };\n }\n return shown.state;\n };\n const nothing = (): void => undefined;\n return Object.freeze({\n url: inert.url,\n timeoutMs: inert.timeoutMs,\n socket: inert.socket,\n subscriptionLane: inert.subscriptionLane,\n getState,\n subscribe: store.subscribe,\n open: nothing,\n close: nothing,\n retain: () => nothing,\n setAuth: () => false,\n nextCallId: inert.nextCallId,\n reportRateLimited: nothing,\n backoffRemaining: () => 0,\n onReconnect: () => nothing,\n onHello: (listener: (hello: HelloFrame) => void) => mockHellos(getState, store, listener),\n watch: () => nothing,\n waitForJoin: () => undefined,\n });\n}\n\n/**\n * `onHello` on a mock's connection: each session that is connected and\n * known is a hello (`$session(...)` that sets one is a reconnect), the\n * current one in a microtask, as a real connection gives it.\n */\nfunction mockHellos(\n getState: () => ConnectionState,\n store: MockStore,\n listener: (hello: HelloFrame) => void,\n): () => void {\n let last: HelloFrame | null = null;\n let stopped = false;\n const deliver = (): void => {\n const { status, hello } = getState();\n if (!stopped && status === \"connected\" && hello !== null && hello !== last) {\n last = hello;\n listener(hello);\n }\n };\n queueMicrotask(deliver);\n const stop = store.subscribe(deliver);\n return () => {\n stopped = true;\n stop();\n };\n}\n\n/** The key of a room's users in the mock's store. */\nfunction presenceKey(room: string): string {\n return `presence\\u0000${room}`;\n}\n\n/**\n * The presence `usePresence` reads on a mock: the users the test set per\n * room, nobody while the session is not known. No frame reaches it.\n */\nfunction mockPresence(store: MockStore, sessionOf: () => SessionState): PresenceStore {\n return Object.freeze({\n users(room: string): readonly string[] {\n if (!sessionOf().isKnown) {\n return NOBODY;\n }\n return (store.value(presenceKey(room)) as readonly string[] | undefined) ?? NOBODY;\n },\n // Every change of the store: `users` returns the same array until the room's own changes.\n listen: (_room: string, listener: () => void) => store.subscribe(listener),\n receive: () => undefined,\n clear: () => undefined,\n });\n}\n\n/** What a mock client gives its tests besides its members: its provider, session and presence. */\n/** The props of a mock's provider: what it renders, and the session it shows there. */\nexport interface MockProviderProps {\n readonly children?: ReactNode;\n /**\n * The session the subtree shows, laid over the mock's own (`$session`)\n * field by field: what it leaves out follows the mock's. Each provider\n * has its own, so stories rendered side by side show their own sessions.\n */\n readonly session?: MockSession;\n}\n\nexport interface MockSessionControls {\n readonly Provider: (props: MockProviderProps) => ReactElement;\n /** The invalidation coordinator of the mock's cache, which the provider gives the hooks too. */\n readonly coordinator: InvalidationCoordinator;\n setSession(session: MockSession): void;\n setPresence(room: string, users: readonly string[]): void;\n}\n\n/** The session a mock starts with: its `session` option over `userId`, connected and known. */\nexport function startingSession(options: MockClientOptions): SessionState {\n const fallback: SessionState = Object.freeze({\n userId: null,\n serviceAccess: Object.freeze({}),\n isConnected: true,\n isKnown: true,\n });\n const withUser = sessionOver(\n \"createMockClient\",\n fallback,\n options.userId === undefined ? undefined : { userId: options.userId },\n );\n return sessionOver(\"createMockClient\", withUser, options.session);\n}\n\n/**\n * `session` laid over the session of `store` (`$Provider`'s prop): checked\n * at once, and the same object while the mock's own session does not change.\n */\nfunction scopeOf(store: MockStore, session: MockSession): SessionScope {\n sessionOver(\"$Provider\", store.session(), session);\n let last: { readonly base: SessionState; readonly shown: SessionState } | undefined;\n return (base) => {\n if (last?.base !== base) {\n last = { base, shown: sessionOver(\"$Provider\", base, session) };\n }\n return last.shown;\n };\n}\n\n/**\n * The provider, session and presence of one mock client: `base` is the\n * session it starts with and goes back to on a reset; `$session` sets\n * another over it.\n */\nexport function mockSessionControls(\n store: MockStore,\n queryClient: QueryClient,\n base: SessionState,\n): MockSessionControls {\n const coordinator = createInvalidationCoordinator(queryClient);\n /** The context value of a connection showing `sessionOf`'s session. */\n const contextOver = (sessionOf: () => SessionState): QuickdrawContextValue => {\n const connection = mockConnection(store, sessionOf);\n // A server renders the session too, so a story or a test that renders on one hydrates it.\n renderOnServerAs(connection, connection.getState);\n inertLiveData(connection, queryClient, mockPresence(store, sessionOf));\n return Object.freeze({ connection, queryClient, coordinator });\n };\n const value = contextOver(store.session);\n /** A provider's `session` prop: its own connection, and the scope the mock's hooks read. */\n const scopedBy = (key: string): { value: QuickdrawContextValue; scoped: ScopedSession } => {\n const scope = scopeOf(store, JSON.parse(key) as MockSession);\n return {\n value: contextOver(() => scope(store.session())),\n scoped: Object.freeze({ store, scope }),\n };\n };\n function MockQuickdrawProvider({ children, session }: MockProviderProps): ReactElement {\n // By value: a story passes a new object on every render.\n const key = session === undefined ? undefined : JSON.stringify(session);\n const scoped = useMemo(() => (key === undefined ? undefined : scopedBy(key)), [key]);\n return (\n <QueryClientProvider client={queryClient}>\n <QuickdrawContext.Provider value={scoped?.value ?? value}>\n <MockSessionScope.Provider value={scoped?.scoped}>{children}</MockSessionScope.Provider>\n </QuickdrawContext.Provider>\n </QueryClientProvider>\n );\n }\n return Object.freeze({\n Provider: MockQuickdrawProvider,\n coordinator,\n setSession(session: MockSession): void {\n store.setSession(sessionOver(\"$session\", base, session));\n },\n setPresence(room: string, users: readonly string[]): void {\n if (typeof room !== \"string\" || room === \"\") {\n throw new TypeError(\"$presence: room must be a room's name\");\n }\n if (!Array.isArray(users) || !users.every((user) => typeof user === \"string\")) {\n throw new TypeError(\"$presence: users must be a list of user ids\");\n }\n store.setValue(presenceKey(room), Object.freeze([...new Set(users)]));\n },\n });\n}\n"],"mappings":"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","names":["useQuery","useSyncExternalStore","useSyncExternalStore","useSyncExternalStore","useSyncExternalStore","keyOf","useSyncExternalStore","keyOf","useSyncExternalStore","useQuery"]}
@@ -1,3 +1,7 @@
1
+ import {
2
+ adminSpecOf
3
+ } from "./chunk-MPQAT55F.js";
4
+
1
5
  // src/client/keys.ts
2
6
  var KEY_ROOT = "qd";
3
7
  function methodKey(service, method, input) {
@@ -17,6 +21,12 @@ function collectionKey(service, collection, scope) {
17
21
  }
18
22
 
19
23
  // src/client/members.ts
24
+ var ADMIN_ROW_READS = ["adminList", "adminGet", "adminSubscribers"];
25
+ var ROW_READ_DEFAULTS = Object.freeze({ staleTime: 0 });
26
+ function queryDefaultsOf(definition) {
27
+ const spec = adminSpecOf(definition);
28
+ return spec !== void 0 && ADMIN_ROW_READS.includes(spec.method) ? { queryDefaults: ROW_READ_DEFAULTS } : {};
29
+ }
20
30
  function isContract(value) {
21
31
  if (typeof value !== "object" || value === null) {
22
32
  return false;
@@ -53,7 +63,8 @@ function buildCaller(owner, contracts, member, reserved = [], live) {
53
63
  kind: definition.kind,
54
64
  output: definition.output,
55
65
  watch: definition.watch,
56
- collections: contract.collections
66
+ collections: contract.collections,
67
+ ...queryDefaultsOf(definition)
57
68
  },
58
69
  definition,
59
70
  contract
@@ -74,4 +85,4 @@ export {
74
85
  collectionKey,
75
86
  buildCaller
76
87
  };
77
- //# sourceMappingURL=chunk-X5CWPX7H.js.map
88
+ //# sourceMappingURL=chunk-IIQ3WMJK.js.map