@rocicorp/zero 1.10.0-canary.1 → 1.10.0-canary.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 (180) hide show
  1. package/out/zero/package.js +1 -1
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  4. package/out/zero-cache/src/auth/write-authorizer.js +8 -1
  5. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  6. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  7. package/out/zero-cache/src/config/normalize.js +7 -2
  8. package/out/zero-cache/src/config/normalize.js.map +1 -1
  9. package/out/zero-cache/src/config/zero-config.d.ts +14 -4
  10. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  11. package/out/zero-cache/src/config/zero-config.js +26 -8
  12. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  13. package/out/zero-cache/src/db/transaction-pool.d.ts +5 -2
  14. package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
  15. package/out/zero-cache/src/db/transaction-pool.js +5 -3
  16. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  17. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  18. package/out/zero-cache/src/server/change-streamer.js +24 -13
  19. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  20. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  21. package/out/zero-cache/src/server/worker-urls.d.ts +0 -1
  22. package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
  23. package/out/zero-cache/src/server/worker-urls.js +1 -2
  24. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  25. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
  26. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  27. package/out/zero-cache/src/services/change-source/pg/change-source.js +1 -1
  28. package/out/zero-cache/src/services/change-source/pg/decommission.d.ts.map +1 -1
  29. package/out/zero-cache/src/services/change-source/pg/decommission.js +20 -11
  30. package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
  31. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  32. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +51 -74
  33. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  34. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +20 -0
  35. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
  36. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +43 -3
  37. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  38. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +24 -1
  39. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  40. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +35 -5
  41. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  42. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +1 -4
  43. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js +11 -18
  45. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
  46. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +8 -3
  48. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  49. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts +34 -0
  50. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts.map +1 -0
  51. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js +67 -0
  52. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js.map +1 -0
  53. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -4
  54. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +8 -2
  56. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  57. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +34 -13
  58. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +149 -27
  60. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  61. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +3 -9
  62. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  64. package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts +11 -0
  65. package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts.map +1 -0
  66. package/out/zero-cache/src/services/change-streamer/shard-sampling.js +19 -0
  67. package/out/zero-cache/src/services/change-streamer/shard-sampling.js.map +1 -0
  68. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +11 -1
  69. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
  70. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +38 -5
  71. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
  72. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +25 -1
  73. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +1 -1
  74. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +95 -12
  75. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
  76. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts +83 -0
  77. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts.map +1 -0
  78. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js +417 -0
  79. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js.map +1 -0
  80. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts +73 -0
  81. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts.map +1 -0
  82. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js +157 -0
  83. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -0
  84. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +1 -1
  85. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -1
  86. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +8 -3
  87. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -1
  88. package/out/zero-cache/src/services/change-streamer/storer.d.ts +23 -0
  89. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  90. package/out/zero-cache/src/services/change-streamer/storer.js +40 -0
  91. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  92. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts +29 -0
  93. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts.map +1 -0
  94. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js +154 -0
  95. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js.map +1 -0
  96. package/out/zero-cache/src/services/http-service.d.ts +1 -0
  97. package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
  98. package/out/zero-cache/src/services/http-service.js +7 -3
  99. package/out/zero-cache/src/services/http-service.js.map +1 -1
  100. package/out/zero-cache/src/services/litestream/metrics.d.ts +1 -0
  101. package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
  102. package/out/zero-cache/src/services/litestream/metrics.js +4 -1
  103. package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
  104. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  105. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
  106. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  107. package/out/zero-cache/src/services/replicator/change-log-db.d.ts +10 -0
  108. package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +1 -1
  109. package/out/zero-cache/src/services/replicator/change-log-db.js +15 -1
  110. package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
  111. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +3 -2
  112. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -1
  113. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +8 -6
  114. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
  115. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +3 -2
  116. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
  117. package/out/zero-cache/src/services/view-syncer/client-handler.js +25 -17
  118. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  119. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  120. package/out/zero-cache/src/services/view-syncer/cvr-store.js +14 -1
  121. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  122. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +3 -3
  123. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  124. package/out/zero-cache/src/services/view-syncer/view-syncer.js +6 -2
  125. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  126. package/out/zero-cache/src/types/downstream.d.ts +11 -0
  127. package/out/zero-cache/src/types/downstream.d.ts.map +1 -0
  128. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  129. package/out/zero-cache/src/types/pg.js +14 -4
  130. package/out/zero-cache/src/types/pg.js.map +1 -1
  131. package/out/zero-cache/src/types/poke-chunk.d.ts +18 -0
  132. package/out/zero-cache/src/types/poke-chunk.d.ts.map +1 -0
  133. package/out/zero-cache/src/types/poke-chunk.js +57 -0
  134. package/out/zero-cache/src/types/poke-chunk.js.map +1 -0
  135. package/out/zero-cache/src/types/processes.js +1 -4
  136. package/out/zero-cache/src/types/processes.js.map +1 -1
  137. package/out/zero-cache/src/workers/connection.d.ts +22 -4
  138. package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
  139. package/out/zero-cache/src/workers/connection.js +112 -14
  140. package/out/zero-cache/src/workers/connection.js.map +1 -1
  141. package/out/zero-client/src/client/version.js +1 -1
  142. package/out/zero-client/src/client/zero-poke-handler.d.ts +7 -2
  143. package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
  144. package/out/zero-client/src/client/zero-poke-handler.js +52 -2
  145. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  146. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  147. package/out/zero-client/src/client/zero.js +38 -6
  148. package/out/zero-client/src/client/zero.js.map +1 -1
  149. package/out/zero-protocol/src/down.d.ts +1 -1
  150. package/out/zero-protocol/src/poke.d.ts +89 -7
  151. package/out/zero-protocol/src/poke.d.ts.map +1 -1
  152. package/out/zero-protocol/src/poke.js +16 -8
  153. package/out/zero-protocol/src/poke.js.map +1 -1
  154. package/out/zero-protocol/src/protocol-version.d.ts +12 -7
  155. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  156. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  157. package/out/zql/src/ivm/memory-source.d.ts +10 -2
  158. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  159. package/out/zql/src/ivm/memory-source.js +14 -6
  160. package/out/zql/src/ivm/memory-source.js.map +1 -1
  161. package/out/zql/src/ivm/stream.d.ts +0 -1
  162. package/out/zql/src/ivm/stream.d.ts.map +1 -1
  163. package/out/zql/src/ivm/stream.js +1 -7
  164. package/out/zql/src/ivm/stream.js.map +1 -1
  165. package/out/zql/src/ivm/union-fan-in.d.ts +1 -1
  166. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  167. package/out/zql/src/ivm/union-fan-in.js +11 -2
  168. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  169. package/out/zqlite/src/db.js +2 -2
  170. package/out/zqlite/src/sqlite-cost-model.js +5 -5
  171. package/out/zqlite/src/table-source.d.ts.map +1 -1
  172. package/out/zqlite/src/table-source.js +4 -4
  173. package/out/zqlite/src/table-source.js.map +1 -1
  174. package/package.json +1 -1
  175. package/out/zero-cache/src/server/backup-watermark-poller.js +0 -31
  176. package/out/zero-cache/src/server/backup-watermark-poller.js.map +0 -1
  177. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts +0 -57
  178. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts.map +0 -1
  179. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js +0 -137
  180. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"connection.js","names":["#ws","#wsID","#protocolVersion","#lc","#onClose","#messageHandler","#downstreamMsgTimer","#webSocketErrors","#handleClose","#handleError","#proxyInbound","#maybeSendPong","#closeWithError","#closed","#viewSyncerOutboundStream","#pusherOutboundStream","#handleMessage","#handleMessageResult","#closeWithThrown","#proxyOutbound","#recordWebSocketError","#lastDownstreamMsgTime"],"sources":["../../../../../zero-cache/src/workers/connection.ts"],"sourcesContent":["import {pipeline, Readable, Writable} from 'node:stream';\nimport type {LogContext, LogLevel} from '@rocicorp/logger';\nimport type {CloseEvent, Data, ErrorEvent} from 'ws';\nimport WebSocket, {createWebSocketStream} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport type {ConnectedMessage} from '../../../zero-protocol/src/connect.ts';\nimport type {Downstream} from '../../../zero-protocol/src/down.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport {\n isProtocolError,\n type ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {\n MIN_SERVER_SUPPORTED_SYNC_PROTOCOL,\n PROTOCOL_VERSION,\n} from '../../../zero-protocol/src/protocol-version.ts';\nimport {upstreamSchema, type Upstream} from '../../../zero-protocol/src/up.ts';\nimport {getOrCreateCounter} from '../observability/metrics.ts';\nimport {\n ProtocolErrorWithLevel,\n getLogLevel,\n wrapWithProtocolError,\n} from '../types/error-with-level.ts';\nimport type {Source} from '../types/streams.ts';\nimport type {ConnectParams} from './connect-params.ts';\n\nexport type HandlerResult =\n | {\n type: 'ok';\n }\n | {\n type: 'fatal';\n error: ErrorBody;\n }\n | {\n type: 'transient';\n errors: ErrorBody[];\n }\n | StreamResult;\n\nexport type StreamResult = {\n type: 'stream';\n source: 'viewSyncer' | 'pusher';\n stream: Source<Downstream>;\n};\n\nexport interface MessageHandler {\n handleMessage(msg: Upstream): Promise<HandlerResult[]>;\n}\n\n// Ensures that a downstream message is sent at least every interval, sending a\n// 'pong' if necessary. This is set to be slightly longer than the client-side\n// PING_INTERVAL of 5 seconds, so that in the common case, 'pong's are sent in\n// response to client-initiated 'ping's. However, if the inbound stream is\n// backed up because a command is taking a long time to process, the pings\n// will be stuck in the queue (i.e. back-pressured), in which case pongs will\n// be manually sent to notify the client of server liveness.\n//\n// This is equivalent to what is done for Postgres keepalives on the\n// replication stream (which can similarly be back-pressured):\n// https://github.com/rocicorp/mono/blob/f98cb369a2dbb15650328859c732db358f187ef0/packages/zero-cache/src/services/change-source/pg/logical-replication/stream.ts#L21\nconst DOWNSTREAM_MSG_INTERVAL_MS = 6_000;\nexport const WEBSOCKET_SEND_TIMEOUT_MS = 10_000;\nconst PROTOCOL_VERSION_ATTRIBUTE = 'protocol.version';\nconst EVENT_TYPE_ATTRIBUTE = 'event.type';\n\n/**\n * Represents a connection between the client and server.\n *\n * Handles incoming messages on the connection and dispatches\n * them to the correct service.\n *\n * Listens to the ViewSyncer and sends messages to the client.\n */\nexport class Connection {\n readonly #ws: WebSocket;\n readonly #wsID: string;\n readonly #protocolVersion: number;\n readonly #lc: LogContext;\n readonly #onClose: () => void;\n readonly #messageHandler: MessageHandler;\n readonly #downstreamMsgTimer: NodeJS.Timeout | undefined;\n readonly #webSocketErrors = getOrCreateCounter(\n 'sync',\n 'websocket.errors',\n 'Client WebSocket error events.',\n );\n\n #viewSyncerOutboundStream: Source<Downstream> | undefined;\n #pusherOutboundStream: Source<Downstream> | undefined;\n #closed = false;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n ws: WebSocket,\n messageHandler: MessageHandler,\n onClose: () => void,\n ) {\n const {clientGroupID, clientID, wsID, protocolVersion} = connectParams;\n this.#messageHandler = messageHandler;\n\n this.#ws = ws;\n this.#wsID = wsID;\n this.#protocolVersion = protocolVersion;\n\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#lc.debug?.('new connection');\n this.#onClose = onClose;\n\n this.#ws.addEventListener('close', this.#handleClose);\n this.#ws.addEventListener('error', this.#handleError);\n\n this.#proxyInbound();\n this.#downstreamMsgTimer = setInterval(\n this.#maybeSendPong,\n DOWNSTREAM_MSG_INTERVAL_MS / 2,\n );\n }\n\n /**\n * Checks the protocol version and errors for unsupported protocols,\n * sending the initial `connected` response on success.\n *\n * This is early in the connection lifecycle because {@link #handleMessage}\n * will only parse messages with schema(s) of supported protocol versions.\n */\n init(): boolean {\n if (\n this.#protocolVersion > PROTOCOL_VERSION ||\n this.#protocolVersion < MIN_SERVER_SUPPORTED_SYNC_PROTOCOL\n ) {\n this.#closeWithError({\n kind: ErrorKind.VersionNotSupported,\n message: `server is at sync protocol v${PROTOCOL_VERSION} and does not support v${\n this.#protocolVersion\n }. The ${\n this.#protocolVersion > PROTOCOL_VERSION ? 'server' : 'client'\n } must be updated to a newer release.`,\n origin: ErrorOrigin.ZeroCache,\n });\n } else {\n const connectedMessage: ConnectedMessage = [\n 'connected',\n {wsid: this.#wsID, timestamp: Date.now()},\n ];\n this.send(connectedMessage, 'ignore-backpressure');\n return true;\n }\n return false;\n }\n\n close(reason: string, ...args: unknown[]) {\n if (this.#closed) {\n return;\n }\n this.#closed = true;\n this.#lc.info?.(`closing connection: ${reason}`, ...args);\n this.#ws.removeEventListener('close', this.#handleClose);\n this.#ws.removeEventListener('error', this.#handleError);\n this.#viewSyncerOutboundStream?.cancel();\n this.#viewSyncerOutboundStream = undefined;\n this.#pusherOutboundStream?.cancel();\n this.#pusherOutboundStream = undefined;\n this.#onClose();\n if (this.#ws.readyState !== this.#ws.CLOSED) {\n this.#ws.close();\n }\n clearTimeout(this.#downstreamMsgTimer);\n\n // spin down services if we have\n // no more client connections for the client group?\n }\n\n handleInitConnection(initConnectionMsg: string) {\n return this.#handleMessage({data: initConnectionMsg});\n }\n\n #handleMessage = async (event: {data: Data}) => {\n const data = event.data.toString();\n if (this.#closed) {\n this.#lc.debug?.('Ignoring message received after closed', data);\n return;\n }\n\n let msg;\n try {\n const value = JSON.parse(data);\n msg = valita.parse(value, upstreamSchema);\n } catch (e) {\n const errorBody = {\n kind: ErrorKind.InvalidMessage,\n message: String(e),\n origin: ErrorOrigin.ZeroCache,\n } as const;\n this.#closeWithError(\n errorBody,\n new ProtocolErrorWithLevel(errorBody, 'warn'),\n );\n return;\n }\n\n try {\n const msgType = msg[0];\n if (msgType === 'ping') {\n this.send(['pong', {}], 'ignore-backpressure');\n return;\n }\n\n const result = await this.#messageHandler.handleMessage(msg);\n for (const r of result) {\n this.#handleMessageResult(r);\n }\n } catch (e) {\n this.#closeWithThrown(e);\n }\n };\n\n #handleMessageResult(result: HandlerResult): void {\n switch (result.type) {\n case 'fatal':\n this.#closeWithError(result.error);\n break;\n case 'ok':\n break;\n case 'stream': {\n switch (result.source) {\n case 'viewSyncer':\n assert(\n this.#viewSyncerOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#viewSyncerOutboundStream = result.stream;\n break;\n case 'pusher':\n assert(\n this.#pusherOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#pusherOutboundStream = result.stream;\n break;\n }\n this.#proxyOutbound(result.stream);\n break;\n }\n case 'transient': {\n for (const error of result.errors) {\n this.sendError(error);\n }\n }\n }\n }\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n if (!wasClean) {\n this.#recordWebSocketError('unclean_close');\n }\n this.close('WebSocket close event', {code, reason, wasClean});\n };\n\n #handleError = (e: ErrorEvent) => {\n this.#recordWebSocketError('error_event');\n this.#lc.warn?.('WebSocket error event', e.message, e.error);\n };\n\n #recordWebSocketError(eventType: string) {\n this.#webSocketErrors.add(1, {\n [PROTOCOL_VERSION_ATTRIBUTE]: this.#protocolVersion,\n [EVENT_TYPE_ATTRIBUTE]: eventType,\n });\n }\n\n #proxyInbound() {\n pipeline(\n createWebSocketStream(this.#ws),\n new Writable({\n write: (data, _encoding, callback) => {\n this.#handleMessage({data}).then(() => callback(), callback);\n },\n }),\n // The done callback is not used, as #handleClose and #handleError,\n // configured on the underlying WebSocket, provide more complete\n // information.\n () => {},\n );\n }\n\n #proxyOutbound(outboundStream: Source<Downstream>) {\n // Note: createWebSocketStream() is avoided here in order to control\n // exception handling with #closeWithThrown(). If the Writable\n // from createWebSocketStream() were instead used, exceptions\n // from the outboundStream result in the Writable closing the\n // the websocket before the error message can be sent.\n pipeline(\n Readable.from(outboundStream),\n new Writable({\n objectMode: true,\n write: (downstream: Downstream, _encoding, callback) =>\n this.send(downstream, callback),\n }),\n e =>\n e\n ? this.#closeWithThrown(e)\n : this.close(`downstream closed by ViewSyncer`),\n );\n }\n\n #closeWithThrown(e: unknown) {\n const errorBody =\n findProtocolError(e)?.errorBody ?? wrapWithProtocolError(e).errorBody;\n\n this.#closeWithError(errorBody, e);\n }\n\n #closeWithError(errorBody: ErrorBody, thrown?: unknown) {\n this.sendError(errorBody, thrown);\n this.close(\n `${errorBody.kind} (${errorBody.origin}): ${errorBody.message}`,\n errorBody,\n );\n }\n\n #lastDownstreamMsgTime = Date.now();\n\n #maybeSendPong = () => {\n if (Date.now() - this.#lastDownstreamMsgTime > DOWNSTREAM_MSG_INTERVAL_MS) {\n this.#lc.debug?.('manually sending pong');\n this.send(['pong', {}], 'ignore-backpressure');\n }\n };\n\n send(\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n ) {\n this.#lastDownstreamMsgTime = Date.now();\n return send(this.#lc, this.#ws, data, callback);\n }\n\n sendError(errorBody: ErrorBody, thrown?: unknown) {\n sendError(this.#lc, this.#ws, errorBody, thrown);\n }\n}\n\nexport type WebSocketLike = Pick<WebSocket, 'readyState'> & {\n send(data: string, cb?: (err?: Error) => void): void;\n};\n\n// Exported for testing purposes.\nexport function send(\n lc: LogContext,\n ws: WebSocketLike,\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n) {\n if (ws.readyState === WebSocket.OPEN) {\n const serialized = JSON.stringify(data);\n if (callback === 'ignore-backpressure') {\n ws.send(serialized);\n return;\n }\n\n let completed = false;\n const timer = setTimeout(() => {\n complete(\n new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: `WebSocket send timed out after ${WEBSOCKET_SEND_TIMEOUT_MS} ms`,\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n ),\n );\n }, WEBSOCKET_SEND_TIMEOUT_MS);\n timer.unref();\n\n const complete = (error?: Error | null) => {\n if (completed) {\n return;\n }\n completed = true;\n clearTimeout(timer);\n callback(error);\n };\n\n ws.send(serialized, complete);\n } else {\n lc.debug?.(`Dropping outbound message on ws (state: ${ws.readyState})`, {\n dropped: data,\n });\n if (callback !== 'ignore-backpressure') {\n callback(\n new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: 'WebSocket closed',\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n ),\n );\n }\n }\n}\n\nexport function sendError(\n lc: LogContext,\n ws: WebSocket,\n errorBody: ErrorBody,\n thrown?: unknown,\n) {\n lc = lc.withContext('errorKind', errorBody.kind);\n\n let logLevel: LogLevel;\n\n // If the thrown error is a ProtocolErrorWithLevel, its explicit logLevel takes precedence\n if (thrown instanceof ProtocolErrorWithLevel) {\n logLevel = thrown.logLevel;\n }\n // Errors with errno or transient socket codes are low-level, transient I/O issues\n // (e.g., EPIPE, ECONNRESET) and should be warnings, not errors\n else if (\n hasErrno(thrown) ||\n hasTransientSocketCode(thrown) ||\n isTransientSocketMessage(errorBody.message)\n ) {\n logLevel = 'warn';\n }\n // Fallback: check errorBody.kind for errors that weren't thrown as ProtocolErrorWithLevel\n else if (\n errorBody.kind === ErrorKind.ClientNotFound ||\n errorBody.kind === ErrorKind.TransformFailed\n ) {\n logLevel = 'warn';\n } else {\n logLevel = thrown ? getLogLevel(thrown) : 'info';\n }\n\n lc[logLevel]?.('Sending error on WebSocket', errorBody, thrown ?? '');\n send(lc, ws, ['error', errorBody], 'ignore-backpressure');\n}\n\nexport function findProtocolError(error: unknown): ProtocolError | undefined {\n if (isProtocolError(error)) {\n return error;\n }\n if (error instanceof Error && error.cause) {\n return findProtocolError(error.cause);\n }\n return undefined;\n}\n\nfunction hasErrno(error: unknown): boolean {\n return Boolean(\n error &&\n typeof error === 'object' &&\n 'errno' in error &&\n typeof (error as {errno: unknown}).errno !== 'undefined',\n );\n}\n\n// System error codes that indicate transient socket conditions.\n// These are checked via the `code` property on errors.\nconst TRANSIENT_SOCKET_ERROR_CODES = new Set([\n 'EPIPE',\n 'ECONNRESET',\n 'ECANCELED',\n]);\n\n// Error messages that indicate transient socket conditions but don't have\n// standard error codes (e.g., WebSocket library errors).\nconst TRANSIENT_SOCKET_MESSAGE_PATTERNS = [\n 'socket was closed while data was being compressed',\n];\n\nfunction hasTransientSocketCode(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n const maybeCode =\n 'code' in error ? String((error as {code?: unknown}).code) : undefined;\n return Boolean(\n maybeCode && TRANSIENT_SOCKET_ERROR_CODES.has(maybeCode.toUpperCase()),\n );\n}\n\nfunction isTransientSocketMessage(message: string | undefined): boolean {\n if (!message) {\n return false;\n }\n const lower = message.toLowerCase();\n return TRANSIENT_SOCKET_MESSAGE_PATTERNS.some(pattern =>\n lower.includes(pattern),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"connection.js","names":["#ws","#wsID","#protocolVersion","#lc","#onClose","#messageHandler","#downstreamSender","#downstreamMsgTimer","#webSocketErrors","#handleClose","#handleError","#proxyInbound","#maybeSendPong","#closeWithError","#closed","#viewSyncerOutboundStream","#pusherOutboundStream","#handleMessage","#handleMessageResult","#closeWithThrown","#proxyOutbound","#recordWebSocketError","#lastDownstreamMsgTime","#pokeChunkEncoder","#sendPokePart","#sendPokeEnd"],"sources":["../../../../../zero-cache/src/workers/connection.ts"],"sourcesContent":["import {pipeline, Readable, Writable} from 'node:stream';\nimport type {LogContext, LogLevel} from '@rocicorp/logger';\nimport type {CloseEvent, Data, ErrorEvent} from 'ws';\nimport WebSocket, {createWebSocketStream} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport type {ConnectedMessage} from '../../../zero-protocol/src/connect.ts';\nimport type {Downstream} from '../../../zero-protocol/src/down.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport {\n isProtocolError,\n type ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {\n POKE_CHUNK_PROTOCOL_VERSION,\n type PokeChunk,\n type PokeEndMessage,\n type PokePartMessage,\n} from '../../../zero-protocol/src/poke.ts';\nimport {\n MIN_SERVER_SUPPORTED_SYNC_PROTOCOL,\n PROTOCOL_VERSION,\n} from '../../../zero-protocol/src/protocol-version.ts';\nimport {upstreamSchema, type Upstream} from '../../../zero-protocol/src/up.ts';\nimport {getOrCreateCounter} from '../observability/metrics.ts';\nimport type {ViewSyncerDownstream} from '../types/downstream.ts';\nimport {\n ProtocolErrorWithLevel,\n getLogLevel,\n wrapWithProtocolError,\n} from '../types/error-with-level.ts';\nimport {PokeChunkEncoder} from '../types/poke-chunk.ts';\nimport type {Source} from '../types/streams.ts';\nimport type {ConnectParams} from './connect-params.ts';\n\nexport type HandlerResult =\n | {\n type: 'ok';\n }\n | {\n type: 'fatal';\n error: ErrorBody;\n }\n | {\n type: 'transient';\n errors: ErrorBody[];\n }\n | StreamResult;\n\nexport type StreamResult =\n | {\n type: 'stream';\n source: 'viewSyncer';\n stream: Source<ViewSyncerDownstream>;\n }\n | {\n type: 'stream';\n source: 'pusher';\n stream: Source<Downstream>;\n };\n\nexport interface MessageHandler {\n handleMessage(msg: Upstream): Promise<HandlerResult[]>;\n}\n\n// Ensures that a downstream message is sent at least every interval, sending a\n// 'pong' if necessary. This is set to be slightly longer than the client-side\n// PING_INTERVAL of 5 seconds, so that in the common case, 'pong's are sent in\n// response to client-initiated 'ping's. However, if the inbound stream is\n// backed up because a command is taking a long time to process, the pings\n// will be stuck in the queue (i.e. back-pressured), in which case pongs will\n// be manually sent to notify the client of server liveness.\n//\n// This is equivalent to what is done for Postgres keepalives on the\n// replication stream (which can similarly be back-pressured):\n// https://github.com/rocicorp/mono/blob/f98cb369a2dbb15650328859c732db358f187ef0/packages/zero-cache/src/services/change-source/pg/logical-replication/stream.ts#L21\nconst DOWNSTREAM_MSG_INTERVAL_MS = 6_000;\nexport const WEBSOCKET_SEND_TIMEOUT_MS = 10_000;\nconst PROTOCOL_VERSION_ATTRIBUTE = 'protocol.version';\nconst EVENT_TYPE_ATTRIBUTE = 'event.type';\n\n/**\n * Represents a connection between the client and server.\n *\n * Handles incoming messages on the connection and dispatches\n * them to the correct service.\n *\n * Listens to the ViewSyncer and sends messages to the client.\n */\nexport class Connection {\n readonly #ws: WebSocket;\n readonly #wsID: string;\n readonly #protocolVersion: number;\n readonly #lc: LogContext;\n readonly #onClose: () => void;\n readonly #messageHandler: MessageHandler;\n readonly #downstreamSender: DownstreamSender;\n readonly #downstreamMsgTimer: NodeJS.Timeout | undefined;\n readonly #webSocketErrors = getOrCreateCounter(\n 'sync',\n 'websocket.errors',\n 'Client WebSocket error events.',\n );\n\n #viewSyncerOutboundStream: Source<ViewSyncerDownstream> | undefined;\n #pusherOutboundStream: Source<Downstream> | undefined;\n #closed = false;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n ws: WebSocket,\n messageHandler: MessageHandler,\n onClose: () => void,\n ) {\n const {clientGroupID, clientID, wsID, protocolVersion} = connectParams;\n this.#messageHandler = messageHandler;\n\n this.#ws = ws;\n this.#wsID = wsID;\n this.#protocolVersion = protocolVersion;\n\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#downstreamSender = new DownstreamSender(\n this.#lc,\n ws,\n protocolVersion,\n );\n this.#lc.debug?.('new connection');\n this.#onClose = onClose;\n\n this.#ws.addEventListener('close', this.#handleClose);\n this.#ws.addEventListener('error', this.#handleError);\n\n this.#proxyInbound();\n this.#downstreamMsgTimer = setInterval(\n this.#maybeSendPong,\n DOWNSTREAM_MSG_INTERVAL_MS / 2,\n );\n }\n\n /**\n * Checks the protocol version and errors for unsupported protocols,\n * sending the initial `connected` response on success.\n *\n * This is early in the connection lifecycle because {@link #handleMessage}\n * will only parse messages with schema(s) of supported protocol versions.\n */\n init(): boolean {\n if (\n this.#protocolVersion > PROTOCOL_VERSION ||\n this.#protocolVersion < MIN_SERVER_SUPPORTED_SYNC_PROTOCOL\n ) {\n this.#closeWithError({\n kind: ErrorKind.VersionNotSupported,\n message: `server is at sync protocol v${PROTOCOL_VERSION} and does not support v${\n this.#protocolVersion\n }. The ${\n this.#protocolVersion > PROTOCOL_VERSION ? 'server' : 'client'\n } must be updated to a newer release.`,\n origin: ErrorOrigin.ZeroCache,\n });\n } else {\n const connectedMessage: ConnectedMessage = [\n 'connected',\n {wsid: this.#wsID, timestamp: Date.now()},\n ];\n this.send(connectedMessage, 'ignore-backpressure');\n return true;\n }\n return false;\n }\n\n close(reason: string, ...args: unknown[]) {\n if (this.#closed) {\n return;\n }\n this.#closed = true;\n this.#lc.info?.(`closing connection: ${reason}`, ...args);\n this.#ws.removeEventListener('close', this.#handleClose);\n this.#ws.removeEventListener('error', this.#handleError);\n this.#viewSyncerOutboundStream?.cancel();\n this.#viewSyncerOutboundStream = undefined;\n this.#pusherOutboundStream?.cancel();\n this.#pusherOutboundStream = undefined;\n this.#onClose();\n if (this.#ws.readyState !== this.#ws.CLOSED) {\n this.#ws.close();\n }\n clearTimeout(this.#downstreamMsgTimer);\n\n // spin down services if we have\n // no more client connections for the client group?\n }\n\n handleInitConnection(initConnectionMsg: string) {\n return this.#handleMessage({data: initConnectionMsg});\n }\n\n #handleMessage = async (event: {data: Data}) => {\n const data = event.data.toString();\n if (this.#closed) {\n this.#lc.debug?.('Ignoring message received after closed', data);\n return;\n }\n\n let msg;\n try {\n const value = JSON.parse(data);\n msg = valita.parse(value, upstreamSchema);\n } catch (e) {\n const errorBody = {\n kind: ErrorKind.InvalidMessage,\n message: String(e),\n origin: ErrorOrigin.ZeroCache,\n } as const;\n this.#closeWithError(\n errorBody,\n new ProtocolErrorWithLevel(errorBody, 'warn'),\n );\n return;\n }\n\n try {\n const msgType = msg[0];\n if (msgType === 'ping') {\n this.send(['pong', {}], 'ignore-backpressure');\n return;\n }\n\n const result = await this.#messageHandler.handleMessage(msg);\n for (const r of result) {\n this.#handleMessageResult(r);\n }\n } catch (e) {\n this.#closeWithThrown(e);\n }\n };\n\n #handleMessageResult(result: HandlerResult): void {\n switch (result.type) {\n case 'fatal':\n this.#closeWithError(result.error);\n break;\n case 'ok':\n break;\n case 'stream': {\n switch (result.source) {\n case 'viewSyncer':\n assert(\n this.#viewSyncerOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#viewSyncerOutboundStream = result.stream;\n break;\n case 'pusher':\n assert(\n this.#pusherOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#pusherOutboundStream = result.stream;\n break;\n }\n if (result.source === 'viewSyncer') {\n this.#proxyOutbound(result.stream, (downstream, callback) =>\n this.send(downstream.message, callback, downstream.serialized),\n );\n } else {\n this.#proxyOutbound(result.stream, (downstream, callback) =>\n this.send(downstream, callback),\n );\n }\n break;\n }\n case 'transient': {\n for (const error of result.errors) {\n this.sendError(error);\n }\n }\n }\n }\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n if (!wasClean) {\n this.#recordWebSocketError('unclean_close');\n }\n this.close('WebSocket close event', {code, reason, wasClean});\n };\n\n #handleError = (e: ErrorEvent) => {\n this.#recordWebSocketError('error_event');\n this.#lc.warn?.('WebSocket error event', e.message, e.error);\n };\n\n #recordWebSocketError(eventType: string) {\n this.#webSocketErrors.add(1, {\n [PROTOCOL_VERSION_ATTRIBUTE]: this.#protocolVersion,\n [EVENT_TYPE_ATTRIBUTE]: eventType,\n });\n }\n\n #proxyInbound() {\n pipeline(\n createWebSocketStream(this.#ws),\n new Writable({\n write: (data, _encoding, callback) => {\n this.#handleMessage({data}).then(() => callback(), callback);\n },\n }),\n // The done callback is not used, as #handleClose and #handleError,\n // configured on the underlying WebSocket, provide more complete\n // information.\n () => {},\n );\n }\n\n #proxyOutbound<T>(\n outboundStream: Source<T>,\n sendMessage: (\n downstream: T,\n callback: (err?: Error | null) => void,\n ) => void,\n ) {\n // Note: createWebSocketStream() is avoided here in order to control\n // exception handling with #closeWithThrown(). If the Writable\n // from createWebSocketStream() were instead used, exceptions\n // from the outboundStream result in the Writable closing the\n // the websocket before the error message can be sent.\n pipeline(\n Readable.from(outboundStream),\n new Writable({\n objectMode: true,\n write: (downstream: T, _encoding, callback) =>\n sendMessage(downstream, callback),\n }),\n e =>\n e\n ? this.#closeWithThrown(e)\n : this.close(`downstream closed by ViewSyncer`),\n );\n }\n\n #closeWithThrown(e: unknown) {\n const errorBody =\n findProtocolError(e)?.errorBody ?? wrapWithProtocolError(e).errorBody;\n\n this.#closeWithError(errorBody, e);\n }\n\n #closeWithError(errorBody: ErrorBody, thrown?: unknown) {\n this.sendError(errorBody, thrown);\n this.close(\n `${errorBody.kind} (${errorBody.origin}): ${errorBody.message}`,\n errorBody,\n );\n }\n\n #lastDownstreamMsgTime = Date.now();\n\n #maybeSendPong = () => {\n if (Date.now() - this.#lastDownstreamMsgTime > DOWNSTREAM_MSG_INTERVAL_MS) {\n this.#lc.debug?.('manually sending pong');\n this.send(['pong', {}], 'ignore-backpressure');\n }\n };\n\n send(\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n serialized?: string | undefined,\n ) {\n this.#lastDownstreamMsgTime = Date.now();\n this.#downstreamSender.send(data, callback, serialized);\n }\n\n sendError(errorBody: ErrorBody, thrown?: unknown) {\n sendError(this.#lc, this.#ws, errorBody, thrown);\n }\n}\n\nexport type WebSocketLike = Pick<WebSocket, 'readyState'> & {\n send(data: string | PokeChunk, cb?: (err?: Error) => void): void;\n};\n\n// Exported for testing purposes.\nexport function send(\n lc: LogContext,\n ws: WebSocketLike,\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n serialized?: string | undefined,\n) {\n if (ws.readyState === WebSocket.OPEN) {\n serialized ??= JSON.stringify(data);\n if (callback === 'ignore-backpressure') {\n ws.send(serialized);\n return;\n }\n\n let completed = false;\n const timer = setTimeout(() => {\n complete(webSocketSendTimeoutError());\n }, WEBSOCKET_SEND_TIMEOUT_MS);\n timer.unref();\n\n const complete = (error?: Error | null) => {\n if (completed) {\n return;\n }\n completed = true;\n clearTimeout(timer);\n callback(error);\n };\n\n ws.send(serialized, complete);\n } else {\n lc.debug?.(`Dropping outbound message on ws (state: ${ws.readyState})`, {\n dropped: data,\n });\n if (callback !== 'ignore-backpressure') {\n callback(\n new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: 'WebSocket closed',\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n ),\n );\n }\n }\n}\n\n/**\n * Sends downstream messages in the format supported by a connection's sync\n * protocol version. Keep version forks contained here so the view-syncer can\n * continue producing one canonical poke representation.\n *\n * Exported for compatibility testing.\n */\nexport class DownstreamSender {\n readonly #lc: LogContext;\n readonly #ws: WebSocketLike;\n readonly #protocolVersion: number;\n #pokeChunkEncoder: PokeChunkEncoder | undefined;\n\n constructor(lc: LogContext, ws: WebSocketLike, protocolVersion: number) {\n this.#lc = lc;\n this.#ws = ws;\n this.#protocolVersion = protocolVersion;\n }\n\n send(\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n serialized?: string | undefined,\n ): void {\n if (this.#protocolVersion >= POKE_CHUNK_PROTOCOL_VERSION) {\n switch (data[0]) {\n case 'pokeStart':\n assert(\n this.#pokeChunkEncoder === undefined,\n 'cannot start a poke while another poke is in progress',\n );\n this.#pokeChunkEncoder = new PokeChunkEncoder();\n break;\n case 'pokePart':\n this.#sendPokePart(data, callback, serialized);\n return;\n case 'pokeEnd':\n this.#sendPokeEnd(data, callback);\n return;\n }\n }\n send(this.#lc, this.#ws, data, callback, serialized);\n }\n\n #sendPokePart(\n pokePart: PokePartMessage,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n serialized?: string | undefined,\n ): void {\n const encoder = this.#pokeChunkEncoder;\n assert(encoder, 'pokePart received without a pokeStart');\n\n serialized ??= JSON.stringify(pokePart);\n assert(\n serialized.startsWith(POKE_PART_PREFIX) && serialized.endsWith(']'),\n 'invalid serialized pokePart',\n );\n void encoder\n .addPatch(serialized.slice(POKE_PART_PREFIX.length, -1), chunk =>\n sendBinary(this.#lc, this.#ws, chunk),\n )\n .then(\n () => invokeCallback(callback),\n error => invokeCallback(callback, toError(error)),\n );\n }\n\n #sendPokeEnd(\n pokeEnd: PokeEndMessage,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n ): void {\n const encoder = this.#pokeChunkEncoder;\n assert(encoder, 'pokeEnd received without a pokeStart');\n this.#pokeChunkEncoder = undefined;\n\n if (pokeEnd[1].cancel) {\n encoder.cancel();\n send(this.#lc, this.#ws, pokeEnd, callback);\n return;\n }\n\n void encoder\n .finish(chunk => sendBinary(this.#lc, this.#ws, chunk))\n .then(() => sendAsync(this.#lc, this.#ws, pokeEnd))\n .then(\n () => invokeCallback(callback),\n error => invokeCallback(callback, toError(error)),\n );\n }\n}\n\nconst POKE_PART_PREFIX = '[\"pokePart\",';\n\nfunction sendAsync(\n lc: LogContext,\n ws: WebSocketLike,\n data: Downstream,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n send(lc, ws, data, error => (error ? reject(error) : resolve()));\n });\n}\n\nfunction sendBinary(\n lc: LogContext,\n ws: WebSocketLike,\n chunk: PokeChunk,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n if (ws.readyState !== WebSocket.OPEN) {\n const error = webSocketClosedError();\n lc.debug?.(\n `Dropping outbound binary message on ws (state: ${ws.readyState})`,\n );\n reject(error);\n return;\n }\n let completed = false;\n const timer = setTimeout(\n () => complete(webSocketSendTimeoutError()),\n WEBSOCKET_SEND_TIMEOUT_MS,\n );\n timer.unref();\n\n const complete = (error?: Error | null) => {\n if (completed) {\n return;\n }\n completed = true;\n clearTimeout(timer);\n error ? reject(error) : resolve();\n };\n\n ws.send(chunk, complete);\n });\n}\n\nfunction invokeCallback(\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n error?: Error,\n): void {\n if (callback !== 'ignore-backpressure') {\n callback(error);\n }\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction webSocketClosedError(): ProtocolErrorWithLevel {\n return new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: 'WebSocket closed',\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n );\n}\n\nfunction webSocketSendTimeoutError(): ProtocolErrorWithLevel {\n return new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: `WebSocket send timed out after ${WEBSOCKET_SEND_TIMEOUT_MS} ms`,\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n );\n}\n\nexport function sendError(\n lc: LogContext,\n ws: WebSocket,\n errorBody: ErrorBody,\n thrown?: unknown,\n) {\n lc = lc.withContext('errorKind', errorBody.kind);\n\n let logLevel: LogLevel;\n\n // If the thrown error is a ProtocolErrorWithLevel, its explicit logLevel takes precedence\n if (thrown instanceof ProtocolErrorWithLevel) {\n logLevel = thrown.logLevel;\n }\n // Errors with errno or transient socket codes are low-level, transient I/O issues\n // (e.g., EPIPE, ECONNRESET) and should be warnings, not errors\n else if (\n hasErrno(thrown) ||\n hasTransientSocketCode(thrown) ||\n isTransientSocketMessage(errorBody.message)\n ) {\n logLevel = 'warn';\n }\n // Fallback: check errorBody.kind for errors that weren't thrown as ProtocolErrorWithLevel\n else if (\n errorBody.kind === ErrorKind.ClientNotFound ||\n errorBody.kind === ErrorKind.TransformFailed\n ) {\n logLevel = 'warn';\n } else {\n logLevel = thrown ? getLogLevel(thrown) : 'info';\n }\n\n lc[logLevel]?.('Sending error on WebSocket', errorBody, thrown ?? '');\n send(lc, ws, ['error', errorBody], 'ignore-backpressure');\n}\n\nexport function findProtocolError(error: unknown): ProtocolError | undefined {\n if (isProtocolError(error)) {\n return error;\n }\n if (error instanceof Error && error.cause) {\n return findProtocolError(error.cause);\n }\n return undefined;\n}\n\nfunction hasErrno(error: unknown): boolean {\n return Boolean(\n error &&\n typeof error === 'object' &&\n 'errno' in error &&\n typeof (error as {errno: unknown}).errno !== 'undefined',\n );\n}\n\n// System error codes that indicate transient socket conditions.\n// These are checked via the `code` property on errors.\nconst TRANSIENT_SOCKET_ERROR_CODES = new Set([\n 'EPIPE',\n 'ECONNRESET',\n 'ECANCELED',\n]);\n\n// Error messages that indicate transient socket conditions but don't have\n// standard error codes (e.g., WebSocket library errors).\nconst TRANSIENT_SOCKET_MESSAGE_PATTERNS = [\n 'socket was closed while data was being compressed',\n];\n\nfunction hasTransientSocketCode(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n const maybeCode =\n 'code' in error ? String((error as {code?: unknown}).code) : undefined;\n return Boolean(\n maybeCode && TRANSIENT_SOCKET_ERROR_CODES.has(maybeCode.toUpperCase()),\n );\n}\n\nfunction isTransientSocketMessage(message: string | undefined): boolean {\n if (!message) {\n return false;\n }\n const lower = message.toLowerCase();\n return TRANSIENT_SOCKET_MESSAGE_PATTERNS.some(pattern =>\n lower.includes(pattern),\n );\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * The current version of Zero.
4
4
  */
5
- var version = "1.10.0-canary.1";
5
+ var version = "1.10.0-canary.3";
6
6
  //#endregion
7
7
  export { version };
8
8
 
@@ -2,7 +2,7 @@ import type { LogContext } from '@rocicorp/logger';
2
2
  import type { PatchOperationInternal, PokeInternal } from '../../../replicache/src/impl.ts';
3
3
  import type { ClientID } from '../../../replicache/src/sync/ids.ts';
4
4
  import type { MutationPatch } from '../../../zero-protocol/src/mutations-patch.ts';
5
- import type { PokeEndBody, PokePartBody, PokeStartBody } from '../../../zero-protocol/src/poke.ts';
5
+ import { type PokeEndBody, type PokePartBody, type PokeStartBody } from '../../../zero-protocol/src/poke.ts';
6
6
  import { type NameMapper } from '../../../zero-schema/src/name-mapper.ts';
7
7
  import type { Schema } from '../../../zero-types/src/schema.ts';
8
8
  import type { MutationTracker } from './mutation-tracker.ts';
@@ -11,6 +11,10 @@ type PokeAccumulator = {
11
11
  readonly parts: PokePartBody[];
12
12
  readonly pokeEnd: PokeEndBody;
13
13
  };
14
+ export type PokeEndResult = {
15
+ readonly lastMutationIDChangeForSelf: number | undefined;
16
+ readonly hasRows: boolean;
17
+ };
14
18
  /**
15
19
  * Handles the multi-part format of zero pokes.
16
20
  * As an optimization it also debounces pokes, only poking Replicache with a
@@ -25,7 +29,8 @@ export declare class PokeHandler {
25
29
  constructor(replicachePoke: (poke: PokeInternal) => Promise<void>, onPokeError: (error: unknown) => void, clientID: ClientID, schema: Schema, lc: LogContext, mutationTracker: MutationTracker);
26
30
  handlePokeStart(pokeStart: PokeStartBody): void;
27
31
  handlePokePart(pokePart: PokePartBody): number | undefined;
28
- handlePokeEnd(pokeEnd: PokeEndBody): void;
32
+ handlePokeChunk(chunk: Uint8Array): void;
33
+ handlePokeEnd(pokeEnd: PokeEndBody): PokeEndResult | undefined;
29
34
  handleDisconnect(): void;
30
35
  }
31
36
  export declare function mergePokes(pokeBuffer: PokeAccumulator[], schema: Schema, serverToClient: NameMapper): (PokeInternal & {
@@ -1 +1 @@
1
- {"version":3,"file":"zero-poke-handler.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/zero-poke-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EACV,sBAAsB,EACtB,YAAY,EACb,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,qCAAqC,CAAC;AAElE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,+CAA+C,CAAC;AACjF,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,aAAa,EACd,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAO9D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,WAAW;;gBAiBpB,cAAc,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,EACrD,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EACrC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,UAAU,EACd,eAAe,EAAE,eAAe;IAWlC,eAAe,CAAC,SAAS,EAAE,aAAa;IAiBxC,cAAc,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS;IAa1D,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAoBzC,gBAAgB,IAAI,IAAI;CAmFzB;AAED,wBAAgB,UAAU,CACxB,UAAU,EAAE,eAAe,EAAE,EAC7B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,UAAU,GAExB,CAAC,YAAY,GAAG;IAAC,eAAe,CAAC,EAAE,aAAa,EAAE,GAAG,SAAS,CAAA;CAAC,CAAC,GAChE,SAAS,CAyFZ;AAyBD,wBAAgB,kCAAkC,CAChD,EAAE,EAAE,aAAa,GAChB,sBAAsB,CAcxB"}
1
+ {"version":3,"file":"zero-poke-handler.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/zero-poke-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EACV,sBAAsB,EACtB,YAAY,EACb,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,qCAAqC,CAAC;AAGlE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,+CAA+C,CAAC;AACjF,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,YAAY,EAEjB,KAAK,aAAa,EACnB,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAO9D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,CAAC;AAQF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,2BAA2B,EAAE,MAAM,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,WAAW;;gBAiBpB,cAAc,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,EACrD,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EACrC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,UAAU,EACd,eAAe,EAAE,eAAe;IAWlC,eAAe,CAAC,SAAS,EAAE,aAAa;IAkBxC,cAAc,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS;IAiB1D,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAYxC,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,GAAG,SAAS;IAqC9D,gBAAgB,IAAI,IAAI;CAmFzB;AA6BD,wBAAgB,UAAU,CACxB,UAAU,EAAE,eAAe,EAAE,EAC7B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,UAAU,GAExB,CAAC,YAAY,GAAG;IAAC,eAAe,CAAC,EAAE,aAAa,EAAE,GAAG,SAAS,CAAA;CAAC,CAAC,GAChE,SAAS,CAyFZ;AAyBD,wBAAgB,kCAAkC,CAChD,EAAE,EAAE,aAAa,GAChB,sBAAsB,CAcxB"}
@@ -1,5 +1,7 @@
1
1
  import { unreachable } from "../../../shared/src/asserts.js";
2
+ import { parse } from "../../../shared/src/valita.js";
2
3
  import { serverToClient } from "../../../zero-schema/src/name-mapper.js";
4
+ import { pokePartsSchema } from "../../../zero-protocol/src/poke.js";
3
5
  import { toDesiredQueriesKey, toGotQueriesKey, toMutationResponseKey, toPrimaryKeyString } from "./keys.js";
4
6
  import { Lock } from "@rocicorp/lock";
5
7
  //#region ../zero-client/src/client/zero-poke-handler.ts
@@ -41,7 +43,8 @@ var PokeHandler = class {
41
43
  }
42
44
  this.#receivingPoke = {
43
45
  pokeStart,
44
- parts: []
46
+ parts: [],
47
+ chunks: []
45
48
  };
46
49
  }
47
50
  handlePokePart(pokePart) {
@@ -49,9 +52,24 @@ var PokeHandler = class {
49
52
  this.#handlePokeError(`pokePart for ${pokePart.pokeID}, when receiving ${this.#receivingPoke?.pokeStart.pokeID}`);
50
53
  return;
51
54
  }
55
+ if (this.#receivingPoke.chunks.length > 0) {
56
+ this.#handlePokeError("received both pokePart and binary poke chunks");
57
+ return;
58
+ }
52
59
  this.#receivingPoke.parts.push(pokePart);
53
60
  return pokePart.lastMutationIDChanges?.[this.#clientID];
54
61
  }
62
+ handlePokeChunk(chunk) {
63
+ if (!this.#receivingPoke) {
64
+ this.#handlePokeError("received a binary poke chunk without pokeStart");
65
+ return;
66
+ }
67
+ if (this.#receivingPoke.parts.length > 0) {
68
+ this.#handlePokeError("received both pokePart and binary poke chunks");
69
+ return;
70
+ }
71
+ this.#receivingPoke.chunks.push(chunk);
72
+ }
55
73
  handlePokeEnd(pokeEnd) {
56
74
  if (pokeEnd.pokeID !== this.#receivingPoke?.pokeStart.pokeID) {
57
75
  this.#handlePokeError(`pokeEnd for ${pokeEnd.pokeID}, when receiving ${this.#receivingPoke?.pokeStart.pokeID}`);
@@ -61,12 +79,23 @@ var PokeHandler = class {
61
79
  this.#receivingPoke = void 0;
62
80
  return;
63
81
  }
82
+ const receivingPoke = this.#receivingPoke;
83
+ let parts = receivingPoke.parts;
84
+ if (receivingPoke.chunks.length > 0) try {
85
+ parts = decodePokeChunks(receivingPoke.chunks);
86
+ } catch (e) {
87
+ this.#handlePokeError(e);
88
+ return;
89
+ }
90
+ const result = summarizePoke(parts, this.#clientID);
64
91
  this.#pokeBuffer.push({
65
- ...this.#receivingPoke,
92
+ pokeStart: receivingPoke.pokeStart,
93
+ parts,
66
94
  pokeEnd
67
95
  });
68
96
  this.#receivingPoke = void 0;
69
97
  if (!this.#pokePlaybackLoopRunning) this.#startPlaybackLoop();
98
+ return result;
70
99
  }
71
100
  handleDisconnect() {
72
101
  this.#lc.debug?.("clearing due to disconnect");
@@ -127,6 +156,27 @@ var PokeHandler = class {
127
156
  this.#pokeBuffer.length = 0;
128
157
  }
129
158
  };
159
+ function decodePokeChunks(chunks) {
160
+ const decoder = new TextDecoder("utf-8", { fatal: true });
161
+ const decoded = new Array(chunks.length + 1);
162
+ for (let i = 0; i < chunks.length; i++) decoded[i] = decoder.decode(chunks[i], { stream: true });
163
+ decoded[chunks.length] = decoder.decode();
164
+ chunks.length = 0;
165
+ return parse(JSON.parse(decoded.join("")), pokePartsSchema, "passthrough");
166
+ }
167
+ function summarizePoke(parts, clientID) {
168
+ let lastMutationIDChangeForSelf;
169
+ let hasRows = false;
170
+ for (const part of parts) {
171
+ const lmid = part.lastMutationIDChanges?.[clientID];
172
+ if (lmid !== void 0) lastMutationIDChangeForSelf = lmid;
173
+ hasRows ||= Boolean(part.rowsPatch?.length);
174
+ }
175
+ return {
176
+ lastMutationIDChangeForSelf,
177
+ hasRows
178
+ };
179
+ }
130
180
  function mergePokes(pokeBuffer, schema, serverToClient) {
131
181
  if (pokeBuffer.length === 0) return;
132
182
  const { baseCookie } = pokeBuffer[0].pokeStart;
@@ -1 +1 @@
1
- {"version":3,"file":"zero-poke-handler.js","names":["#replicachePoke","#onPokeError","#clientID","#lc","#pokeBuffer","#pokeLock","#schema","#serverToClient","#mutationTracker","#receivingPoke","#handlePokeError","#pokePlaybackLoopRunning","#startPlaybackLoop","#clear","#scheduledCallback","#lastScheduledTimestamp","#processPokesForFrame"],"sources":["../../../../../zero-client/src/client/zero-poke-handler.ts"],"sourcesContent":["import {Lock} from '@rocicorp/lock';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {\n PatchOperationInternal,\n PokeInternal,\n} from '../../../replicache/src/impl.ts';\nimport type {PatchOperation} from '../../../replicache/src/patch-operation.ts';\nimport type {ClientID} from '../../../replicache/src/sync/ids.ts';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport type {MutationPatch} from '../../../zero-protocol/src/mutations-patch.ts';\nimport type {\n PokeEndBody,\n PokePartBody,\n PokeStartBody,\n} from '../../../zero-protocol/src/poke.ts';\nimport type {QueriesPatchOp} from '../../../zero-protocol/src/queries-patch.ts';\nimport type {RowPatchOp} from '../../../zero-protocol/src/row-patch.ts';\nimport {\n serverToClient,\n type NameMapper,\n} from '../../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {\n toDesiredQueriesKey,\n toGotQueriesKey,\n toMutationResponseKey,\n toPrimaryKeyString,\n} from './keys.ts';\nimport type {MutationTracker} from './mutation-tracker.ts';\n\ntype PokeAccumulator = {\n readonly pokeStart: PokeStartBody;\n readonly parts: PokePartBody[];\n readonly pokeEnd: PokeEndBody;\n};\n\n/**\n * Handles the multi-part format of zero pokes.\n * As an optimization it also debounces pokes, only poking Replicache with a\n * merged poke at most once per scheduled callback (using setTimeout).\n * This debouncing avoids wastefully computing separate diffs and IVM updates\n * for intermediate states. setTimeout is used instead of requestAnimationFrame\n * to ensure pokes are delivered even when the tab is in the background,\n * enabling notifications (sounds, favicon badges) to work correctly.\n */\nexport class PokeHandler {\n readonly #replicachePoke: (poke: PokeInternal) => Promise<void>;\n readonly #onPokeError: (error: unknown) => void;\n readonly #clientID: ClientID;\n readonly #lc: LogContext;\n #receivingPoke: Omit<PokeAccumulator, 'pokeEnd'> | undefined = undefined;\n readonly #pokeBuffer: PokeAccumulator[] = [];\n #pokePlaybackLoopRunning = false;\n #lastScheduledTimestamp = 0;\n // Serializes calls to this.#replicachePoke otherwise we can cause out of\n // order poke errors.\n readonly #pokeLock = new Lock();\n readonly #schema: Schema;\n readonly #serverToClient: NameMapper;\n readonly #mutationTracker: MutationTracker;\n\n constructor(\n replicachePoke: (poke: PokeInternal) => Promise<void>,\n onPokeError: (error: unknown) => void,\n clientID: ClientID,\n schema: Schema,\n lc: LogContext,\n mutationTracker: MutationTracker,\n ) {\n this.#replicachePoke = replicachePoke;\n this.#onPokeError = onPokeError;\n this.#clientID = clientID;\n this.#schema = schema;\n this.#serverToClient = serverToClient(schema.tables);\n this.#lc = lc.withContext('PokeHandler');\n this.#mutationTracker = mutationTracker;\n }\n\n handlePokeStart(pokeStart: PokeStartBody) {\n if (this.#receivingPoke) {\n this.#handlePokeError(\n `pokeStart ${JSON.stringify(\n pokeStart,\n )} while still receiving ${JSON.stringify(\n this.#receivingPoke.pokeStart,\n )} `,\n );\n return;\n }\n this.#receivingPoke = {\n pokeStart,\n parts: [],\n };\n }\n\n handlePokePart(pokePart: PokePartBody): number | undefined {\n if (pokePart.pokeID !== this.#receivingPoke?.pokeStart.pokeID) {\n this.#handlePokeError(\n `pokePart for ${pokePart.pokeID}, when receiving ${\n this.#receivingPoke?.pokeStart.pokeID\n }`,\n );\n return;\n }\n this.#receivingPoke.parts.push(pokePart);\n return pokePart.lastMutationIDChanges?.[this.#clientID];\n }\n\n handlePokeEnd(pokeEnd: PokeEndBody): void {\n if (pokeEnd.pokeID !== this.#receivingPoke?.pokeStart.pokeID) {\n this.#handlePokeError(\n `pokeEnd for ${pokeEnd.pokeID}, when receiving ${\n this.#receivingPoke?.pokeStart.pokeID\n }`,\n );\n return;\n }\n if (pokeEnd.cancel) {\n this.#receivingPoke = undefined;\n return;\n }\n this.#pokeBuffer.push({...this.#receivingPoke, pokeEnd});\n this.#receivingPoke = undefined;\n if (!this.#pokePlaybackLoopRunning) {\n this.#startPlaybackLoop();\n }\n }\n\n handleDisconnect(): void {\n this.#lc.debug?.('clearing due to disconnect');\n this.#clear();\n }\n\n #startPlaybackLoop() {\n this.#lc.debug?.('starting playback loop');\n this.#pokePlaybackLoopRunning = true;\n setTimeout(this.#scheduledCallback, 0);\n }\n\n #scheduledCallback = async () => {\n const lc = this.#lc.withContext(\n 'scheduledAt',\n Math.floor(performance.now()),\n );\n if (this.#pokeBuffer.length === 0) {\n lc.debug?.('stopping playback loop');\n this.#pokePlaybackLoopRunning = false;\n return;\n }\n setTimeout(this.#scheduledCallback, 0);\n const start = performance.now();\n lc.debug?.(\n 'scheduled callback fired, processing pokes. Since last callback',\n start - this.#lastScheduledTimestamp,\n );\n this.#lastScheduledTimestamp = start;\n await this.#processPokesForFrame(lc);\n lc.debug?.('processing pokes took', performance.now() - start);\n };\n\n #processPokesForFrame(lc: LogContext): Promise<void> {\n return this.#pokeLock.withLock(async () => {\n const now = Date.now();\n lc.debug?.('got poke lock at', now);\n lc.debug?.('merging', this.#pokeBuffer.length);\n try {\n const merged = mergePokes(\n this.#pokeBuffer,\n this.#schema,\n this.#serverToClient,\n );\n this.#pokeBuffer.length = 0;\n if (merged === undefined) {\n lc.debug?.('frame is empty');\n return;\n }\n const start = performance.now();\n lc.debug?.('poking replicache');\n await this.#replicachePoke(merged);\n lc.debug?.('poking replicache took', performance.now() - start);\n\n if (!('error' in merged.pullResponse)) {\n const lmid =\n merged.pullResponse.lastMutationIDChanges[this.#clientID];\n if (lmid !== undefined) {\n this.#mutationTracker.lmidAdvanced(lmid);\n }\n }\n } catch (e) {\n this.#handlePokeError(e);\n }\n });\n }\n\n #handlePokeError(e: unknown) {\n if (String(e).includes('unexpected base cookie for poke')) {\n // This can happen if cookie changes due to refresh from idb due\n // to an update arriving to different tabs in the same\n // client group at very different times. Unusual but possible.\n this.#lc.debug?.('clearing due to', e);\n } else {\n this.#lc.error?.('clearing due to unexpected poke error', e);\n }\n this.#clear();\n this.#onPokeError(e);\n }\n\n #clear() {\n this.#receivingPoke = undefined;\n this.#pokeBuffer.length = 0;\n }\n}\n\nexport function mergePokes(\n pokeBuffer: PokeAccumulator[],\n schema: Schema,\n serverToClient: NameMapper,\n):\n | (PokeInternal & {mutationResults?: MutationPatch[] | undefined})\n | undefined {\n if (pokeBuffer.length === 0) {\n return undefined;\n }\n const {baseCookie} = pokeBuffer[0].pokeStart;\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const lastPoke = pokeBuffer.at(-1)!;\n const {cookie} = lastPoke.pokeEnd;\n const mergedPatch: PatchOperationInternal[] = [];\n const mergedLastMutationIDChanges: Record<string, number> = {};\n const mutationResults: MutationPatch[] = [];\n\n let prevPokeEnd = undefined;\n for (const pokeAccumulator of pokeBuffer) {\n if (\n prevPokeEnd &&\n pokeAccumulator.pokeStart.baseCookie &&\n pokeAccumulator.pokeStart.baseCookie > prevPokeEnd.cookie\n ) {\n throw Error(\n `unexpected cookie gap ${JSON.stringify(prevPokeEnd)} ${JSON.stringify(\n pokeAccumulator.pokeStart,\n )}`,\n );\n }\n prevPokeEnd = pokeAccumulator.pokeEnd;\n for (const pokePart of pokeAccumulator.parts) {\n if (pokePart.lastMutationIDChanges) {\n for (const [clientID, lastMutationID] of Object.entries(\n pokePart.lastMutationIDChanges,\n )) {\n mergedLastMutationIDChanges[clientID] = lastMutationID;\n }\n }\n if (pokePart.desiredQueriesPatches) {\n for (const [clientID, queriesPatch] of Object.entries(\n pokePart.desiredQueriesPatches,\n )) {\n for (const op of queriesPatch) {\n mergedPatch.push(\n queryPatchOpToReplicachePatchOp(op, hash =>\n toDesiredQueriesKey(clientID, hash),\n ),\n );\n }\n }\n }\n if (pokePart.gotQueriesPatch) {\n for (const op of pokePart.gotQueriesPatch) {\n mergedPatch.push(\n queryPatchOpToReplicachePatchOp(op, toGotQueriesKey),\n );\n }\n }\n if (pokePart.rowsPatch) {\n for (const p of pokePart.rowsPatch) {\n const patchOp = rowsPatchOpToReplicachePatchOp(\n p,\n schema,\n serverToClient,\n );\n if (patchOp) {\n mergedPatch.push(patchOp);\n }\n }\n }\n if (pokePart.mutationsPatch) {\n for (const op of pokePart.mutationsPatch) {\n mergedPatch.push(mutationPatchOpToReplicachePatchOp(op));\n }\n }\n }\n }\n const ret: PokeInternal & {mutationResults?: MutationPatch[] | undefined} = {\n baseCookie,\n pullResponse: {\n lastMutationIDChanges: mergedLastMutationIDChanges,\n patch: mergedPatch,\n cookie,\n },\n };\n\n // For backwards compatibility. Because we're strict on our validation,\n // zero-client must be able to parse pokes with this field before we introduce it.\n // So users can update their clients and then start using custom mutators that write responses to the db.\n if (mutationResults.length > 0) {\n ret.mutationResults = mutationResults;\n }\n return ret;\n}\n\nfunction queryPatchOpToReplicachePatchOp(\n op: QueriesPatchOp,\n toKey: (hash: string) => string,\n): PatchOperation {\n switch (op.op) {\n case 'clear':\n return op;\n case 'del':\n return {\n op: 'del',\n key: toKey(op.hash),\n };\n case 'put':\n return {\n op: 'put',\n key: toKey(op.hash),\n value: null,\n };\n default:\n unreachable(op);\n }\n}\n\nexport function mutationPatchOpToReplicachePatchOp(\n op: MutationPatch,\n): PatchOperationInternal {\n switch (op.op) {\n case 'put':\n return {\n op: 'put',\n key: toMutationResponseKey(op.mutation.id),\n value: op.mutation.result,\n };\n case 'del':\n return {\n op: 'del',\n key: toMutationResponseKey(op.id),\n };\n }\n}\n\nfunction rowsPatchOpToReplicachePatchOp(\n op: RowPatchOp,\n schema: Schema,\n serverToClient: NameMapper,\n): PatchOperationInternal | undefined {\n if (op.op === 'clear') {\n return op;\n }\n // Skip rows for tables not in the client schema. This can happen when\n // the server-side query AST references tables (e.g. issueNotifications)\n // that are not yet part of the client schema definition.\n const tableName = serverToClient.tableNameIfKnown(op.tableName);\n if (!tableName) {\n return undefined;\n }\n switch (op.op) {\n case 'del':\n return {\n op: 'del',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.id),\n ),\n };\n case 'put':\n return {\n op: 'put',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.value),\n ),\n value: serverToClient.row(op.tableName, op.value),\n };\n case 'update':\n return {\n op: 'update',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.id),\n ),\n merge: op.merge\n ? serverToClient.row(op.tableName, op.merge)\n : undefined,\n constrain: serverToClient.columns(op.tableName, op.constrain),\n };\n default:\n unreachable(op);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"zero-poke-handler.js","names":["#replicachePoke","#onPokeError","#clientID","#lc","#pokeBuffer","#pokeLock","#schema","#serverToClient","#mutationTracker","#receivingPoke","#handlePokeError","#pokePlaybackLoopRunning","#startPlaybackLoop","#clear","#scheduledCallback","#lastScheduledTimestamp","#processPokesForFrame"],"sources":["../../../../../zero-client/src/client/zero-poke-handler.ts"],"sourcesContent":["import {Lock} from '@rocicorp/lock';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {\n PatchOperationInternal,\n PokeInternal,\n} from '../../../replicache/src/impl.ts';\nimport type {PatchOperation} from '../../../replicache/src/patch-operation.ts';\nimport type {ClientID} from '../../../replicache/src/sync/ids.ts';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {MutationPatch} from '../../../zero-protocol/src/mutations-patch.ts';\nimport {\n pokePartsSchema,\n type PokeEndBody,\n type PokePartBody,\n type PokeParts,\n type PokeStartBody,\n} from '../../../zero-protocol/src/poke.ts';\nimport type {QueriesPatchOp} from '../../../zero-protocol/src/queries-patch.ts';\nimport type {RowPatchOp} from '../../../zero-protocol/src/row-patch.ts';\nimport {\n serverToClient,\n type NameMapper,\n} from '../../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {\n toDesiredQueriesKey,\n toGotQueriesKey,\n toMutationResponseKey,\n toPrimaryKeyString,\n} from './keys.ts';\nimport type {MutationTracker} from './mutation-tracker.ts';\n\ntype PokeAccumulator = {\n readonly pokeStart: PokeStartBody;\n readonly parts: PokePartBody[];\n readonly pokeEnd: PokeEndBody;\n};\n\ntype ReceivingPoke = {\n readonly pokeStart: PokeStartBody;\n readonly parts: PokePartBody[];\n readonly chunks: Uint8Array[];\n};\n\nexport type PokeEndResult = {\n readonly lastMutationIDChangeForSelf: number | undefined;\n readonly hasRows: boolean;\n};\n\n/**\n * Handles the multi-part format of zero pokes.\n * As an optimization it also debounces pokes, only poking Replicache with a\n * merged poke at most once per scheduled callback (using setTimeout).\n * This debouncing avoids wastefully computing separate diffs and IVM updates\n * for intermediate states. setTimeout is used instead of requestAnimationFrame\n * to ensure pokes are delivered even when the tab is in the background,\n * enabling notifications (sounds, favicon badges) to work correctly.\n */\nexport class PokeHandler {\n readonly #replicachePoke: (poke: PokeInternal) => Promise<void>;\n readonly #onPokeError: (error: unknown) => void;\n readonly #clientID: ClientID;\n readonly #lc: LogContext;\n #receivingPoke: ReceivingPoke | undefined = undefined;\n readonly #pokeBuffer: PokeAccumulator[] = [];\n #pokePlaybackLoopRunning = false;\n #lastScheduledTimestamp = 0;\n // Serializes calls to this.#replicachePoke otherwise we can cause out of\n // order poke errors.\n readonly #pokeLock = new Lock();\n readonly #schema: Schema;\n readonly #serverToClient: NameMapper;\n readonly #mutationTracker: MutationTracker;\n\n constructor(\n replicachePoke: (poke: PokeInternal) => Promise<void>,\n onPokeError: (error: unknown) => void,\n clientID: ClientID,\n schema: Schema,\n lc: LogContext,\n mutationTracker: MutationTracker,\n ) {\n this.#replicachePoke = replicachePoke;\n this.#onPokeError = onPokeError;\n this.#clientID = clientID;\n this.#schema = schema;\n this.#serverToClient = serverToClient(schema.tables);\n this.#lc = lc.withContext('PokeHandler');\n this.#mutationTracker = mutationTracker;\n }\n\n handlePokeStart(pokeStart: PokeStartBody) {\n if (this.#receivingPoke) {\n this.#handlePokeError(\n `pokeStart ${JSON.stringify(\n pokeStart,\n )} while still receiving ${JSON.stringify(\n this.#receivingPoke.pokeStart,\n )} `,\n );\n return;\n }\n this.#receivingPoke = {\n pokeStart,\n parts: [],\n chunks: [],\n };\n }\n\n handlePokePart(pokePart: PokePartBody): number | undefined {\n if (pokePart.pokeID !== this.#receivingPoke?.pokeStart.pokeID) {\n this.#handlePokeError(\n `pokePart for ${pokePart.pokeID}, when receiving ${\n this.#receivingPoke?.pokeStart.pokeID\n }`,\n );\n return;\n }\n if (this.#receivingPoke.chunks.length > 0) {\n this.#handlePokeError('received both pokePart and binary poke chunks');\n return;\n }\n this.#receivingPoke.parts.push(pokePart);\n return pokePart.lastMutationIDChanges?.[this.#clientID];\n }\n\n handlePokeChunk(chunk: Uint8Array): void {\n if (!this.#receivingPoke) {\n this.#handlePokeError('received a binary poke chunk without pokeStart');\n return;\n }\n if (this.#receivingPoke.parts.length > 0) {\n this.#handlePokeError('received both pokePart and binary poke chunks');\n return;\n }\n this.#receivingPoke.chunks.push(chunk);\n }\n\n handlePokeEnd(pokeEnd: PokeEndBody): PokeEndResult | undefined {\n if (pokeEnd.pokeID !== this.#receivingPoke?.pokeStart.pokeID) {\n this.#handlePokeError(\n `pokeEnd for ${pokeEnd.pokeID}, when receiving ${\n this.#receivingPoke?.pokeStart.pokeID\n }`,\n );\n return;\n }\n if (pokeEnd.cancel) {\n this.#receivingPoke = undefined;\n return;\n }\n const receivingPoke = this.#receivingPoke;\n let parts = receivingPoke.parts;\n if (receivingPoke.chunks.length > 0) {\n try {\n parts = decodePokeChunks(receivingPoke.chunks);\n } catch (e) {\n this.#handlePokeError(e);\n return;\n }\n }\n\n const result = summarizePoke(parts, this.#clientID);\n this.#pokeBuffer.push({\n pokeStart: receivingPoke.pokeStart,\n parts,\n pokeEnd,\n });\n this.#receivingPoke = undefined;\n if (!this.#pokePlaybackLoopRunning) {\n this.#startPlaybackLoop();\n }\n return result;\n }\n\n handleDisconnect(): void {\n this.#lc.debug?.('clearing due to disconnect');\n this.#clear();\n }\n\n #startPlaybackLoop() {\n this.#lc.debug?.('starting playback loop');\n this.#pokePlaybackLoopRunning = true;\n setTimeout(this.#scheduledCallback, 0);\n }\n\n #scheduledCallback = async () => {\n const lc = this.#lc.withContext(\n 'scheduledAt',\n Math.floor(performance.now()),\n );\n if (this.#pokeBuffer.length === 0) {\n lc.debug?.('stopping playback loop');\n this.#pokePlaybackLoopRunning = false;\n return;\n }\n setTimeout(this.#scheduledCallback, 0);\n const start = performance.now();\n lc.debug?.(\n 'scheduled callback fired, processing pokes. Since last callback',\n start - this.#lastScheduledTimestamp,\n );\n this.#lastScheduledTimestamp = start;\n await this.#processPokesForFrame(lc);\n lc.debug?.('processing pokes took', performance.now() - start);\n };\n\n #processPokesForFrame(lc: LogContext): Promise<void> {\n return this.#pokeLock.withLock(async () => {\n const now = Date.now();\n lc.debug?.('got poke lock at', now);\n lc.debug?.('merging', this.#pokeBuffer.length);\n try {\n const merged = mergePokes(\n this.#pokeBuffer,\n this.#schema,\n this.#serverToClient,\n );\n this.#pokeBuffer.length = 0;\n if (merged === undefined) {\n lc.debug?.('frame is empty');\n return;\n }\n const start = performance.now();\n lc.debug?.('poking replicache');\n await this.#replicachePoke(merged);\n lc.debug?.('poking replicache took', performance.now() - start);\n\n if (!('error' in merged.pullResponse)) {\n const lmid =\n merged.pullResponse.lastMutationIDChanges[this.#clientID];\n if (lmid !== undefined) {\n this.#mutationTracker.lmidAdvanced(lmid);\n }\n }\n } catch (e) {\n this.#handlePokeError(e);\n }\n });\n }\n\n #handlePokeError(e: unknown) {\n if (String(e).includes('unexpected base cookie for poke')) {\n // This can happen if cookie changes due to refresh from idb due\n // to an update arriving to different tabs in the same\n // client group at very different times. Unusual but possible.\n this.#lc.debug?.('clearing due to', e);\n } else {\n this.#lc.error?.('clearing due to unexpected poke error', e);\n }\n this.#clear();\n this.#onPokeError(e);\n }\n\n #clear() {\n this.#receivingPoke = undefined;\n this.#pokeBuffer.length = 0;\n }\n}\n\nfunction decodePokeChunks(chunks: Uint8Array[]): PokeParts {\n const decoder = new TextDecoder('utf-8', {fatal: true});\n const decoded = new Array<string>(chunks.length + 1);\n for (let i = 0; i < chunks.length; i++) {\n decoded[i] = decoder.decode(chunks[i], {stream: true});\n }\n decoded[chunks.length] = decoder.decode();\n chunks.length = 0;\n return v.parse(JSON.parse(decoded.join('')), pokePartsSchema, 'passthrough');\n}\n\nfunction summarizePoke(\n parts: PokePartBody[],\n clientID: ClientID,\n): PokeEndResult {\n let lastMutationIDChangeForSelf: number | undefined;\n let hasRows = false;\n for (const part of parts) {\n const lmid = part.lastMutationIDChanges?.[clientID];\n if (lmid !== undefined) {\n lastMutationIDChangeForSelf = lmid;\n }\n hasRows ||= Boolean(part.rowsPatch?.length);\n }\n return {lastMutationIDChangeForSelf, hasRows};\n}\n\nexport function mergePokes(\n pokeBuffer: PokeAccumulator[],\n schema: Schema,\n serverToClient: NameMapper,\n):\n | (PokeInternal & {mutationResults?: MutationPatch[] | undefined})\n | undefined {\n if (pokeBuffer.length === 0) {\n return undefined;\n }\n const {baseCookie} = pokeBuffer[0].pokeStart;\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const lastPoke = pokeBuffer.at(-1)!;\n const {cookie} = lastPoke.pokeEnd;\n const mergedPatch: PatchOperationInternal[] = [];\n const mergedLastMutationIDChanges: Record<string, number> = {};\n const mutationResults: MutationPatch[] = [];\n\n let prevPokeEnd = undefined;\n for (const pokeAccumulator of pokeBuffer) {\n if (\n prevPokeEnd &&\n pokeAccumulator.pokeStart.baseCookie &&\n pokeAccumulator.pokeStart.baseCookie > prevPokeEnd.cookie\n ) {\n throw Error(\n `unexpected cookie gap ${JSON.stringify(prevPokeEnd)} ${JSON.stringify(\n pokeAccumulator.pokeStart,\n )}`,\n );\n }\n prevPokeEnd = pokeAccumulator.pokeEnd;\n for (const pokePart of pokeAccumulator.parts) {\n if (pokePart.lastMutationIDChanges) {\n for (const [clientID, lastMutationID] of Object.entries(\n pokePart.lastMutationIDChanges,\n )) {\n mergedLastMutationIDChanges[clientID] = lastMutationID;\n }\n }\n if (pokePart.desiredQueriesPatches) {\n for (const [clientID, queriesPatch] of Object.entries(\n pokePart.desiredQueriesPatches,\n )) {\n for (const op of queriesPatch) {\n mergedPatch.push(\n queryPatchOpToReplicachePatchOp(op, hash =>\n toDesiredQueriesKey(clientID, hash),\n ),\n );\n }\n }\n }\n if (pokePart.gotQueriesPatch) {\n for (const op of pokePart.gotQueriesPatch) {\n mergedPatch.push(\n queryPatchOpToReplicachePatchOp(op, toGotQueriesKey),\n );\n }\n }\n if (pokePart.rowsPatch) {\n for (const p of pokePart.rowsPatch) {\n const patchOp = rowsPatchOpToReplicachePatchOp(\n p,\n schema,\n serverToClient,\n );\n if (patchOp) {\n mergedPatch.push(patchOp);\n }\n }\n }\n if (pokePart.mutationsPatch) {\n for (const op of pokePart.mutationsPatch) {\n mergedPatch.push(mutationPatchOpToReplicachePatchOp(op));\n }\n }\n }\n }\n const ret: PokeInternal & {mutationResults?: MutationPatch[] | undefined} = {\n baseCookie,\n pullResponse: {\n lastMutationIDChanges: mergedLastMutationIDChanges,\n patch: mergedPatch,\n cookie,\n },\n };\n\n // For backwards compatibility. Because we're strict on our validation,\n // zero-client must be able to parse pokes with this field before we introduce it.\n // So users can update their clients and then start using custom mutators that write responses to the db.\n if (mutationResults.length > 0) {\n ret.mutationResults = mutationResults;\n }\n return ret;\n}\n\nfunction queryPatchOpToReplicachePatchOp(\n op: QueriesPatchOp,\n toKey: (hash: string) => string,\n): PatchOperation {\n switch (op.op) {\n case 'clear':\n return op;\n case 'del':\n return {\n op: 'del',\n key: toKey(op.hash),\n };\n case 'put':\n return {\n op: 'put',\n key: toKey(op.hash),\n value: null,\n };\n default:\n unreachable(op);\n }\n}\n\nexport function mutationPatchOpToReplicachePatchOp(\n op: MutationPatch,\n): PatchOperationInternal {\n switch (op.op) {\n case 'put':\n return {\n op: 'put',\n key: toMutationResponseKey(op.mutation.id),\n value: op.mutation.result,\n };\n case 'del':\n return {\n op: 'del',\n key: toMutationResponseKey(op.id),\n };\n }\n}\n\nfunction rowsPatchOpToReplicachePatchOp(\n op: RowPatchOp,\n schema: Schema,\n serverToClient: NameMapper,\n): PatchOperationInternal | undefined {\n if (op.op === 'clear') {\n return op;\n }\n // Skip rows for tables not in the client schema. This can happen when\n // the server-side query AST references tables (e.g. issueNotifications)\n // that are not yet part of the client schema definition.\n const tableName = serverToClient.tableNameIfKnown(op.tableName);\n if (!tableName) {\n return undefined;\n }\n switch (op.op) {\n case 'del':\n return {\n op: 'del',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.id),\n ),\n };\n case 'put':\n return {\n op: 'put',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.value),\n ),\n value: serverToClient.row(op.tableName, op.value),\n };\n case 'update':\n return {\n op: 'update',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.id),\n ),\n merge: op.merge\n ? serverToClient.row(op.tableName, op.merge)\n : undefined,\n constrain: serverToClient.columns(op.tableName, op.constrain),\n };\n default:\n unreachable(op);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA2DA,IAAa,cAAb,MAAyB;CACvB;CACA;CACA;CACA;CACA,iBAA4C,KAAA;CAC5C,cAA0C,CAAC;CAC3C,2BAA2B;CAC3B,0BAA0B;CAG1B,YAAqB,IAAI,KAAK;CAC9B;CACA;CACA;CAEA,YACE,gBACA,aACA,UACA,QACA,IACA,iBACA;EACA,KAAKA,kBAAkB;EACvB,KAAKC,eAAe;EACpB,KAAKC,YAAY;EACjB,KAAKI,UAAU;EACf,KAAKC,kBAAkB,eAAe,OAAO,MAAM;EACnD,KAAKJ,MAAM,GAAG,YAAY,aAAa;EACvC,KAAKK,mBAAmB;CAC1B;CAEA,gBAAgB,WAA0B;EACxC,IAAI,KAAKC,gBAAgB;GACvB,KAAKC,iBACH,aAAa,KAAK,UAChB,SACF,EAAE,0BAA0B,KAAK,UAC/B,KAAKD,eAAe,SACtB,EAAE,EACJ;GACA;EACF;EACA,KAAKA,iBAAiB;GACpB;GACA,OAAO,CAAC;GACR,QAAQ,CAAC;EACX;CACF;CAEA,eAAe,UAA4C;EACzD,IAAI,SAAS,WAAW,KAAKA,gBAAgB,UAAU,QAAQ;GAC7D,KAAKC,iBACH,gBAAgB,SAAS,OAAO,mBAC9B,KAAKD,gBAAgB,UAAU,QAEnC;GACA;EACF;EACA,IAAI,KAAKA,eAAe,OAAO,SAAS,GAAG;GACzC,KAAKC,iBAAiB,+CAA+C;GACrE;EACF;EACA,KAAKD,eAAe,MAAM,KAAK,QAAQ;EACvC,OAAO,SAAS,wBAAwB,KAAKP;CAC/C;CAEA,gBAAgB,OAAyB;EACvC,IAAI,CAAC,KAAKO,gBAAgB;GACxB,KAAKC,iBAAiB,gDAAgD;GACtE;EACF;EACA,IAAI,KAAKD,eAAe,MAAM,SAAS,GAAG;GACxC,KAAKC,iBAAiB,+CAA+C;GACrE;EACF;EACA,KAAKD,eAAe,OAAO,KAAK,KAAK;CACvC;CAEA,cAAc,SAAiD;EAC7D,IAAI,QAAQ,WAAW,KAAKA,gBAAgB,UAAU,QAAQ;GAC5D,KAAKC,iBACH,eAAe,QAAQ,OAAO,mBAC5B,KAAKD,gBAAgB,UAAU,QAEnC;GACA;EACF;EACA,IAAI,QAAQ,QAAQ;GAClB,KAAKA,iBAAiB,KAAA;GACtB;EACF;EACA,MAAM,gBAAgB,KAAKA;EAC3B,IAAI,QAAQ,cAAc;EAC1B,IAAI,cAAc,OAAO,SAAS,GAChC,IAAI;GACF,QAAQ,iBAAiB,cAAc,MAAM;EAC/C,SAAS,GAAG;GACV,KAAKC,iBAAiB,CAAC;GACvB;EACF;EAGF,MAAM,SAAS,cAAc,OAAO,KAAKR,SAAS;EAClD,KAAKE,YAAY,KAAK;GACpB,WAAW,cAAc;GACzB;GACA;EACF,CAAC;EACD,KAAKK,iBAAiB,KAAA;EACtB,IAAI,CAAC,KAAKE,0BACR,KAAKC,mBAAmB;EAE1B,OAAO;CACT;CAEA,mBAAyB;EACvB,KAAKT,IAAI,QAAQ,4BAA4B;EAC7C,KAAKU,OAAO;CACd;CAEA,qBAAqB;EACnB,KAAKV,IAAI,QAAQ,wBAAwB;EACzC,KAAKQ,2BAA2B;EAChC,WAAW,KAAKG,oBAAoB,CAAC;CACvC;CAEA,qBAAqB,YAAY;EAC/B,MAAM,KAAK,KAAKX,IAAI,YAClB,eACA,KAAK,MAAM,YAAY,IAAI,CAAC,CAC9B;EACA,IAAI,KAAKC,YAAY,WAAW,GAAG;GACjC,GAAG,QAAQ,wBAAwB;GACnC,KAAKO,2BAA2B;GAChC;EACF;EACA,WAAW,KAAKG,oBAAoB,CAAC;EACrC,MAAM,QAAQ,YAAY,IAAI;EAC9B,GAAG,QACD,mEACA,QAAQ,KAAKC,uBACf;EACA,KAAKA,0BAA0B;EAC/B,MAAM,KAAKC,sBAAsB,EAAE;EACnC,GAAG,QAAQ,yBAAyB,YAAY,IAAI,IAAI,KAAK;CAC/D;CAEA,sBAAsB,IAA+B;EACnD,OAAO,KAAKX,UAAU,SAAS,YAAY;GACzC,MAAM,MAAM,KAAK,IAAI;GACrB,GAAG,QAAQ,oBAAoB,GAAG;GAClC,GAAG,QAAQ,WAAW,KAAKD,YAAY,MAAM;GAC7C,IAAI;IACF,MAAM,SAAS,WACb,KAAKA,aACL,KAAKE,SACL,KAAKC,eACP;IACA,KAAKH,YAAY,SAAS;IAC1B,IAAI,WAAW,KAAA,GAAW;KACxB,GAAG,QAAQ,gBAAgB;KAC3B;IACF;IACA,MAAM,QAAQ,YAAY,IAAI;IAC9B,GAAG,QAAQ,mBAAmB;IAC9B,MAAM,KAAKJ,gBAAgB,MAAM;IACjC,GAAG,QAAQ,0BAA0B,YAAY,IAAI,IAAI,KAAK;IAE9D,IAAI,EAAE,WAAW,OAAO,eAAe;KACrC,MAAM,OACJ,OAAO,aAAa,sBAAsB,KAAKE;KACjD,IAAI,SAAS,KAAA,GACX,KAAKM,iBAAiB,aAAa,IAAI;IAE3C;GACF,SAAS,GAAG;IACV,KAAKE,iBAAiB,CAAC;GACzB;EACF,CAAC;CACH;CAEA,iBAAiB,GAAY;EAC3B,IAAI,OAAO,CAAC,EAAE,SAAS,iCAAiC,GAItD,KAAKP,IAAI,QAAQ,mBAAmB,CAAC;OAErC,KAAKA,IAAI,QAAQ,yCAAyC,CAAC;EAE7D,KAAKU,OAAO;EACZ,KAAKZ,aAAa,CAAC;CACrB;CAEA,SAAS;EACP,KAAKQ,iBAAiB,KAAA;EACtB,KAAKL,YAAY,SAAS;CAC5B;AACF;AAEA,SAAS,iBAAiB,QAAiC;CACzD,MAAM,UAAU,IAAI,YAAY,SAAS,EAAC,OAAO,KAAI,CAAC;CACtD,MAAM,UAAU,IAAI,MAAc,OAAO,SAAS,CAAC;CACnD,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,QAAQ,KAAK,QAAQ,OAAO,OAAO,IAAI,EAAC,QAAQ,KAAI,CAAC;CAEvD,QAAQ,OAAO,UAAU,QAAQ,OAAO;CACxC,OAAO,SAAS;CAChB,OAAO,MAAQ,KAAK,MAAM,QAAQ,KAAK,EAAE,CAAC,GAAG,iBAAiB,aAAa;AAC7E;AAEA,SAAS,cACP,OACA,UACe;CACf,IAAI;CACJ,IAAI,UAAU;CACd,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,KAAK,wBAAwB;EAC1C,IAAI,SAAS,KAAA,GACX,8BAA8B;EAEhC,YAAY,QAAQ,KAAK,WAAW,MAAM;CAC5C;CACA,OAAO;EAAC;EAA6B;CAAO;AAC9C;AAEA,SAAgB,WACd,YACA,QACA,gBAGY;CACZ,IAAI,WAAW,WAAW,GACxB;CAEF,MAAM,EAAC,eAAc,WAAW,GAAG;CAGnC,MAAM,EAAC,WADU,WAAW,GAAG,EACd,EAAS;CAC1B,MAAM,cAAwC,CAAC;CAC/C,MAAM,8BAAsD,CAAC;CAC7D,MAAM,kBAAmC,CAAC;CAE1C,IAAI,cAAc,KAAA;CAClB,KAAK,MAAM,mBAAmB,YAAY;EACxC,IACE,eACA,gBAAgB,UAAU,cAC1B,gBAAgB,UAAU,aAAa,YAAY,QAEnD,MAAM,MACJ,yBAAyB,KAAK,UAAU,WAAW,EAAE,GAAG,KAAK,UAC3D,gBAAgB,SAClB,GACF;EAEF,cAAc,gBAAgB;EAC9B,KAAK,MAAM,YAAY,gBAAgB,OAAO;GAC5C,IAAI,SAAS,uBACX,KAAK,MAAM,CAAC,UAAU,mBAAmB,OAAO,QAC9C,SAAS,qBACX,GACE,4BAA4B,YAAY;GAG5C,IAAI,SAAS,uBACX,KAAK,MAAM,CAAC,UAAU,iBAAiB,OAAO,QAC5C,SAAS,qBACX,GACE,KAAK,MAAM,MAAM,cACf,YAAY,KACV,gCAAgC,KAAI,SAClC,oBAAoB,UAAU,IAAI,CACpC,CACF;GAIN,IAAI,SAAS,iBACX,KAAK,MAAM,MAAM,SAAS,iBACxB,YAAY,KACV,gCAAgC,IAAI,eAAe,CACrD;GAGJ,IAAI,SAAS,WACX,KAAK,MAAM,KAAK,SAAS,WAAW;IAClC,MAAM,UAAU,+BACd,GACA,QACA,cACF;IACA,IAAI,SACF,YAAY,KAAK,OAAO;GAE5B;GAEF,IAAI,SAAS,gBACX,KAAK,MAAM,MAAM,SAAS,gBACxB,YAAY,KAAK,mCAAmC,EAAE,CAAC;EAG7D;CACF;CACA,MAAM,MAAsE;EAC1E;EACA,cAAc;GACZ,uBAAuB;GACvB,OAAO;GACP;EACF;CACF;CAKA,IAAI,gBAAgB,SAAS,GAC3B,IAAI,kBAAkB;CAExB,OAAO;AACT;AAEA,SAAS,gCACP,IACA,OACgB;CAChB,QAAQ,GAAG,IAAX;EACE,KAAK,SACH,OAAO;EACT,KAAK,OACH,OAAO;GACL,IAAI;GACJ,KAAK,MAAM,GAAG,IAAI;EACpB;EACF,KAAK,OACH,OAAO;GACL,IAAI;GACJ,KAAK,MAAM,GAAG,IAAI;GAClB,OAAO;EACT;EACF,SACE,YAAY,EAAE;CAClB;AACF;AAEA,SAAgB,mCACd,IACwB;CACxB,QAAQ,GAAG,IAAX;EACE,KAAK,OACH,OAAO;GACL,IAAI;GACJ,KAAK,sBAAsB,GAAG,SAAS,EAAE;GACzC,OAAO,GAAG,SAAS;EACrB;EACF,KAAK,OACH,OAAO;GACL,IAAI;GACJ,KAAK,sBAAsB,GAAG,EAAE;EAClC;CACJ;AACF;AAEA,SAAS,+BACP,IACA,QACA,gBACoC;CACpC,IAAI,GAAG,OAAO,SACZ,OAAO;CAKT,MAAM,YAAY,eAAe,iBAAiB,GAAG,SAAS;CAC9D,IAAI,CAAC,WACH;CAEF,QAAQ,GAAG,IAAX;EACE,KAAK,OACH,OAAO;GACL,IAAI;GACJ,KAAK,mBACH,WACA,OAAO,OAAO,WAAW,YACzB,eAAe,IAAI,GAAG,WAAW,GAAG,EAAE,CACxC;EACF;EACF,KAAK,OACH,OAAO;GACL,IAAI;GACJ,KAAK,mBACH,WACA,OAAO,OAAO,WAAW,YACzB,eAAe,IAAI,GAAG,WAAW,GAAG,KAAK,CAC3C;GACA,OAAO,eAAe,IAAI,GAAG,WAAW,GAAG,KAAK;EAClD;EACF,KAAK,UACH,OAAO;GACL,IAAI;GACJ,KAAK,mBACH,WACA,OAAO,OAAO,WAAW,YACzB,eAAe,IAAI,GAAG,WAAW,GAAG,EAAE,CACxC;GACA,OAAO,GAAG,QACN,eAAe,IAAI,GAAG,WAAW,GAAG,KAAK,IACzC,KAAA;GACJ,WAAW,eAAe,QAAQ,GAAG,WAAW,GAAG,SAAS;EAC9D;EACF,SACE,YAAY,EAAE;CAClB;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"zero.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/zero.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"zero.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/zero.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,7 @@ import { encodeSecProtocols } from "../../../zero-protocol/src/connect.js";
31
31
  import { CRUD, Custom } from "../../../zero-protocol/src/mutation-type-enum.js";
32
32
  import { mapCRUD } from "../../../zero-protocol/src/mutation.js";
33
33
  import { nullableVersionSchema } from "../../../zero-protocol/src/version.js";
34
+ import "../../../zero-protocol/src/poke.js";
34
35
  import { downstreamSchema } from "../../../zero-protocol/src/down.js";
35
36
  import "../../../zero-protocol/src/protocol-version.js";
36
37
  import { isClientMetric } from "../../../zql/src/query/metrics-delegate.js";
@@ -280,7 +281,7 @@ var Zero = class Zero {
280
281
  })).toString(36);
281
282
  const storageScopedUserID = userID ?? "__anonymous__";
282
283
  const replicacheOptions = {
283
- schemaVersion: `51.${hash}`,
284
+ schemaVersion: `52.${hash}`,
284
285
  logLevel: logOptions.logLevel,
285
286
  logSinks: [logOptions.logSink],
286
287
  mutators: replicacheMutators,
@@ -641,13 +642,36 @@ var Zero = class Zero {
641
642
  }
642
643
  #onMessage = (e) => {
643
644
  const lc = this.#lc;
644
- lc.debug?.("received message", e.data);
645
+ const { data } = e;
646
+ lc.debug?.("received message", data instanceof ArrayBuffer ? {
647
+ type: "binary",
648
+ byteLength: data.byteLength
649
+ } : data);
645
650
  if (this.closed) {
646
651
  lc.debug?.("ignoring message because already closed");
647
652
  return;
648
653
  }
649
654
  let downMessage;
650
- const { data } = e;
655
+ if (data instanceof ArrayBuffer) {
656
+ this.#messageCount++;
657
+ const message = new Uint8Array(data);
658
+ if (message[0] !== 0) {
659
+ this.#disconnect(lc, new ClientError({
660
+ kind: InvalidMessage$1,
661
+ message: `Unknown binary message type: ${String(message[0])}`
662
+ }));
663
+ return;
664
+ }
665
+ this.#handlePokeChunk(message.subarray(1));
666
+ return;
667
+ }
668
+ if (typeof data !== "string") {
669
+ this.#disconnect(lc, new ClientError({
670
+ kind: InvalidMessage$1,
671
+ message: "Invalid binary message received from server"
672
+ }));
673
+ return;
674
+ }
651
675
  try {
652
676
  downMessage = parse(JSON.parse(data), downstreamSchema, "passthrough");
653
677
  } catch (e) {
@@ -993,9 +1017,15 @@ var Zero = class Zero {
993
1017
  const lastMutationIDChangeForSelf = this.#pokeHandler.handlePokePart(pokeMessage[1]);
994
1018
  if (lastMutationIDChangeForSelf !== void 0) this.#lastMutationIDReceived = lastMutationIDChangeForSelf;
995
1019
  }
1020
+ #handlePokeChunk(chunk) {
1021
+ this.#abortPingTimeout();
1022
+ this.#pokeHandler.handlePokeChunk(chunk);
1023
+ }
996
1024
  #handlePokeEnd(_lc, pokeMessage) {
997
1025
  this.#abortPingTimeout();
998
- this.#pokeHandler.handlePokeEnd(pokeMessage[1]);
1026
+ const result = this.#pokeHandler.handlePokeEnd(pokeMessage[1]);
1027
+ if (result?.hasRows) resetBackoff();
1028
+ if (result?.lastMutationIDChangeForSelf !== void 0) this.#lastMutationIDReceived = result.lastMutationIDChangeForSelf;
999
1029
  }
1000
1030
  #onPokeError(error) {
1001
1031
  const lc = this.#lc;
@@ -1465,14 +1495,16 @@ async function createSocket(rep, queryManager, deleteClientsManager, socketOrigi
1465
1495
  queriesPatch = void 0;
1466
1496
  } else deletedClients = void 0;
1467
1497
  onEvent?.("creating the WebSocket");
1498
+ const socket = new WS(url.toString(), secProtocol);
1499
+ socket.binaryType = "arraybuffer";
1468
1500
  return [
1469
- new WS(url.toString(), secProtocol),
1501
+ socket,
1470
1502
  queriesPatch,
1471
1503
  skipEmptyDeletedClients(deletedClients)
1472
1504
  ];
1473
1505
  }
1474
1506
  async function createConnectionURL(socketOrigin, clientID, clientGroupID, userID, baseCookie, lmid, wsid, rep, debugPerf, additionalConnectParams, lc) {
1475
- const url = new URL(appendPath(socketOrigin, `/sync/v51/connect`));
1507
+ const url = new URL(appendPath(socketOrigin, `/sync/v52/connect`));
1476
1508
  const { searchParams } = url;
1477
1509
  searchParams.set("clientID", clientID);
1478
1510
  searchParams.set("clientGroupID", clientGroupID);