@rocicorp/zero 1.12.0-canary.8 → 1.12.0-canary.9

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 (516) hide show
  1. package/out/replicache/src/btree/node.d.ts +0 -2
  2. package/out/replicache/src/btree/node.d.ts.map +1 -1
  3. package/out/replicache/src/btree/node.js.map +1 -1
  4. package/out/replicache/src/connection-loop.d.ts.map +1 -1
  5. package/out/replicache/src/connection-loop.js +1 -1
  6. package/out/replicache/src/connection-loop.js.map +1 -1
  7. package/out/replicache/src/db/commit.d.ts +0 -1
  8. package/out/replicache/src/db/commit.d.ts.map +1 -1
  9. package/out/replicache/src/db/commit.js +1 -1
  10. package/out/replicache/src/db/commit.js.map +1 -1
  11. package/out/replicache/src/db/write.d.ts +0 -2
  12. package/out/replicache/src/db/write.d.ts.map +1 -1
  13. package/out/replicache/src/db/write.js +1 -1
  14. package/out/replicache/src/db/write.js.map +1 -1
  15. package/out/replicache/src/deleted-clients.d.ts +0 -2
  16. package/out/replicache/src/deleted-clients.d.ts.map +1 -1
  17. package/out/replicache/src/deleted-clients.js +1 -1
  18. package/out/replicache/src/deleted-clients.js.map +1 -1
  19. package/out/replicache/src/frozen-json.d.ts +0 -1
  20. package/out/replicache/src/frozen-json.d.ts.map +1 -1
  21. package/out/replicache/src/frozen-json.js.map +1 -1
  22. package/out/replicache/src/index-defs.d.ts +0 -2
  23. package/out/replicache/src/index-defs.d.ts.map +1 -1
  24. package/out/replicache/src/index-defs.js +1 -1
  25. package/out/replicache/src/index-defs.js.map +1 -1
  26. package/out/replicache/src/kv/expo-sqlite/store.d.ts +0 -1
  27. package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
  28. package/out/replicache/src/kv/expo-sqlite/store.js +1 -1
  29. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  30. package/out/replicache/src/kv/idb-store.js +1 -1
  31. package/out/replicache/src/kv/idb-store.js.map +1 -1
  32. package/out/replicache/src/kv/throw-if-closed.d.ts +0 -3
  33. package/out/replicache/src/kv/throw-if-closed.d.ts.map +1 -1
  34. package/out/replicache/src/kv/throw-if-closed.js.map +1 -1
  35. package/out/replicache/src/persist/client-group-gc.d.ts +0 -7
  36. package/out/replicache/src/persist/client-group-gc.d.ts.map +1 -1
  37. package/out/replicache/src/persist/client-group-gc.js +1 -1
  38. package/out/replicache/src/persist/client-group-gc.js.map +1 -1
  39. package/out/replicache/src/process-scheduler.js +1 -1
  40. package/out/replicache/src/process-scheduler.js.map +1 -1
  41. package/out/replicache/src/replicache-impl.d.ts +2 -7
  42. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  43. package/out/replicache/src/replicache-impl.js +1 -1
  44. package/out/replicache/src/replicache-impl.js.map +1 -1
  45. package/out/replicache/src/sync/patch.d.ts +0 -5
  46. package/out/replicache/src/sync/patch.d.ts.map +1 -1
  47. package/out/replicache/src/sync/patch.js +1 -1
  48. package/out/replicache/src/sync/patch.js.map +1 -1
  49. package/out/replicache/src/sync/pull.d.ts +0 -1
  50. package/out/replicache/src/sync/pull.d.ts.map +1 -1
  51. package/out/replicache/src/sync/pull.js.map +1 -1
  52. package/out/replicache/src/sync/push.d.ts +2 -1
  53. package/out/replicache/src/sync/push.d.ts.map +1 -1
  54. package/out/replicache/src/sync/push.js.map +1 -1
  55. package/out/replicache/src/types.d.ts +2 -1
  56. package/out/replicache/src/types.d.ts.map +1 -1
  57. package/out/shared/src/asserts.d.ts +0 -5
  58. package/out/shared/src/asserts.d.ts.map +1 -1
  59. package/out/shared/src/asserts.js +1 -1
  60. package/out/shared/src/asserts.js.map +1 -1
  61. package/out/shared/src/browser-env.d.ts +0 -17
  62. package/out/shared/src/browser-env.d.ts.map +1 -1
  63. package/out/shared/src/browser-env.js.map +1 -1
  64. package/out/shared/src/deep-clone.d.ts +0 -1
  65. package/out/shared/src/deep-clone.d.ts.map +1 -1
  66. package/out/shared/src/deep-clone.js +1 -1
  67. package/out/shared/src/deep-clone.js.map +1 -1
  68. package/out/shared/src/document-visible.js +1 -1
  69. package/out/shared/src/document-visible.js.map +1 -1
  70. package/out/shared/src/expand.d.ts +0 -3
  71. package/out/shared/src/expand.d.ts.map +1 -1
  72. package/out/shared/src/immutable.d.ts +1 -1
  73. package/out/shared/src/immutable.d.ts.map +1 -1
  74. package/out/shared/src/iterables.d.ts +0 -1
  75. package/out/shared/src/iterables.d.ts.map +1 -1
  76. package/out/shared/src/iterables.js.map +1 -1
  77. package/out/shared/src/logging-test-utils.js +1 -1
  78. package/out/shared/src/logging-test-utils.js.map +1 -1
  79. package/out/shared/src/options-types.d.ts +1 -1
  80. package/out/shared/src/options-types.d.ts.map +1 -1
  81. package/out/shared/src/options.d.ts +2 -3
  82. package/out/shared/src/options.d.ts.map +1 -1
  83. package/out/shared/src/options.js +1 -1
  84. package/out/shared/src/options.js.map +1 -1
  85. package/out/shared/src/promise-race.js +1 -1
  86. package/out/shared/src/promise-race.js.map +1 -1
  87. package/out/shared/src/queue.js +1 -1
  88. package/out/shared/src/queue.js.map +1 -1
  89. package/out/shared/src/resolved-promises.d.ts +0 -4
  90. package/out/shared/src/resolved-promises.d.ts.map +1 -1
  91. package/out/shared/src/resolved-promises.js +0 -1
  92. package/out/shared/src/resolved-promises.js.map +1 -1
  93. package/out/shared/src/resolver.d.ts +9 -0
  94. package/out/shared/src/resolver.d.ts.map +1 -0
  95. package/out/shared/src/resolver.js +6 -0
  96. package/out/shared/src/resolver.js.map +1 -0
  97. package/out/shared/src/sleep.js +1 -1
  98. package/out/shared/src/sleep.js.map +1 -1
  99. package/out/shared/src/tdigest.d.ts +0 -1
  100. package/out/shared/src/tdigest.d.ts.map +1 -1
  101. package/out/shared/src/tdigest.js.map +1 -1
  102. package/out/z2s/src/sql.d.ts +1 -5
  103. package/out/z2s/src/sql.d.ts.map +1 -1
  104. package/out/z2s/src/sql.js.map +1 -1
  105. package/out/zero/package.js +2 -3
  106. package/out/zero/package.js.map +1 -1
  107. package/out/zero/src/zero-cache-dev.js +1 -1
  108. package/out/zero/src/zero-cache-dev.js.map +1 -1
  109. package/out/zero-cache/src/config/zero-config.d.ts +14 -11
  110. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  111. package/out/zero-cache/src/config/zero-config.js +23 -0
  112. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  113. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  114. package/out/zero-cache/src/db/migration.js +1 -1
  115. package/out/zero-cache/src/db/migration.js.map +1 -1
  116. package/out/zero-cache/src/db/pg-copy-binary.js +1 -1
  117. package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -1
  118. package/out/zero-cache/src/db/pg-type-parser.js.map +1 -1
  119. package/out/zero-cache/src/db/specs.d.ts +3 -4
  120. package/out/zero-cache/src/db/specs.d.ts.map +1 -1
  121. package/out/zero-cache/src/db/specs.js +5 -7
  122. package/out/zero-cache/src/db/specs.js.map +1 -1
  123. package/out/zero-cache/src/db/transaction-pool.js +2 -2
  124. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  125. package/out/zero-cache/src/observability/events.js +1 -1
  126. package/out/zero-cache/src/observability/events.js.map +1 -1
  127. package/out/zero-cache/src/server/change-streamer.js +1 -1
  128. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  129. package/out/zero-cache/src/server/main.js +1 -1
  130. package/out/zero-cache/src/server/main.js.map +1 -1
  131. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  132. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  133. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +1 -1
  134. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  135. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +1 -1
  136. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  137. package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -1
  138. package/out/zero-cache/src/services/change-source/custom/change-source.js +1 -1
  139. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  140. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
  141. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  142. package/out/zero-cache/src/services/change-source/pg/change-source.js +1 -1
  143. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  144. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
  145. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  146. package/out/zero-cache/src/services/change-source/pg/lsn.d.ts +0 -2
  147. package/out/zero-cache/src/services/change-source/pg/lsn.d.ts.map +1 -1
  148. package/out/zero-cache/src/services/change-source/pg/lsn.js.map +1 -1
  149. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +1 -1
  150. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  151. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +0 -12
  152. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  153. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +5 -3
  154. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  155. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts +1 -1
  156. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts.map +1 -1
  157. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
  158. package/out/zero-cache/src/services/change-source/pg/schema/validation.d.ts +0 -1
  159. package/out/zero-cache/src/services/change-source/pg/schema/validation.d.ts.map +1 -1
  160. package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
  161. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +1 -1
  162. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  163. package/out/zero-cache/src/services/change-streamer/broadcast.js +1 -1
  164. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  165. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +1 -1
  166. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  167. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +1 -1
  168. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  169. package/out/zero-cache/src/services/change-streamer/change-streamer.js +1 -1
  170. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  171. package/out/zero-cache/src/services/change-streamer/drain-tracker.js +1 -1
  172. package/out/zero-cache/src/services/change-streamer/drain-tracker.js.map +1 -1
  173. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js +2 -2
  174. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -1
  175. package/out/zero-cache/src/services/change-streamer/restore-admission.js +2 -2
  176. package/out/zero-cache/src/services/change-streamer/restore-admission.js.map +1 -1
  177. package/out/zero-cache/src/services/change-streamer/schema/tables.js +1 -1
  178. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  179. package/out/zero-cache/src/services/change-streamer/snapshot-message.js +2 -3
  180. package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -1
  181. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +2 -2
  182. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
  183. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -1
  184. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  185. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  186. package/out/zero-cache/src/services/change-streamer/subscribe.js +1 -1
  187. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
  188. package/out/zero-cache/src/services/change-streamer/subscriber.js +1 -1
  189. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  190. package/out/zero-cache/src/services/life-cycle.js +1 -1
  191. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  192. package/out/zero-cache/src/services/litestream/commands.js +1 -1
  193. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  194. package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
  195. package/out/zero-cache/src/services/mutagen/mutagen.js +2 -2
  196. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  197. package/out/zero-cache/src/services/plannerz.d.ts +4 -12
  198. package/out/zero-cache/src/services/plannerz.d.ts.map +1 -1
  199. package/out/zero-cache/src/services/plannerz.js +1 -3
  200. package/out/zero-cache/src/services/plannerz.js.map +1 -1
  201. package/out/zero-cache/src/services/replicator/change-log-db.js +1 -1
  202. package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
  203. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +1 -1
  204. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
  205. package/out/zero-cache/src/services/replicator/drain-handler.js +1 -1
  206. package/out/zero-cache/src/services/replicator/drain-handler.js.map +1 -1
  207. package/out/zero-cache/src/services/replicator/reporter/recorder.js +1 -1
  208. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  209. package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts +2 -11
  210. package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts.map +1 -1
  211. package/out/zero-cache/src/services/replicator/reporter/report-schema.js +1 -1
  212. package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -1
  213. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +0 -10
  214. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts.map +1 -1
  215. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  216. package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts +0 -7
  217. package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts.map +1 -1
  218. package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
  219. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
  220. package/out/zero-cache/src/services/replicator/write-worker-client.js +1 -1
  221. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  222. package/out/zero-cache/src/services/running-state.js +1 -1
  223. package/out/zero-cache/src/services/running-state.js.map +1 -1
  224. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  225. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  226. package/out/zero-cache/src/services/view-syncer/cvr-store.js +1 -1
  227. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  228. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  229. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js +1 -1
  230. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  231. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  232. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -1
  233. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +1 -1
  234. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  235. package/out/zero-cache/src/services/view-syncer/schema/cvr.js +1 -1
  236. package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
  237. package/out/zero-cache/src/services/view-syncer/schema/types.js +3 -17
  238. package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
  239. package/out/zero-cache/src/services/view-syncer/snapshotter.js +1 -1
  240. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  241. package/out/zero-cache/src/services/view-syncer/view-syncer.js +10 -10
  242. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  243. package/out/zero-cache/src/types/lite.d.ts +0 -1
  244. package/out/zero-cache/src/types/lite.d.ts.map +1 -1
  245. package/out/zero-cache/src/types/lite.js +2 -2
  246. package/out/zero-cache/src/types/lite.js.map +1 -1
  247. package/out/zero-cache/src/types/pg-types.d.ts +0 -40
  248. package/out/zero-cache/src/types/pg-types.d.ts.map +1 -1
  249. package/out/zero-cache/src/types/pg-types.js.map +1 -1
  250. package/out/zero-cache/src/types/poke-chunk.js +1 -1
  251. package/out/zero-cache/src/types/poke-chunk.js.map +1 -1
  252. package/out/zero-cache/src/types/row-key.d.ts +0 -5
  253. package/out/zero-cache/src/types/row-key.d.ts.map +1 -1
  254. package/out/zero-cache/src/types/row-key.js.map +1 -1
  255. package/out/zero-cache/src/types/streams.js +1 -1
  256. package/out/zero-cache/src/types/streams.js.map +1 -1
  257. package/out/zero-cache/src/types/subscription.js +1 -1
  258. package/out/zero-cache/src/types/subscription.js.map +1 -1
  259. package/out/zero-cache/src/types/websocket-handoff.d.ts +0 -6
  260. package/out/zero-cache/src/types/websocket-handoff.d.ts.map +1 -1
  261. package/out/zero-cache/src/types/websocket-handoff.js +8 -2
  262. package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
  263. package/out/zero-cache/src/types/ws.d.ts +2 -1
  264. package/out/zero-cache/src/types/ws.d.ts.map +1 -1
  265. package/out/zero-cache/src/types/ws.js +1 -1
  266. package/out/zero-cache/src/types/ws.js.map +1 -1
  267. package/out/zero-cache/src/workers/connection.js +4 -1
  268. package/out/zero-cache/src/workers/connection.js.map +1 -1
  269. package/out/zero-cache/src/workers/mutator.js +1 -1
  270. package/out/zero-cache/src/workers/mutator.js.map +1 -1
  271. package/out/zero-cache/src/workers/syncer.js +36 -2
  272. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  273. package/out/zero-client/src/client/active-clients-manager.d.ts.map +1 -1
  274. package/out/zero-client/src/client/active-clients-manager.js +1 -1
  275. package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
  276. package/out/zero-client/src/client/connection-manager.js +1 -1
  277. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  278. package/out/zero-client/src/client/crud.d.ts +2 -8
  279. package/out/zero-client/src/client/crud.d.ts.map +1 -1
  280. package/out/zero-client/src/client/crud.js +1 -1
  281. package/out/zero-client/src/client/crud.js.map +1 -1
  282. package/out/zero-client/src/client/custom.d.ts +0 -2
  283. package/out/zero-client/src/client/custom.d.ts.map +1 -1
  284. package/out/zero-client/src/client/custom.js +1 -1
  285. package/out/zero-client/src/client/custom.js.map +1 -1
  286. package/out/zero-client/src/client/enable-analytics.d.ts +0 -1
  287. package/out/zero-client/src/client/enable-analytics.d.ts.map +1 -1
  288. package/out/zero-client/src/client/enable-analytics.js +2 -1
  289. package/out/zero-client/src/client/enable-analytics.js.map +1 -1
  290. package/out/zero-client/src/client/error.d.ts +4 -5
  291. package/out/zero-client/src/client/error.d.ts.map +1 -1
  292. package/out/zero-client/src/client/error.js.map +1 -1
  293. package/out/zero-client/src/client/keys.d.ts +0 -1
  294. package/out/zero-client/src/client/keys.d.ts.map +1 -1
  295. package/out/zero-client/src/client/keys.js +4 -2
  296. package/out/zero-client/src/client/keys.js.map +1 -1
  297. package/out/zero-client/src/client/mutation-tracker.d.ts.map +1 -1
  298. package/out/zero-client/src/client/mutation-tracker.js +1 -1
  299. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  300. package/out/zero-client/src/client/options.d.ts +0 -4
  301. package/out/zero-client/src/client/options.d.ts.map +1 -1
  302. package/out/zero-client/src/client/options.js.map +1 -1
  303. package/out/zero-client/src/client/replicache-types.d.ts +3 -2
  304. package/out/zero-client/src/client/replicache-types.d.ts.map +1 -1
  305. package/out/zero-client/src/client/version.js +1 -1
  306. package/out/zero-client/src/client/zero-poke-handler.d.ts +1 -2
  307. package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
  308. package/out/zero-client/src/client/zero-poke-handler.js +1 -1
  309. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  310. package/out/zero-client/src/client/zero.d.ts +1 -5
  311. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  312. package/out/zero-client/src/client/zero.js +1 -1
  313. package/out/zero-client/src/client/zero.js.map +1 -1
  314. package/out/zero-client/src/types/client-state.d.ts +0 -6
  315. package/out/zero-client/src/types/client-state.d.ts.map +1 -1
  316. package/out/zero-permissions/src/permissions.d.ts +1 -12
  317. package/out/zero-permissions/src/permissions.d.ts.map +1 -1
  318. package/out/zero-permissions/src/permissions.js +1 -2
  319. package/out/zero-permissions/src/permissions.js.map +1 -1
  320. package/out/zero-protocol/src/ast.d.ts +0 -4
  321. package/out/zero-protocol/src/ast.d.ts.map +1 -1
  322. package/out/zero-protocol/src/ast.js +1 -2
  323. package/out/zero-protocol/src/ast.js.map +1 -1
  324. package/out/zero-protocol/src/change-desired-queries.d.ts +0 -20
  325. package/out/zero-protocol/src/change-desired-queries.d.ts.map +1 -1
  326. package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
  327. package/out/zero-protocol/src/client-schema.d.ts +2 -5
  328. package/out/zero-protocol/src/client-schema.d.ts.map +1 -1
  329. package/out/zero-protocol/src/client-schema.js +1 -1
  330. package/out/zero-protocol/src/client-schema.js.map +1 -1
  331. package/out/zero-protocol/src/close-connection.d.ts +0 -8
  332. package/out/zero-protocol/src/close-connection.d.ts.map +1 -1
  333. package/out/zero-protocol/src/close-connection.js +1 -1
  334. package/out/zero-protocol/src/close-connection.js.map +1 -1
  335. package/out/zero-protocol/src/connect.d.ts +0 -12
  336. package/out/zero-protocol/src/connect.d.ts.map +1 -1
  337. package/out/zero-protocol/src/connect.js +1 -1
  338. package/out/zero-protocol/src/connect.js.map +1 -1
  339. package/out/zero-protocol/src/custom-queries.d.ts +3 -17
  340. package/out/zero-protocol/src/custom-queries.d.ts.map +1 -1
  341. package/out/zero-protocol/src/custom-queries.js +1 -1
  342. package/out/zero-protocol/src/custom-queries.js.map +1 -1
  343. package/out/zero-protocol/src/data.d.ts +0 -1
  344. package/out/zero-protocol/src/data.d.ts.map +1 -1
  345. package/out/zero-protocol/src/data.js +1 -1
  346. package/out/zero-protocol/src/data.js.map +1 -1
  347. package/out/zero-protocol/src/down.d.ts +27 -27
  348. package/out/zero-protocol/src/inspect-down.d.ts +82 -84
  349. package/out/zero-protocol/src/inspect-down.d.ts.map +1 -1
  350. package/out/zero-protocol/src/inspect-down.js +1 -1
  351. package/out/zero-protocol/src/inspect-down.js.map +1 -1
  352. package/out/zero-protocol/src/inspect-up.d.ts +0 -28
  353. package/out/zero-protocol/src/inspect-up.d.ts.map +1 -1
  354. package/out/zero-protocol/src/inspect-up.js +1 -1
  355. package/out/zero-protocol/src/inspect-up.js.map +1 -1
  356. package/out/zero-protocol/src/mutate-server.d.ts +0 -21
  357. package/out/zero-protocol/src/mutate-server.d.ts.map +1 -1
  358. package/out/zero-protocol/src/mutate-server.js +1 -1
  359. package/out/zero-protocol/src/mutate-server.js.map +1 -1
  360. package/out/zero-protocol/src/mutation-type-enum.d.ts +0 -1
  361. package/out/zero-protocol/src/mutation-type-enum.d.ts.map +1 -1
  362. package/out/zero-protocol/src/mutation-type-enum.js.map +1 -1
  363. package/out/zero-protocol/src/mutation.d.ts +2 -4
  364. package/out/zero-protocol/src/mutation.d.ts.map +1 -1
  365. package/out/zero-protocol/src/mutation.js +1 -1
  366. package/out/zero-protocol/src/mutation.js.map +1 -1
  367. package/out/zero-protocol/src/mutations-patch.d.ts +0 -33
  368. package/out/zero-protocol/src/mutations-patch.d.ts.map +1 -1
  369. package/out/zero-protocol/src/mutations-patch.js +1 -1
  370. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  371. package/out/zero-protocol/src/ping.d.ts +0 -2
  372. package/out/zero-protocol/src/ping.d.ts.map +1 -1
  373. package/out/zero-protocol/src/ping.js +1 -1
  374. package/out/zero-protocol/src/ping.js.map +1 -1
  375. package/out/zero-protocol/src/poke.d.ts +3 -2
  376. package/out/zero-protocol/src/poke.d.ts.map +1 -1
  377. package/out/zero-protocol/src/poke.js +1 -1
  378. package/out/zero-protocol/src/poke.js.map +1 -1
  379. package/out/zero-protocol/src/pong.d.ts +0 -2
  380. package/out/zero-protocol/src/pong.d.ts.map +1 -1
  381. package/out/zero-protocol/src/pong.js +1 -1
  382. package/out/zero-protocol/src/pong.js.map +1 -1
  383. package/out/zero-protocol/src/pull.d.ts +2 -7
  384. package/out/zero-protocol/src/pull.d.ts.map +1 -1
  385. package/out/zero-protocol/src/pull.js +1 -1
  386. package/out/zero-protocol/src/pull.js.map +1 -1
  387. package/out/zero-protocol/src/push.d.ts +1 -3
  388. package/out/zero-protocol/src/push.d.ts.map +1 -1
  389. package/out/zero-protocol/src/push.js +1 -1
  390. package/out/zero-protocol/src/push.js.map +1 -1
  391. package/out/zero-protocol/src/queries-patch.d.ts +0 -16
  392. package/out/zero-protocol/src/queries-patch.d.ts.map +1 -1
  393. package/out/zero-protocol/src/queries-patch.js +1 -1
  394. package/out/zero-protocol/src/queries-patch.js.map +1 -1
  395. package/out/zero-protocol/src/query-server.d.ts +2 -41
  396. package/out/zero-protocol/src/query-server.d.ts.map +1 -1
  397. package/out/zero-protocol/src/query-server.js +1 -1
  398. package/out/zero-protocol/src/query-server.js.map +1 -1
  399. package/out/zero-protocol/src/up.d.ts +42 -42
  400. package/out/zero-protocol/src/version.d.ts +0 -1
  401. package/out/zero-protocol/src/version.d.ts.map +1 -1
  402. package/out/zero-protocol/src/version.js.map +1 -1
  403. package/out/zero-react/src/use-query.js +1 -1
  404. package/out/zero-react/src/use-query.js.map +1 -1
  405. package/out/zero-schema/src/builder/relationship-builder.d.ts +0 -2
  406. package/out/zero-schema/src/builder/relationship-builder.d.ts.map +1 -1
  407. package/out/zero-schema/src/builder/relationship-builder.js.map +1 -1
  408. package/out/zero-schema/src/builder/table-builder.d.ts +1 -1
  409. package/out/zero-schema/src/builder/table-builder.d.ts.map +1 -1
  410. package/out/zero-schema/src/builder/table-builder.js +1 -1
  411. package/out/zero-schema/src/builder/table-builder.js.map +1 -1
  412. package/out/zero-schema/src/schema-config.d.ts +0 -1
  413. package/out/zero-schema/src/schema-config.d.ts.map +1 -1
  414. package/out/zero-schema/src/schema-config.js +1 -1
  415. package/out/zero-schema/src/schema-config.js.map +1 -1
  416. package/out/zero-schema/src/table-schema.d.ts +3 -11
  417. package/out/zero-schema/src/table-schema.d.ts.map +1 -1
  418. package/out/zero-server/src/queries/process-queries.d.ts +2 -4
  419. package/out/zero-server/src/queries/process-queries.d.ts.map +1 -1
  420. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  421. package/out/zero-solid/src/solid-view.d.ts +0 -2
  422. package/out/zero-solid/src/solid-view.d.ts.map +1 -1
  423. package/out/zero-solid/src/solid-view.js +1 -1
  424. package/out/zero-solid/src/solid-view.js.map +1 -1
  425. package/out/zero-types/src/name-mapper.d.ts +2 -1
  426. package/out/zero-types/src/name-mapper.d.ts.map +1 -1
  427. package/out/zero-types/src/name-mapper.js.map +1 -1
  428. package/out/zero-types/src/schema-value.d.ts +3 -2
  429. package/out/zero-types/src/schema-value.d.ts.map +1 -1
  430. package/out/zero-types/src/schema.d.ts +1 -1
  431. package/out/zero-types/src/schema.d.ts.map +1 -1
  432. package/out/zql/src/builder/builder.d.ts +1 -11
  433. package/out/zql/src/builder/builder.d.ts.map +1 -1
  434. package/out/zql/src/builder/builder.js +1 -1
  435. package/out/zql/src/builder/builder.js.map +1 -1
  436. package/out/zql/src/builder/filter.d.ts +0 -1
  437. package/out/zql/src/builder/filter.d.ts.map +1 -1
  438. package/out/zql/src/builder/filter.js.map +1 -1
  439. package/out/zql/src/error.d.ts +0 -5
  440. package/out/zql/src/error.d.ts.map +1 -1
  441. package/out/zql/src/error.js.map +1 -1
  442. package/out/zql/src/ivm/constraint.d.ts +0 -7
  443. package/out/zql/src/ivm/constraint.d.ts.map +1 -1
  444. package/out/zql/src/ivm/constraint.js +1 -1
  445. package/out/zql/src/ivm/constraint.js.map +1 -1
  446. package/out/zql/src/ivm/data.d.ts +0 -1
  447. package/out/zql/src/ivm/data.d.ts.map +1 -1
  448. package/out/zql/src/ivm/data.js.map +1 -1
  449. package/out/zql/src/ivm/memory-source.d.ts +1 -2
  450. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  451. package/out/zql/src/ivm/memory-source.js +1 -1
  452. package/out/zql/src/ivm/memory-source.js.map +1 -1
  453. package/out/zql/src/ivm/stream.d.ts +0 -1
  454. package/out/zql/src/ivm/stream.d.ts.map +1 -1
  455. package/out/zql/src/ivm/stream.js.map +1 -1
  456. package/out/zql/src/ivm/union-fan-in.d.ts +0 -1
  457. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  458. package/out/zql/src/ivm/union-fan-in.js +1 -1
  459. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  460. package/out/zql/src/ivm/view-apply-change.d.ts +5 -35
  461. package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
  462. package/out/zql/src/ivm/view-apply-change.js +1 -1
  463. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  464. package/out/zql/src/mutate/crud.d.ts +1 -3
  465. package/out/zql/src/mutate/crud.d.ts.map +1 -1
  466. package/out/zql/src/mutate/crud.js +1 -1
  467. package/out/zql/src/mutate/crud.js.map +1 -1
  468. package/out/zql/src/mutate/mutator-registry.d.ts +1 -1
  469. package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -1
  470. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  471. package/out/zql/src/mutate/mutator.d.ts +1 -1
  472. package/out/zql/src/mutate/mutator.d.ts.map +1 -1
  473. package/out/zql/src/mutate/mutator.js.map +1 -1
  474. package/out/zql/src/planner/planner-debug.d.ts +10 -9
  475. package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
  476. package/out/zql/src/planner/planner-debug.js.map +1 -1
  477. package/out/zql/src/planner/planner-node.d.ts +0 -1
  478. package/out/zql/src/planner/planner-node.d.ts.map +1 -1
  479. package/out/zql/src/planner/planner-node.js.map +1 -1
  480. package/out/zql/src/planner/planner-warnings.d.ts +3 -3
  481. package/out/zql/src/planner/planner-warnings.d.ts.map +1 -1
  482. package/out/zql/src/planner/planner-warnings.js.map +1 -1
  483. package/out/zql/src/query/expression.d.ts +0 -22
  484. package/out/zql/src/query/expression.d.ts.map +1 -1
  485. package/out/zql/src/query/expression.js +1 -1
  486. package/out/zql/src/query/expression.js.map +1 -1
  487. package/out/zql/src/query/query-delegate-base.d.ts +0 -7
  488. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  489. package/out/zql/src/query/query-delegate-base.js +2 -2
  490. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  491. package/out/zql/src/query/query-delegate.d.ts +1 -4
  492. package/out/zql/src/query/query-delegate.d.ts.map +1 -1
  493. package/out/zql/src/query/query-internals.d.ts +0 -3
  494. package/out/zql/src/query/query-internals.d.ts.map +1 -1
  495. package/out/zql/src/query/query-internals.js.map +1 -1
  496. package/out/zql/src/query/query-registry.d.ts +5 -5
  497. package/out/zql/src/query/query-registry.d.ts.map +1 -1
  498. package/out/zql/src/query/query-registry.js.map +1 -1
  499. package/out/zql/src/query/query.d.ts +2 -13
  500. package/out/zql/src/query/query.d.ts.map +1 -1
  501. package/out/zql/src/query/runnable-query-impl.d.ts +1 -19
  502. package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
  503. package/out/zql/src/query/runnable-query-impl.js +1 -1
  504. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  505. package/out/zql/src/query/ttl.d.ts +2 -3
  506. package/out/zql/src/query/ttl.d.ts.map +1 -1
  507. package/out/zql/src/query/ttl.js.map +1 -1
  508. package/out/zqlite/src/query-builder.d.ts +0 -2
  509. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  510. package/out/zqlite/src/query-builder.js +1 -1
  511. package/out/zqlite/src/query-builder.js.map +1 -1
  512. package/out/zqlite/src/table-source.d.ts +0 -1
  513. package/out/zqlite/src/table-source.d.ts.map +1 -1
  514. package/out/zqlite/src/table-source.js +1 -1
  515. package/out/zqlite/src/table-source.js.map +1 -1
  516. package/package.json +2 -3
@@ -1 +1 @@
1
- {"version":3,"file":"client-handler.js","names":["v.parse","v","ErrorKind.InvalidConnectionRequestBaseCookie","ErrorOrigin.ZeroCache"],"sources":["../../../../../../zero-cache/src/services/view-syncer/client-handler.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport type {JSONObject} from '../../../../shared/src/bigint-json.ts';\nimport {getErrorMessage} from '../../../../shared/src/error.ts';\nimport {hasOwn} from '../../../../shared/src/has-own.ts';\nimport {\n assertJSONValue,\n type JSONObject as SafeJSONObject,\n} from '../../../../shared/src/json.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport type {Writable} from '../../../../shared/src/writable.ts';\nimport type {ErroredQuery} from '../../../../zero-protocol/src/custom-queries.ts';\nimport type {DeleteClientsBody} from '../../../../zero-protocol/src/delete-clients.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 {\n ProtocolError,\n type TransformFailedBody,\n} from '../../../../zero-protocol/src/error.ts';\nimport type {InspectDownBody} from '../../../../zero-protocol/src/inspect-down.ts';\nimport {mutationResultSchema} from '../../../../zero-protocol/src/mutation.ts';\nimport type {\n PokePartBody,\n PokePartMessage,\n PokeStartBody,\n} from '../../../../zero-protocol/src/poke.ts';\nimport {primaryKeyValueRecordSchema} from '../../../../zero-protocol/src/primary-key.ts';\nimport type {RowPatchOp} from '../../../../zero-protocol/src/row-patch.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport type {ViewSyncerDownstream} from '../../types/downstream.ts';\nimport {\n getLogLevel,\n wrapWithProtocolError,\n} from '../../types/error-with-level.ts';\nimport {upstreamSchema, type ShardID} from '../../types/shards.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {\n cmpVersions,\n cookieToVersion,\n versionToCookie,\n versionToNullableCookie,\n type CVRVersion,\n type DelQueryPatch,\n type NullableCVRVersion,\n type PutQueryPatch,\n type RowID,\n} from './schema/types.ts';\n\nexport type PutRowPatch = {\n type: 'row';\n op: 'put';\n id: RowID;\n contents: JSONObject;\n};\n\nexport type DeleteRowPatch = {\n type: 'row';\n op: 'del';\n id: RowID;\n};\n\nexport type RowPatch = PutRowPatch | DeleteRowPatch;\nexport type ConfigPatch = DelQueryPatch | PutQueryPatch;\n\nexport type Patch = ConfigPatch | RowPatch;\n\nexport type PatchToVersion = {\n patch: Patch;\n toVersion: CVRVersion;\n};\n\nexport interface PokeHandler {\n addPatch(patch: PatchToVersion): Promise<void>;\n cancel(): Promise<void>;\n end(finalVersion: CVRVersion): Promise<void>;\n}\n\n/**\n * A put/del row patch and its JSON serialization. Computed once per client\n * group and shared by all of the group's clients.\n */\ntype SerializedRowPatch = {\n readonly rowPatch: RowPatchOp;\n readonly json: string;\n};\n\n/**\n * Lazily holds the {@link SerializedRowPatch} of one patch so that it is\n * computed at most once, by the first client that needs it.\n */\ntype SharedRowPatch = {serialized?: SerializedRowPatch | undefined};\n\n/** A {@link PokeHandler} for a single client. */\nexport interface ClientPokeHandler extends PokeHandler {\n addPatch(patch: PatchToVersion, shared?: SharedRowPatch): Promise<void>;\n\n /**\n * Whether a patch has been sent to the client. Once one has, the poke can\n * only be ended with a `finalVersion` that is ahead of the client's base\n * version.\n */\n readonly patchesSent: boolean;\n}\n\n/**\n * A {@link PokeHandler} for multiple clients, as returned by\n * {@link startPoke}.\n */\nexport interface MultiPokeHandler extends PokeHandler {\n /** Whether a patch has been sent to any of the clients. */\n readonly patchesSent: boolean;\n}\n\nconst NOOP: ClientPokeHandler = {\n patchesSent: false,\n addPatch: () => promiseVoid,\n cancel: () => promiseVoid,\n end: () => promiseVoid,\n};\n\n/** Wraps PokeHandlers for multiple clients in a single PokeHandler. */\nexport function startPoke(\n lc: LogContext,\n clients: ClientHandler[],\n tentativeVersion: CVRVersion,\n): MultiPokeHandler {\n const pokers = clients.map(c => c.startPoke(tentativeVersion));\n\n // Promise.allSettled() ensures that a failed (e.g. disconnected) client\n // does not prevent other clients from receiving the pokes. However, the\n // rate (per client group) will be limited by the slowest connection.\n //\n // The individual pokers report their own failures (by failing the\n // connection), so a rejection here is not expected. Log it rather than\n // dropping it on the floor.\n const settle = async (op: string, results: Promise<void>[]) => {\n for (const result of await Promise.allSettled(results)) {\n if (result.status === 'rejected') {\n lc.error?.(`unhandled error poking client (${op})`, result.reason);\n }\n }\n };\n\n return {\n get patchesSent() {\n return pokers.some(poker => poker.patchesSent);\n },\n addPatch: patch => {\n const shared: SharedRowPatch = {};\n return settle(\n 'addPatch',\n pokers.map(poker => poker.addPatch(patch, shared)),\n );\n },\n cancel: () =>\n settle(\n 'cancel',\n pokers.map(poker => poker.cancel()),\n ),\n end: finalVersion =>\n settle(\n 'end',\n pokers.map(poker => poker.end(finalVersion)),\n ),\n };\n}\n\n// Soft limit on the serialized row characters accumulated in a poke part.\n// This bounds view-syncer memory and keeps legacy JSON messages reasonably\n// sized. PokeChunkEncoder separately enforces the binary frame limit in bytes.\nexport const POKE_PART_FLUSH_THRESHOLD_CHARS = 1024 * 1024;\nconst PART_COUNT_FLUSH_THRESHOLD = 100;\n\n/**\n * Handles a single `ViewSyncer` connection.\n */\nexport class ClientHandler {\n readonly #clientGroupID: string;\n readonly clientID: string;\n readonly wsID: string;\n readonly #zeroClientsTable: string;\n readonly #zeroMutationsTable: string;\n readonly #lc: LogContext;\n readonly #downstream: Subscription<ViewSyncerDownstream>;\n #baseVersion: NullableCVRVersion;\n // We will send a poke on connect even if the client is already caught up, so that it can learn its\n // got-queries state has been reconciled with the server. After that, we will only send a poke if\n // the client is behind.\n #everPoked = false;\n\n readonly #pokeTime = getOrCreateLatencyHistogram(\n 'sync',\n 'poke.time',\n 'Time elapsed for each poke transaction. Canceled / noop pokes are excluded.',\n );\n\n readonly #pokeTransactions = getOrCreateCounter(\n 'sync',\n 'poke.transactions',\n 'Count of poke transactions.',\n );\n\n readonly #pokedRows = getOrCreateCounter(\n 'sync',\n 'poke.rows',\n 'Count of poked rows.',\n );\n\n constructor(\n lc: LogContext,\n clientGroupID: string,\n clientID: string,\n wsID: string,\n shard: ShardID,\n baseCookie: string | null,\n downstream: Subscription<ViewSyncerDownstream>,\n ) {\n lc.debug?.('new client handler');\n this.#clientGroupID = clientGroupID;\n this.clientID = clientID;\n this.wsID = wsID;\n this.#zeroClientsTable = `${upstreamSchema(shard)}.clients`;\n this.#zeroMutationsTable = `${upstreamSchema(shard)}.mutations`;\n this.#lc = lc;\n this.#downstream = downstream;\n this.#baseVersion = parseBaseCookie(baseCookie);\n }\n\n version(): NullableCVRVersion {\n return this.#baseVersion;\n }\n\n async #push(msg: Downstream, serialized?: string | undefined): Promise<void> {\n const {result} = this.#downstream.push({message: msg, serialized});\n await result;\n }\n\n fail(e: unknown) {\n this.#lc[getLogLevel(e)]?.(\n `view-syncer closing connection with error: ${String(e)}`,\n e,\n );\n this.#downstream.fail(wrapWithProtocolError(e));\n }\n\n close(reason: string) {\n this.#lc.debug?.(`view-syncer closing connection: ${reason}`);\n this.#downstream.cancel();\n }\n\n startPoke(tentativeVersion: CVRVersion): ClientPokeHandler {\n const pokeID = versionToCookie(tentativeVersion);\n const lc = this.#lc.withContext('pokeID', pokeID);\n\n // Force an (empty) initial poke even when already caught up; see #everPoked.\n const forceInitialPoke = !this.#everPoked;\n const cmp = cmpVersions(this.#baseVersion, tentativeVersion);\n if (cmp > 0 || (cmp === 0 && !forceInitialPoke)) {\n lc.info?.(`already caught up, not sending poke.`);\n return NOOP;\n }\n\n const baseCookie = versionToNullableCookie(this.#baseVersion);\n const cookie = versionToCookie(tentativeVersion);\n lc.debug?.(`starting poke from ${baseCookie} to ${cookie}`);\n\n const start = performance.now();\n\n const pokeStart: PokeStartBody = {pokeID, baseCookie};\n\n let pokeStarted = false;\n let body: PokePartBody | undefined;\n // rowsPatch is kept out of `body` until it is flushed so that the poke\n // part can be serialized by concatenating the pre-serialized rows.\n let rowsPatch: RowPatchOp[] = [];\n let rowsJSON: string[] = [];\n let partCount = 0;\n let serializedRowsLength = 0;\n\n const ensureBody = async () => {\n if (!pokeStarted) {\n await this.#push(['pokeStart', pokeStart]);\n pokeStarted = true;\n }\n return (body ??= {pokeID});\n };\n\n const flushBody = async () => {\n if (body) {\n let message: PokePartMessage;\n let serialized: string;\n if (rowsPatch.length === 0) {\n message = ['pokePart', body];\n serialized = JSON.stringify(message);\n } else {\n // Equivalent to JSON.stringify(message): rowsPatch is the last\n // property of the body.\n const prefix = JSON.stringify(['pokePart', body]);\n message = ['pokePart', {...body, rowsPatch}];\n serialized = `${prefix.slice(0, -2)},\"rowsPatch\":[${rowsJSON.join(',')}]}]`;\n rowsPatch = [];\n rowsJSON = [];\n }\n await this.#push(message, serialized);\n body = undefined;\n partCount = 0;\n serializedRowsLength = 0;\n }\n };\n\n const addPatch = async (\n patchToVersion: PatchToVersion,\n shared: SharedRowPatch,\n ) => {\n const {patch, toVersion} = patchToVersion;\n if (cmpVersions(toVersion, this.#baseVersion) <= 0) {\n return;\n }\n const target = await ensureBody();\n\n const {type, op} = patch;\n switch (type) {\n case 'query': {\n const patches = patch.clientID\n ? ((target.desiredQueriesPatches ??= {})[patch.clientID] ??= [])\n : (target.gotQueriesPatch ??= []);\n patches.push({op, hash: patch.id});\n break;\n }\n case 'row':\n if (patch.id.table === this.#zeroClientsTable) {\n this.#updateLMIDs((target.lastMutationIDChanges ??= {}), patch);\n } else if (patch.id.table === this.#zeroMutationsTable) {\n const patches = (target.mutationsPatch ??= []);\n if (op === 'put') {\n const row = v.parse(\n ensureSafeJSON(patch.contents),\n mutationRowSchema,\n 'passthrough',\n );\n patches.push({\n op: 'put',\n mutation: {\n id: {\n clientID: row.clientID,\n id: row.mutationID,\n },\n result: row.result,\n },\n });\n } else {\n const {clientID, mutationID} = patch.id.rowKey;\n assert(\n typeof clientID === 'string',\n 'client id must be a string',\n );\n const id = Number(mutationID);\n assert(\n !Number.isNaN(id) && Number.isFinite(id) && id >= 0,\n 'mutation id must be a finite number',\n );\n patches.push({\n op: 'del',\n id: {\n clientID,\n id,\n },\n });\n }\n } else {\n const {rowPatch, json} = (shared.serialized ??=\n serializeRowPatch(patch));\n rowsPatch.push(rowPatch);\n rowsJSON.push(json);\n serializedRowsLength += json.length;\n }\n break;\n default:\n unreachable(patch);\n }\n\n partCount++;\n if (\n serializedRowsLength >= POKE_PART_FLUSH_THRESHOLD_CHARS ||\n partCount >= PART_COUNT_FLUSH_THRESHOLD\n ) {\n await flushBody();\n }\n };\n\n // A poke that was started must always be ended. If it isn't, the\n // connection is left mid-poke and the *next* pokeStart fails the\n // DownstreamSender's in-progress check, surfacing the error far from\n // its cause. Fail the connection instead; the client reconnects and\n // resyncs from its baseVersion.\n return {\n get patchesSent() {\n return pokeStarted;\n },\n addPatch: async (\n patchToVersion: PatchToVersion,\n shared: SharedRowPatch = {},\n ) => {\n try {\n await addPatch(patchToVersion, shared);\n if (patchToVersion.patch.type === 'row') {\n this.#pokedRows.add(1);\n }\n } catch (e) {\n this.fail(e);\n }\n },\n\n cancel: async () => {\n try {\n if (pokeStarted) {\n await this.#push(['pokeEnd', {pokeID, cookie: '', cancel: true}]);\n }\n } catch (e) {\n this.fail(e);\n }\n },\n\n end: async (finalVersion: CVRVersion) => {\n try {\n const cookie = versionToCookie(finalVersion);\n if (!pokeStarted) {\n if (\n cmpVersions(this.#baseVersion, finalVersion) === 0 &&\n !forceInitialPoke\n ) {\n return; // Nothing changed and nothing was sent.\n }\n await this.#push(['pokeStart', pokeStart]);\n } else if (cmpVersions(this.#baseVersion, finalVersion) >= 0) {\n // Sanity check: If the poke was started, the finalVersion\n // must be > #baseVersion.\n throw new Error(\n `Patches were sent but finalVersion ${cookie} is not ` +\n `greater than baseVersion ` +\n `${versionToNullableCookie(this.#baseVersion)}`,\n );\n }\n await flushBody();\n await this.#push(['pokeEnd', {pokeID, cookie}]);\n this.#baseVersion = finalVersion;\n this.#everPoked = true;\n\n const elapsed = performance.now() - start;\n this.#pokeTransactions.add(1);\n this.#pokeTime.recordMs(elapsed);\n } catch (e) {\n this.fail(e);\n }\n },\n };\n }\n\n async sendDeleteClients(\n lc: LogContext,\n deletedClientIDs: string[],\n deletedClientGroupIDs: string[],\n ) {\n const deleteClientsBody: Writable<DeleteClientsBody> = {};\n if (deletedClientIDs.length > 0) {\n deleteClientsBody.clientIDs = deletedClientIDs;\n }\n if (deletedClientGroupIDs.length > 0) {\n deleteClientsBody.clientGroupIDs = deletedClientGroupIDs;\n }\n lc.debug?.('sending deleteClients', deleteClientsBody);\n await this.#push(['deleteClients', deleteClientsBody]);\n }\n\n sendQueryTransformApplicationErrors(errors: ErroredQuery[]) {\n void this.#push(['transformError', errors]);\n }\n\n sendQueryTransformFailedError(error: TransformFailedBody) {\n this.fail(new ProtocolError(error));\n }\n\n sendInspectResponse(lc: LogContext, response: InspectDownBody): void {\n lc.debug?.('sending inspect response', response);\n this.#downstream.push({\n message: ['inspect', response],\n serialized: undefined,\n });\n }\n\n #updateLMIDs(lmids: Record<string, number>, patch: RowPatch) {\n if (patch.op === 'put') {\n const row = ensureSafeJSON(patch.contents);\n const {clientGroupID, clientID, lastMutationID} = v.parse(\n row,\n lmidRowSchema,\n 'passthrough',\n );\n if (clientGroupID !== this.#clientGroupID) {\n this.#lc.error?.(\n `Received clients row for wrong clientGroupID. Ignoring.`,\n clientGroupID,\n );\n } else {\n lmids[clientID] = lastMutationID;\n }\n } else {\n // The 'constrain' and 'del' ops for clients can be ignored.\n patch.op satisfies 'constrain' | 'del';\n }\n }\n}\n\n// Note: The {APP_ID}_{SHARD_ID}.clients table is set up in replicator/initial-sync.ts.\nconst lmidRowSchema = v.object({\n clientGroupID: v.string(),\n clientID: v.string(),\n lastMutationID: v.number(), // Actually returned as a bigint, but converted by ensureSafeJSON().\n});\n\nconst mutationRowSchema = v.object({\n clientGroupID: v.string(),\n clientID: v.string(),\n mutationID: v.number(),\n result: mutationResultSchema,\n});\n\nfunction serializeRowPatch(patch: RowPatch): SerializedRowPatch {\n const rowPatch = makeRowPatch(patch);\n return {rowPatch, json: JSON.stringify(rowPatch)};\n}\n\nfunction makeRowPatch(patch: RowPatch): RowPatchOp {\n const {\n op,\n id: {table: tableName, rowKey: id},\n } = patch;\n\n switch (op) {\n case 'put':\n // ensureSafeJSON() converts bigints and asserts (outside of prod) that\n // the values are JSON, which is all that rowSchema checks.\n return {\n op: 'put',\n tableName,\n value: ensureSafeJSON(patch.contents),\n };\n\n case 'del':\n return {\n op,\n tableName,\n id: v.parse(id, primaryKeyValueRecordSchema),\n };\n\n default:\n unreachable(op);\n }\n}\n\n/**\n * Column values of type INT8 are returned as the `bigint` from the\n * Postgres library. These are converted to `number` if they are within\n * the safe Number range, allowing the protocol to support numbers larger\n * than 32-bits. Values outside of the safe number range (e.g. > 2^53) will\n * result in an Error.\n */\nexport function ensureSafeJSON(row: JSONObject): SafeJSONObject {\n let modified: Record<string, unknown> | undefined;\n for (const k in row) {\n if (!hasOwn(row, k)) {\n continue;\n }\n const v = row[k];\n if (typeof v === 'bigint') {\n if (v < Number.MIN_SAFE_INTEGER || v > Number.MAX_SAFE_INTEGER) {\n throw new Error(`Value of \"${k}\" exceeds safe Number range (${v})`);\n }\n (modified ??= {...row})[k] = Number(v);\n } else if (typeof v === 'object') {\n assertJSONValue(v);\n }\n }\n return (modified ?? row) as SafeJSONObject;\n}\n\n/**\n * The baseCookie comes from the connection URL so it is client input. A\n * malformed one is reported to the client as an invalid baseCookie (which\n * makes it reset its local state) rather than as an internal error.\n */\nfunction parseBaseCookie(baseCookie: string | null): NullableCVRVersion {\n try {\n return cookieToVersion(baseCookie);\n } catch (e) {\n throw new ProtocolError(\n {\n kind: ErrorKind.InvalidConnectionRequestBaseCookie,\n message: `Invalid baseCookie ${JSON.stringify(baseCookie)}: ${getErrorMessage(e)}`,\n origin: ErrorOrigin.ZeroCache,\n },\n {cause: e},\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-handler.js","names":["v.parse","v","ErrorKind.InvalidConnectionRequestBaseCookie","ErrorOrigin.ZeroCache"],"sources":["../../../../../../zero-cache/src/services/view-syncer/client-handler.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport type {JSONObject} from '../../../../shared/src/bigint-json.ts';\nimport {getErrorMessage} from '../../../../shared/src/error.ts';\nimport {hasOwn} from '../../../../shared/src/has-own.ts';\nimport {\n assertJSONValue,\n type JSONObject as SafeJSONObject,\n} from '../../../../shared/src/json.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport type {Writable} from '../../../../shared/src/writable.ts';\nimport type {ErroredQuery} from '../../../../zero-protocol/src/custom-queries.ts';\nimport type {DeleteClientsBody} from '../../../../zero-protocol/src/delete-clients.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 {\n ProtocolError,\n type TransformFailedBody,\n} from '../../../../zero-protocol/src/error.ts';\nimport type {InspectDownBody} from '../../../../zero-protocol/src/inspect-down.ts';\nimport {mutationResultSchema} from '../../../../zero-protocol/src/mutation.ts';\nimport type {\n PokePartBody,\n PokePartMessage,\n PokeStartBody,\n} from '../../../../zero-protocol/src/poke.ts';\nimport {primaryKeyValueRecordSchema} from '../../../../zero-protocol/src/primary-key.ts';\nimport type {RowPatchOp} from '../../../../zero-protocol/src/row-patch.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport type {ViewSyncerDownstream} from '../../types/downstream.ts';\nimport {\n getLogLevel,\n wrapWithProtocolError,\n} from '../../types/error-with-level.ts';\nimport {upstreamSchema, type ShardID} from '../../types/shards.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {\n cmpVersions,\n cookieToVersion,\n versionToCookie,\n versionToNullableCookie,\n type CVRVersion,\n type DelQueryPatch,\n type NullableCVRVersion,\n type PutQueryPatch,\n type RowID,\n} from './schema/types.ts';\n\ntype PutRowPatch = {\n type: 'row';\n op: 'put';\n id: RowID;\n contents: JSONObject;\n};\n\ntype DeleteRowPatch = {\n type: 'row';\n op: 'del';\n id: RowID;\n};\n\nexport type RowPatch = PutRowPatch | DeleteRowPatch;\ntype ConfigPatch = DelQueryPatch | PutQueryPatch;\n\nexport type Patch = ConfigPatch | RowPatch;\n\nexport type PatchToVersion = {\n patch: Patch;\n toVersion: CVRVersion;\n};\n\nexport interface PokeHandler {\n addPatch(patch: PatchToVersion): Promise<void>;\n cancel(): Promise<void>;\n end(finalVersion: CVRVersion): Promise<void>;\n}\n\n/**\n * A put/del row patch and its JSON serialization. Computed once per client\n * group and shared by all of the group's clients.\n */\ntype SerializedRowPatch = {\n readonly rowPatch: RowPatchOp;\n readonly json: string;\n};\n\n/**\n * Lazily holds the {@link SerializedRowPatch} of one patch so that it is\n * computed at most once, by the first client that needs it.\n */\ntype SharedRowPatch = {serialized?: SerializedRowPatch | undefined};\n\n/** A {@link PokeHandler} for a single client. */\nexport interface ClientPokeHandler extends PokeHandler {\n addPatch(patch: PatchToVersion, shared?: SharedRowPatch): Promise<void>;\n\n /**\n * Whether a patch has been sent to the client. Once one has, the poke can\n * only be ended with a `finalVersion` that is ahead of the client's base\n * version.\n */\n readonly patchesSent: boolean;\n}\n\n/**\n * A {@link PokeHandler} for multiple clients, as returned by\n * {@link startPoke}.\n */\nexport interface MultiPokeHandler extends PokeHandler {\n /** Whether a patch has been sent to any of the clients. */\n readonly patchesSent: boolean;\n}\n\nconst NOOP: ClientPokeHandler = {\n patchesSent: false,\n addPatch: () => promiseVoid,\n cancel: () => promiseVoid,\n end: () => promiseVoid,\n};\n\n/** Wraps PokeHandlers for multiple clients in a single PokeHandler. */\nexport function startPoke(\n lc: LogContext,\n clients: ClientHandler[],\n tentativeVersion: CVRVersion,\n): MultiPokeHandler {\n const pokers = clients.map(c => c.startPoke(tentativeVersion));\n\n // Promise.allSettled() ensures that a failed (e.g. disconnected) client\n // does not prevent other clients from receiving the pokes. However, the\n // rate (per client group) will be limited by the slowest connection.\n //\n // The individual pokers report their own failures (by failing the\n // connection), so a rejection here is not expected. Log it rather than\n // dropping it on the floor.\n const settle = async (op: string, results: Promise<void>[]) => {\n for (const result of await Promise.allSettled(results)) {\n if (result.status === 'rejected') {\n lc.error?.(`unhandled error poking client (${op})`, result.reason);\n }\n }\n };\n\n return {\n get patchesSent() {\n return pokers.some(poker => poker.patchesSent);\n },\n addPatch: patch => {\n const shared: SharedRowPatch = {};\n return settle(\n 'addPatch',\n pokers.map(poker => poker.addPatch(patch, shared)),\n );\n },\n cancel: () =>\n settle(\n 'cancel',\n pokers.map(poker => poker.cancel()),\n ),\n end: finalVersion =>\n settle(\n 'end',\n pokers.map(poker => poker.end(finalVersion)),\n ),\n };\n}\n\n// Soft limit on the serialized row characters accumulated in a poke part.\n// This bounds view-syncer memory and keeps legacy JSON messages reasonably\n// sized. PokeChunkEncoder separately enforces the binary frame limit in bytes.\nexport const POKE_PART_FLUSH_THRESHOLD_CHARS = 1024 * 1024;\nconst PART_COUNT_FLUSH_THRESHOLD = 100;\n\n/**\n * Handles a single `ViewSyncer` connection.\n */\nexport class ClientHandler {\n readonly #clientGroupID: string;\n readonly clientID: string;\n readonly wsID: string;\n readonly #zeroClientsTable: string;\n readonly #zeroMutationsTable: string;\n readonly #lc: LogContext;\n readonly #downstream: Subscription<ViewSyncerDownstream>;\n #baseVersion: NullableCVRVersion;\n // We will send a poke on connect even if the client is already caught up, so that it can learn its\n // got-queries state has been reconciled with the server. After that, we will only send a poke if\n // the client is behind.\n #everPoked = false;\n\n readonly #pokeTime = getOrCreateLatencyHistogram(\n 'sync',\n 'poke.time',\n 'Time elapsed for each poke transaction. Canceled / noop pokes are excluded.',\n );\n\n readonly #pokeTransactions = getOrCreateCounter(\n 'sync',\n 'poke.transactions',\n 'Count of poke transactions.',\n );\n\n readonly #pokedRows = getOrCreateCounter(\n 'sync',\n 'poke.rows',\n 'Count of poked rows.',\n );\n\n constructor(\n lc: LogContext,\n clientGroupID: string,\n clientID: string,\n wsID: string,\n shard: ShardID,\n baseCookie: string | null,\n downstream: Subscription<ViewSyncerDownstream>,\n ) {\n lc.debug?.('new client handler');\n this.#clientGroupID = clientGroupID;\n this.clientID = clientID;\n this.wsID = wsID;\n this.#zeroClientsTable = `${upstreamSchema(shard)}.clients`;\n this.#zeroMutationsTable = `${upstreamSchema(shard)}.mutations`;\n this.#lc = lc;\n this.#downstream = downstream;\n this.#baseVersion = parseBaseCookie(baseCookie);\n }\n\n version(): NullableCVRVersion {\n return this.#baseVersion;\n }\n\n async #push(msg: Downstream, serialized?: string | undefined): Promise<void> {\n const {result} = this.#downstream.push({message: msg, serialized});\n await result;\n }\n\n fail(e: unknown) {\n this.#lc[getLogLevel(e)]?.(\n `view-syncer closing connection with error: ${String(e)}`,\n e,\n );\n this.#downstream.fail(wrapWithProtocolError(e));\n }\n\n close(reason: string) {\n this.#lc.debug?.(`view-syncer closing connection: ${reason}`);\n this.#downstream.cancel();\n }\n\n startPoke(tentativeVersion: CVRVersion): ClientPokeHandler {\n const pokeID = versionToCookie(tentativeVersion);\n const lc = this.#lc.withContext('pokeID', pokeID);\n\n // Force an (empty) initial poke even when already caught up; see #everPoked.\n const forceInitialPoke = !this.#everPoked;\n const cmp = cmpVersions(this.#baseVersion, tentativeVersion);\n if (cmp > 0 || (cmp === 0 && !forceInitialPoke)) {\n lc.info?.(`already caught up, not sending poke.`);\n return NOOP;\n }\n\n const baseCookie = versionToNullableCookie(this.#baseVersion);\n const cookie = versionToCookie(tentativeVersion);\n lc.debug?.(`starting poke from ${baseCookie} to ${cookie}`);\n\n const start = performance.now();\n\n const pokeStart: PokeStartBody = {pokeID, baseCookie};\n\n let pokeStarted = false;\n let body: PokePartBody | undefined;\n // rowsPatch is kept out of `body` until it is flushed so that the poke\n // part can be serialized by concatenating the pre-serialized rows.\n let rowsPatch: RowPatchOp[] = [];\n let rowsJSON: string[] = [];\n let partCount = 0;\n let serializedRowsLength = 0;\n\n const ensureBody = async () => {\n if (!pokeStarted) {\n await this.#push(['pokeStart', pokeStart]);\n pokeStarted = true;\n }\n return (body ??= {pokeID});\n };\n\n const flushBody = async () => {\n if (body) {\n let message: PokePartMessage;\n let serialized: string;\n if (rowsPatch.length === 0) {\n message = ['pokePart', body];\n serialized = JSON.stringify(message);\n } else {\n // Equivalent to JSON.stringify(message): rowsPatch is the last\n // property of the body.\n const prefix = JSON.stringify(['pokePart', body]);\n message = ['pokePart', {...body, rowsPatch}];\n serialized = `${prefix.slice(0, -2)},\"rowsPatch\":[${rowsJSON.join(',')}]}]`;\n rowsPatch = [];\n rowsJSON = [];\n }\n await this.#push(message, serialized);\n body = undefined;\n partCount = 0;\n serializedRowsLength = 0;\n }\n };\n\n const addPatch = async (\n patchToVersion: PatchToVersion,\n shared: SharedRowPatch,\n ) => {\n const {patch, toVersion} = patchToVersion;\n if (cmpVersions(toVersion, this.#baseVersion) <= 0) {\n return;\n }\n const target = await ensureBody();\n\n const {type, op} = patch;\n switch (type) {\n case 'query': {\n const patches = patch.clientID\n ? ((target.desiredQueriesPatches ??= {})[patch.clientID] ??= [])\n : (target.gotQueriesPatch ??= []);\n patches.push({op, hash: patch.id});\n break;\n }\n case 'row':\n if (patch.id.table === this.#zeroClientsTable) {\n this.#updateLMIDs((target.lastMutationIDChanges ??= {}), patch);\n } else if (patch.id.table === this.#zeroMutationsTable) {\n const patches = (target.mutationsPatch ??= []);\n if (op === 'put') {\n const row = v.parse(\n ensureSafeJSON(patch.contents),\n mutationRowSchema,\n 'passthrough',\n );\n patches.push({\n op: 'put',\n mutation: {\n id: {\n clientID: row.clientID,\n id: row.mutationID,\n },\n result: row.result,\n },\n });\n } else {\n const {clientID, mutationID} = patch.id.rowKey;\n assert(\n typeof clientID === 'string',\n 'client id must be a string',\n );\n const id = Number(mutationID);\n assert(\n !Number.isNaN(id) && Number.isFinite(id) && id >= 0,\n 'mutation id must be a finite number',\n );\n patches.push({\n op: 'del',\n id: {\n clientID,\n id,\n },\n });\n }\n } else {\n const {rowPatch, json} = (shared.serialized ??=\n serializeRowPatch(patch));\n rowsPatch.push(rowPatch);\n rowsJSON.push(json);\n serializedRowsLength += json.length;\n }\n break;\n default:\n unreachable(patch);\n }\n\n partCount++;\n if (\n serializedRowsLength >= POKE_PART_FLUSH_THRESHOLD_CHARS ||\n partCount >= PART_COUNT_FLUSH_THRESHOLD\n ) {\n await flushBody();\n }\n };\n\n // A poke that was started must always be ended. If it isn't, the\n // connection is left mid-poke and the *next* pokeStart fails the\n // DownstreamSender's in-progress check, surfacing the error far from\n // its cause. Fail the connection instead; the client reconnects and\n // resyncs from its baseVersion.\n return {\n get patchesSent() {\n return pokeStarted;\n },\n addPatch: async (\n patchToVersion: PatchToVersion,\n shared: SharedRowPatch = {},\n ) => {\n try {\n await addPatch(patchToVersion, shared);\n if (patchToVersion.patch.type === 'row') {\n this.#pokedRows.add(1);\n }\n } catch (e) {\n this.fail(e);\n }\n },\n\n cancel: async () => {\n try {\n if (pokeStarted) {\n await this.#push(['pokeEnd', {pokeID, cookie: '', cancel: true}]);\n }\n } catch (e) {\n this.fail(e);\n }\n },\n\n end: async (finalVersion: CVRVersion) => {\n try {\n const cookie = versionToCookie(finalVersion);\n if (!pokeStarted) {\n if (\n cmpVersions(this.#baseVersion, finalVersion) === 0 &&\n !forceInitialPoke\n ) {\n return; // Nothing changed and nothing was sent.\n }\n await this.#push(['pokeStart', pokeStart]);\n } else if (cmpVersions(this.#baseVersion, finalVersion) >= 0) {\n // Sanity check: If the poke was started, the finalVersion\n // must be > #baseVersion.\n throw new Error(\n `Patches were sent but finalVersion ${cookie} is not ` +\n `greater than baseVersion ` +\n `${versionToNullableCookie(this.#baseVersion)}`,\n );\n }\n await flushBody();\n await this.#push(['pokeEnd', {pokeID, cookie}]);\n this.#baseVersion = finalVersion;\n this.#everPoked = true;\n\n const elapsed = performance.now() - start;\n this.#pokeTransactions.add(1);\n this.#pokeTime.recordMs(elapsed);\n } catch (e) {\n this.fail(e);\n }\n },\n };\n }\n\n async sendDeleteClients(\n lc: LogContext,\n deletedClientIDs: string[],\n deletedClientGroupIDs: string[],\n ) {\n const deleteClientsBody: Writable<DeleteClientsBody> = {};\n if (deletedClientIDs.length > 0) {\n deleteClientsBody.clientIDs = deletedClientIDs;\n }\n if (deletedClientGroupIDs.length > 0) {\n deleteClientsBody.clientGroupIDs = deletedClientGroupIDs;\n }\n lc.debug?.('sending deleteClients', deleteClientsBody);\n await this.#push(['deleteClients', deleteClientsBody]);\n }\n\n sendQueryTransformApplicationErrors(errors: ErroredQuery[]) {\n void this.#push(['transformError', errors]);\n }\n\n sendQueryTransformFailedError(error: TransformFailedBody) {\n this.fail(new ProtocolError(error));\n }\n\n sendInspectResponse(lc: LogContext, response: InspectDownBody): void {\n lc.debug?.('sending inspect response', response);\n this.#downstream.push({\n message: ['inspect', response],\n serialized: undefined,\n });\n }\n\n #updateLMIDs(lmids: Record<string, number>, patch: RowPatch) {\n if (patch.op === 'put') {\n const row = ensureSafeJSON(patch.contents);\n const {clientGroupID, clientID, lastMutationID} = v.parse(\n row,\n lmidRowSchema,\n 'passthrough',\n );\n if (clientGroupID !== this.#clientGroupID) {\n this.#lc.error?.(\n `Received clients row for wrong clientGroupID. Ignoring.`,\n clientGroupID,\n );\n } else {\n lmids[clientID] = lastMutationID;\n }\n } else {\n // The 'constrain' and 'del' ops for clients can be ignored.\n patch.op satisfies 'constrain' | 'del';\n }\n }\n}\n\n// Note: The {APP_ID}_{SHARD_ID}.clients table is set up in replicator/initial-sync.ts.\nconst lmidRowSchema = v.object({\n clientGroupID: v.string(),\n clientID: v.string(),\n lastMutationID: v.number(), // Actually returned as a bigint, but converted by ensureSafeJSON().\n});\n\nconst mutationRowSchema = v.object({\n clientGroupID: v.string(),\n clientID: v.string(),\n mutationID: v.number(),\n result: mutationResultSchema,\n});\n\nfunction serializeRowPatch(patch: RowPatch): SerializedRowPatch {\n const rowPatch = makeRowPatch(patch);\n return {rowPatch, json: JSON.stringify(rowPatch)};\n}\n\nfunction makeRowPatch(patch: RowPatch): RowPatchOp {\n const {\n op,\n id: {table: tableName, rowKey: id},\n } = patch;\n\n switch (op) {\n case 'put':\n // ensureSafeJSON() converts bigints and asserts (outside of prod) that\n // the values are JSON, which is all that rowSchema checks.\n return {\n op: 'put',\n tableName,\n value: ensureSafeJSON(patch.contents),\n };\n\n case 'del':\n return {\n op,\n tableName,\n id: v.parse(id, primaryKeyValueRecordSchema),\n };\n\n default:\n unreachable(op);\n }\n}\n\n/**\n * Column values of type INT8 are returned as the `bigint` from the\n * Postgres library. These are converted to `number` if they are within\n * the safe Number range, allowing the protocol to support numbers larger\n * than 32-bits. Values outside of the safe number range (e.g. > 2^53) will\n * result in an Error.\n */\nexport function ensureSafeJSON(row: JSONObject): SafeJSONObject {\n let modified: Record<string, unknown> | undefined;\n for (const k in row) {\n if (!hasOwn(row, k)) {\n continue;\n }\n const v = row[k];\n if (typeof v === 'bigint') {\n if (v < Number.MIN_SAFE_INTEGER || v > Number.MAX_SAFE_INTEGER) {\n throw new Error(`Value of \"${k}\" exceeds safe Number range (${v})`);\n }\n (modified ??= {...row})[k] = Number(v);\n } else if (typeof v === 'object') {\n assertJSONValue(v);\n }\n }\n return (modified ?? row) as SafeJSONObject;\n}\n\n/**\n * The baseCookie comes from the connection URL so it is client input. A\n * malformed one is reported to the client as an invalid baseCookie (which\n * makes it reset its local state) rather than as an internal error.\n */\nfunction parseBaseCookie(baseCookie: string | null): NullableCVRVersion {\n try {\n return cookieToVersion(baseCookie);\n } catch (e) {\n throw new ProtocolError(\n {\n kind: ErrorKind.InvalidConnectionRequestBaseCookie,\n message: `Invalid baseCookie ${JSON.stringify(baseCookie)}: ${getErrorMessage(e)}`,\n origin: ErrorOrigin.ZeroCache,\n },\n {cause: e},\n );\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"connection-context-manager.js","names":["ErrorKind.Unauthorized","ErrorOrigin.ZeroCache","ErrorKind.InvalidConnectionRequest"],"sources":["../../../../../../zero-cache/src/services/view-syncer/connection-context-manager.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {InitConnectionBody} from '../../../../zero-protocol/src/connect.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport type {UpdateAuthBody} from '../../../../zero-protocol/src/update-auth.ts';\nimport {\n authEquals,\n resolveAuth,\n type Auth,\n type ValidateLegacyJWT,\n} from '../../auth/auth.ts';\nimport type {ZeroConfig} from '../../config/zero-config.ts';\nimport {compileUrlPattern} from '../../custom/fetch.ts';\nimport {ProtocolErrorWithLevel} from '../../types/error-with-level.ts';\nimport type {ConnectParams} from '../../workers/connect-params.ts';\n\nexport type ConnectionState = 'provisional' | 'validated';\n\n/**\n * Normalized user identity shared by live connection state and group auth state.\n * `id: null` means logged out.\n */\nexport type UserState = {readonly id: string | null};\n\n/**\n * Delineates the two paths for validating a connection: either server can validate\n * the user's identity and return a definitive userID to trust, or we fall back to\n * trusting the one provided by the client in the incoming query params.\n */\nexport type ConnectionValidation =\n | {kind: 'client-fallback'}\n | {kind: 'server-validated'; validatedUserID: string | null};\n\n/**\n * Identifies one live websocket for a client slot.\n */\nexport type ConnectionSelector = {\n readonly clientID: string;\n readonly wsID: string;\n};\n\ntype FetchConfig = ZeroConfig['query'];\n\nexport type HeaderOptions = {\n readonly apiKey?: string | undefined;\n /** Allowlisted headers provided in the client options. */\n readonly customHeaders?: Readonly<Record<string, string>> | undefined;\n /** Allowlisted headers from the incoming HTTP request. */\n readonly requestHeaders?: Readonly<Record<string, string>> | undefined;\n readonly cookie?: string | undefined;\n readonly origin?: string | undefined;\n};\n\nexport type ConnectionFetchContext = {\n readonly url: string | undefined;\n readonly allowedUrlPatterns: readonly URLPattern[] | undefined;\n readonly headerOptions: HeaderOptions;\n};\n\n/**\n * A snapshot of one live connection tracked by the manager.\n *\n * `revalidateAt` is only populated while the connection is `validated`.\n */\nexport type ConnectionContext = {\n readonly state: ConnectionState;\n\n readonly clientID: string;\n readonly wsID: string;\n readonly user: UserState;\n\n readonly auth: Auth | undefined;\n\n readonly profileID: string | null;\n readonly baseCookie: string | null;\n readonly protocolVersion: number;\n\n readonly revision: number;\n\n readonly revalidateAt: number | undefined;\n\n readonly insertionOrder: number;\n\n readonly queryContext: ConnectionFetchContext;\n readonly mutateContext: ConnectionFetchContext;\n};\n\n/**\n * Group-scoped auth state shared across the live connections.\n *\n * The background connection is the validated connection currently used for\n * shared background work. Retransform happens on a group level, and uses\n * the background connection's credential to refetch the latest queries.\n */\nexport type GroupAuthState = {\n readonly pinnedUser: UserState | undefined;\n\n readonly backgroundConnection: ConnectionSelector | undefined;\n readonly retransformAt: number | undefined;\n // Defer all maintenance in case a transient failure occurs.\n readonly maintenanceNotBeforeAt: number | undefined;\n};\n\nexport type ConnectionContextManager = {\n registerConnection(\n selector: ConnectionSelector,\n connectParams: ConnectParams,\n auth?: Auth,\n ): Readonly<ConnectionContext>;\n\n initConnection(\n selector: ConnectionSelector,\n body: InitConnectionBody,\n ): Readonly<ConnectionContext>;\n\n updateAuth(\n selector: ConnectionSelector,\n body: UpdateAuthBody,\n ): Promise<Readonly<ConnectionContext>>;\n\n validateConnection(\n selector: ConnectionSelector,\n revision: number,\n validation: ConnectionValidation,\n ):\n | Readonly<{\n connection: ConnectionContext;\n group: GroupAuthState;\n }>\n | undefined;\n\n failConnection(\n selector: ConnectionSelector,\n revision: number,\n ): Readonly<ConnectionContext> | undefined;\n closeConnection(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined;\n\n markBackgroundRetransformSuccess(\n selector: ConnectionSelector,\n revision: number,\n ): void;\n\n setSharedRetransformReady(ready: boolean): void;\n\n deferMaintenance(kind: 'revalidate' | 'retransform'): void;\n\n getConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined;\n mustGetConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext>;\n\n getBackgroundConnectionContext(): Readonly<ConnectionContext> | undefined;\n mustGetBackgroundConnectionContext(): Readonly<ConnectionContext>;\n\n getGroupState(): Readonly<GroupAuthState>;\n\n planMaintenance(): {\n dueRevalidations: Readonly<ConnectionContext>[];\n dueRetransform: boolean;\n earliestDeadlineAt: number | undefined;\n };\n};\n\n/**\n * State machine for the auth state of a single `ViewSyncerService`.\n *\n * Connections are registered as `provisional`, optionally backfilled with\n * `initConnection` metadata, and then promoted to `validated` once their\n * effective `userID` is confirmed as valid. The manager also tracks which\n * validated connection currently serves as the group's background connection.\n */\nexport class ConnectionContextManagerImpl implements ConnectionContextManager {\n readonly #lc: LogContext;\n\n // The live connection records, keyed by clientID\n readonly #connections = new Map<string, ConnectionContext>();\n #group: GroupAuthState = {\n pinnedUser: undefined,\n backgroundConnection: undefined,\n retransformAt: undefined,\n maintenanceNotBeforeAt: undefined,\n };\n\n readonly #validateLegacyJWT: ValidateLegacyJWT | undefined;\n\n readonly #now: () => number;\n readonly #revalidateIntervalMs: number | undefined;\n readonly #retransformIntervalMs: number | undefined;\n readonly #queryConfig: FetchConfig | undefined;\n readonly #pushConfig: FetchConfig | undefined;\n #sharedRetransformReady = false;\n #nextInsertionOrder = 0;\n\n constructor(\n lc: LogContext,\n revalidateIntervalSeconds?: number,\n retransformIntervalSeconds?: number,\n queryConfig?: FetchConfig,\n pushConfig?: FetchConfig,\n validateLegacyJWT?: ValidateLegacyJWT,\n now?: () => number,\n ) {\n this.#lc = lc;\n this.#now = now ?? Date.now;\n this.#revalidateIntervalMs =\n revalidateIntervalSeconds === undefined\n ? undefined\n : revalidateIntervalSeconds * 1000;\n this.#retransformIntervalMs =\n retransformIntervalSeconds === undefined\n ? undefined\n : retransformIntervalSeconds * 1000;\n this.#queryConfig = queryConfig;\n this.#pushConfig = pushConfig;\n this.#validateLegacyJWT = validateLegacyJWT;\n }\n\n /**\n * Creates or replaces the live record for a websocket connection.\n *\n * Re-registering the same `clientID` drops the old socket record and starts\n * the replacement back in `provisional` state.\n */\n registerConnection(\n selector: ConnectionSelector,\n connectParams: ConnectParams,\n auth?: Auth,\n ): Readonly<ConnectionContext> {\n this.#removeConnection(selector);\n\n const getContext = (type: 'query' | 'mutate'): ConnectionFetchContext => {\n const config = type === 'query' ? this.#queryConfig : this.#pushConfig;\n\n return {\n url: config?.url?.[0],\n allowedUrlPatterns: config?.url?.map(compileUrlPattern),\n headerOptions: {\n customHeaders: undefined,\n requestHeaders: filterHeaders(\n connectParams.requestHeaders,\n config?.allowedRequestHeaders,\n ),\n origin: connectParams.origin,\n apiKey: config?.apiKey,\n cookie: config?.forwardCookies ? connectParams.httpCookie : undefined,\n },\n };\n };\n\n const connection: ConnectionContext = {\n state: 'provisional',\n\n clientID: connectParams.clientID,\n wsID: connectParams.wsID,\n revision: 0,\n user: {id: connectParams.userID ?? null},\n auth,\n\n profileID: connectParams.profileID,\n baseCookie: connectParams.baseCookie,\n protocolVersion: connectParams.protocolVersion,\n\n revalidateAt: undefined,\n\n queryContext: getContext('query'),\n mutateContext: getContext('mutate'),\n\n insertionOrder: ++this.#nextInsertionOrder,\n };\n this.#storeConnection(connection);\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n return connection;\n }\n\n /**\n * Backfills `initConnection` data for sockets that were registered before the\n * client could send its full init payload.\n *\n * This updates metadata only; it does not validate the connection.\n */\n initConnection(\n selector: ConnectionSelector,\n body: InitConnectionBody,\n ): Readonly<ConnectionContext> {\n const connection = this.#mustGetConnectionContext(selector);\n\n let queryContext = connection.queryContext;\n let mutateContext = connection.mutateContext;\n\n if (body.userQueryURL) {\n queryContext = {\n ...queryContext,\n url: body.userQueryURL,\n };\n }\n if (body.userQueryHeaders) {\n queryContext = {\n ...queryContext,\n headerOptions: {\n ...queryContext.headerOptions,\n customHeaders: filterHeaders(\n body.userQueryHeaders,\n this.#queryConfig?.allowedClientHeaders,\n ),\n },\n };\n }\n if (body.userPushURL) {\n mutateContext = {\n ...mutateContext,\n url: body.userPushURL,\n };\n }\n if (body.userPushHeaders) {\n mutateContext = {\n ...mutateContext,\n headerOptions: {\n ...mutateContext.headerOptions,\n customHeaders: filterHeaders(\n body.userPushHeaders,\n this.#pushConfig?.allowedClientHeaders,\n ),\n },\n };\n }\n\n return this.#demoteConnection({\n ...connection,\n revision: connection.revision + 1,\n queryContext,\n mutateContext,\n });\n }\n\n /**\n * A material auth change demotes the connection back to provisional until it\n * is validated again.\n */\n async updateAuth(\n selector: ConnectionSelector,\n body: UpdateAuthBody,\n ): Promise<Readonly<ConnectionContext>> {\n const connection = this.#mustGetConnectionContext(selector);\n\n const nextAuth = await resolveAuth(\n this.#lc,\n connection.auth,\n connection.user.id,\n body.auth,\n this.#validateLegacyJWT,\n );\n\n const authChanged = !authEquals(connection.auth, nextAuth);\n if (authChanged) {\n return this.#demoteConnection({\n ...connection,\n auth: nextAuth,\n revision: connection.revision + 1,\n });\n }\n\n if (nextAuth === connection.auth) {\n return connection;\n }\n\n return this.#storeConnection({\n ...connection,\n auth: nextAuth,\n });\n }\n\n /**\n * Validates one connection against the group's pinned `userID`.\n *\n * The first successful validation binds the group `userID`. Later\n * validations must match it. Validation also refreshes the connection's\n * revalidation deadline and may pick the connection as the group\n * background connection if none is currently available. If the websocket is\n * gone by the time async validation finishes, this becomes a no-op.\n */\n validateConnection(\n selector: ConnectionSelector,\n revision: number,\n validation: ConnectionValidation,\n ):\n | Readonly<{\n connection: ConnectionContext;\n group: GroupAuthState;\n }>\n | undefined {\n const connection = this.#getConnectionContext(selector);\n if (!connection) {\n return undefined;\n }\n\n if (connection.revision !== revision) {\n this.#lc.debug?.('Skipping validateConnection for stale revision', {\n clientID: selector.clientID,\n attemptedRevision: revision,\n currentRevision: connection.revision,\n });\n return undefined;\n }\n\n let validatedUserState: UserState | undefined;\n\n // If the API server has validated the user's identity, we ensure that\n // the connection's claimed userID matches it.\n if (validation.kind === 'server-validated') {\n validatedUserState = {id: validation.validatedUserID};\n\n // Check that the ws connection userID provided by the client\n // matches the validated userID from the API server.\n if (connection.user.id !== validatedUserState.id) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Unauthorized,\n message:\n 'Connection userID does not match validated server userID.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n }\n\n // The incoming user state is either the validated user state from the server\n // or the WS client's claimed user state if no server validation occurred.\n const incomingUserState = validatedUserState ?? connection.user;\n\n // Once a client group is validated, every later validated connection must\n // agree with that pinned identity.\n if (\n this.#group.pinnedUser !== undefined &&\n this.#group.pinnedUser.id !== incomingUserState.id\n ) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Unauthorized,\n message:\n 'Client groups are pinned to a single userID. Connection userID does not match existing client group userID.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n\n if (this.#group.pinnedUser === undefined) {\n this.#setGroup({\n ...this.#group,\n pinnedUser: incomingUserState,\n });\n }\n\n const validatedConnection = this.#storeConnection({\n ...connection,\n state: 'validated',\n revalidateAt: this.#nextRevalidateAt(),\n });\n this.#refreshBackgroundConnectionContext(validatedConnection);\n this.#updateBackgroundRetransformDeadline(false);\n\n return {\n connection: validatedConnection,\n group: this.getGroupState(),\n };\n }\n\n /** Removes one connection due to failed auth and updates all derived background/deadline state. */\n failConnection(\n selector: ConnectionSelector,\n revision: number,\n ): ConnectionContext | undefined {\n return this.#removeConnection(selector, revision);\n }\n\n /** Removes one disconnected connection and updates all derived background/deadline state. */\n closeConnection(selector: ConnectionSelector): ConnectionContext | undefined {\n return this.#removeConnection(selector);\n }\n\n /**\n * Records a successful background retransform. This starts a fresh interval\n * from the manager clock when shared retransform is schedulable, or\n * clears the deadline if it is not.\n */\n markBackgroundRetransformSuccess(\n selector: ConnectionSelector,\n revision: number,\n ): void {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (!backgroundConnection) {\n return;\n }\n if (\n backgroundConnection.clientID !== selector.clientID ||\n backgroundConnection.wsID !== selector.wsID ||\n backgroundConnection.revision !== revision\n ) {\n return;\n }\n this.#updateBackgroundRetransformDeadline(true);\n }\n\n setSharedRetransformReady(ready: boolean): void {\n if (this.#sharedRetransformReady === ready) {\n return;\n }\n this.#sharedRetransformReady = ready;\n this.#updateBackgroundRetransformDeadline(true);\n }\n\n deferMaintenance(kind: 'revalidate' | 'retransform'): void {\n const intervalMs =\n kind === 'revalidate'\n ? this.#revalidateIntervalMs\n : this.#retransformIntervalMs;\n if (intervalMs === undefined) {\n return;\n }\n this.#setGroup({\n ...this.#group,\n maintenanceNotBeforeAt: Math.max(\n this.#group.maintenanceNotBeforeAt ?? 0,\n this.#now() + intervalMs,\n ),\n });\n }\n\n /** Returns the current live record for a client slot, if any. */\n getConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined {\n return this.#getConnectionContext(selector);\n }\n\n /** Returns the live record for one websocket or throws if it is unavailable. */\n mustGetConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> {\n return this.#mustGetConnectionContext(selector);\n }\n\n /** Returns the current background connection, if one exists. */\n getBackgroundConnectionContext(): Readonly<ConnectionContext> | undefined {\n return this.#getBackgroundConnectionContext();\n }\n\n mustGetBackgroundConnectionContext(): Readonly<ConnectionContext> {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (!backgroundConnection) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.InvalidConnectionRequest,\n message:\n 'No validated connection is available for shared query work.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n return backgroundConnection;\n }\n\n /** Returns the shared group auth state. */\n getGroupState(): Readonly<GroupAuthState> {\n return this.#group;\n }\n\n /**\n * Reports which maintenance work is currently due.\n *\n * The result is a pure snapshot: callers decide which actions to run and\n * when to wake up next. `earliestDeadlineAt` is the earliest outstanding\n * maintenance deadline, including overdue work, unless a transient failure\n * has deferred all scheduled maintenance until `maintenanceNotBeforeAt`.\n */\n planMaintenance(): {\n dueRevalidations: Readonly<ConnectionContext>[];\n dueRetransform: boolean;\n earliestDeadlineAt: number | undefined;\n } {\n const dueRevalidations: Readonly<ConnectionContext>[] = [];\n const now = this.#now();\n let earliestDeadlineAt = this.#group.retransformAt;\n\n for (const connection of this.#connections.values()) {\n if (\n connection.state !== 'validated' ||\n connection.revalidateAt === undefined\n ) {\n continue;\n }\n if (connection.revalidateAt <= now) {\n dueRevalidations.push(connection);\n }\n earliestDeadlineAt = minDefined(\n earliestDeadlineAt,\n connection.revalidateAt,\n );\n }\n\n const dueRetransform =\n this.#group.retransformAt !== undefined &&\n this.#group.retransformAt <= now;\n const maintenanceNotBeforeAt = this.#group.maintenanceNotBeforeAt;\n\n if (\n maintenanceNotBeforeAt !== undefined &&\n maintenanceNotBeforeAt > now &&\n earliestDeadlineAt !== undefined\n ) {\n return {\n dueRevalidations: [],\n dueRetransform: false,\n earliestDeadlineAt: Math.max(\n earliestDeadlineAt,\n maintenanceNotBeforeAt,\n ),\n };\n }\n\n return {\n dueRevalidations: dueRevalidations.sort(compareByInsertionOrder),\n dueRetransform,\n earliestDeadlineAt,\n };\n }\n\n #removeConnection(\n selector: ConnectionSelector,\n revision?: number,\n ): Readonly<ConnectionContext> | undefined {\n const connection = this.#getConnectionContext(selector);\n\n if (!connection) {\n return undefined;\n }\n\n // If the revision has changed, we should not remove the connection\n if (revision !== undefined && connection.revision !== revision) {\n this.#lc.debug?.('Ignoring failConnection for stale revision', {\n clientID: selector.clientID,\n wsID: selector.wsID,\n attemptedRevision: revision,\n currentRevision: connection.revision,\n });\n return undefined;\n }\n\n this.#connections.delete(connection.clientID);\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n\n return connection;\n }\n\n #demoteConnection(connection: ConnectionContext): ConnectionContext {\n const demotedConnection = this.#storeConnection({\n ...connection,\n state: 'provisional',\n revalidateAt: undefined,\n });\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n return demotedConnection;\n }\n\n /**\n * Keeps the background connection sticky while it remains validated.\n *\n * When a newly validated `preferred` connection is provided, it is promoted\n * only if there is no current validated background connection. Otherwise the\n * existing background connection stays in place until it disappears or is\n * demoted, at which point the newest validated connection is selected.\n */\n #refreshBackgroundConnectionContext(preferred?: ConnectionContext): void {\n if (preferred?.state === 'validated') {\n const currentBackgroundConnection =\n this.#getBackgroundConnectionContext();\n if (\n currentBackgroundConnection?.clientID === preferred.clientID &&\n currentBackgroundConnection.wsID === preferred.wsID\n ) {\n return;\n }\n if (currentBackgroundConnection !== undefined) {\n return;\n }\n this.#setBackgroundConnection({\n clientID: preferred.clientID,\n wsID: preferred.wsID,\n });\n this.#lc.debug?.('Selected background connection for shared auth work', {\n clientID: preferred.clientID,\n wsID: preferred.wsID,\n revision: preferred.revision,\n reason: 'preferred-validated',\n });\n return;\n }\n\n const currentBackgroundConnection = this.#getBackgroundConnectionContext();\n if (currentBackgroundConnection?.state === 'validated') {\n return;\n }\n\n const nextBackgroundConnection = [...this.#connections.values()]\n .filter(connection => connection.state === 'validated')\n .sort(comparePreferredValidatedConnection)\n .at(0);\n this.#setBackgroundConnection(\n nextBackgroundConnection\n ? {\n clientID: nextBackgroundConnection.clientID,\n wsID: nextBackgroundConnection.wsID,\n }\n : undefined,\n );\n if (nextBackgroundConnection) {\n this.#lc.debug?.('Selected background connection for shared auth work', {\n clientID: nextBackgroundConnection.clientID,\n wsID: nextBackgroundConnection.wsID,\n revision: nextBackgroundConnection.revision,\n reason: 'fallback-validated',\n });\n }\n }\n\n #getBackgroundConnectionContext(): ConnectionContext | undefined {\n const backgroundConnection = this.#group.backgroundConnection;\n if (!backgroundConnection) {\n return undefined;\n }\n return this.#getConnectionContext(backgroundConnection);\n }\n\n #getConnectionContext(\n selector: ConnectionSelector,\n ): ConnectionContext | undefined {\n const connection = this.#connections.get(selector.clientID);\n if (!connection) {\n return undefined;\n }\n if (connection.wsID !== selector.wsID) {\n return undefined;\n }\n return connection;\n }\n\n #mustGetConnectionContext(selector: ConnectionSelector): ConnectionContext {\n const connection = this.#getConnectionContext(selector);\n\n if (!connection) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.InvalidConnectionRequest,\n message:\n 'Connection auth state was not available for this websocket.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n\n return connection;\n }\n\n #storeConnection(connection: ConnectionContext): ConnectionContext {\n this.#connections.set(connection.clientID, connection);\n return connection;\n }\n\n #setGroup(group: GroupAuthState): GroupAuthState {\n this.#group = group;\n return group;\n }\n\n #setBackgroundConnection(\n backgroundConnection: ConnectionSelector | undefined,\n ) {\n if (\n sameConnectionSelector(\n this.#group.backgroundConnection,\n backgroundConnection,\n )\n ) {\n return;\n }\n this.#setGroup({\n ...this.#group,\n backgroundConnection: backgroundConnection\n ? {...backgroundConnection}\n : undefined,\n });\n }\n\n /**\n * Keeps the group background retransform deadline coherent with current\n * schedulability.\n *\n * When `reset` is false, this seeds a deadline only when shared retransform\n * is now possible and no deadline exists yet, preserving any existing\n * cadence. When `reset` is true, it starts a fresh interval from `#now()` if\n * retransform is schedulable for the current ready ViewSyncer instance, or\n * clears the deadline if it is not.\n */\n #updateBackgroundRetransformDeadline(reset: boolean) {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (\n !backgroundConnection ||\n this.#retransformIntervalMs === undefined ||\n !this.#sharedRetransformReady\n ) {\n if (this.#group.retransformAt !== undefined) {\n this.#setGroup({\n ...this.#group,\n retransformAt: undefined,\n });\n }\n return;\n }\n\n if (reset || this.#group.retransformAt === undefined) {\n this.#setGroup({\n ...this.#group,\n retransformAt: this.#now() + this.#retransformIntervalMs,\n });\n }\n }\n\n #nextRevalidateAt() {\n return this.#revalidateIntervalMs === undefined\n ? undefined\n : this.#now() + this.#revalidateIntervalMs;\n }\n}\n\nfunction compareByInsertionOrder(\n a: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n b: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n) {\n return a.insertionOrder - b.insertionOrder || a.wsID.localeCompare(b.wsID);\n}\n\nfunction comparePreferredValidatedConnection(\n a: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n b: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n) {\n return b.insertionOrder - a.insertionOrder || b.wsID.localeCompare(a.wsID);\n}\n\nfunction minDefined(a: number | undefined, b: number | undefined) {\n if (a === undefined) {\n return b;\n }\n if (b === undefined) {\n return a;\n }\n return Math.min(a, b);\n}\n\nfunction sameConnectionSelector(\n a: ConnectionSelector | undefined,\n b: ConnectionSelector | undefined,\n) {\n return a?.clientID === b?.clientID && a?.wsID === b?.wsID;\n}\n\nfunction filterHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n allowedHeaders: readonly string[] | undefined,\n) {\n if (!headers || !allowedHeaders || allowedHeaders.length === 0) {\n return undefined;\n }\n\n const allowed = new Set(allowedHeaders.map(header => header.toLowerCase()));\n let filtered: Record<string, string> | undefined;\n for (const [key, value] of Object.entries(headers)) {\n if (allowed.has(key.toLowerCase())) {\n filtered ??= {};\n filtered[key] = value;\n }\n }\n return filtered;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"connection-context-manager.js","names":["ErrorKind.Unauthorized","ErrorOrigin.ZeroCache","ErrorKind.InvalidConnectionRequest"],"sources":["../../../../../../zero-cache/src/services/view-syncer/connection-context-manager.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {InitConnectionBody} from '../../../../zero-protocol/src/connect.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport type {UpdateAuthBody} from '../../../../zero-protocol/src/update-auth.ts';\nimport {\n authEquals,\n resolveAuth,\n type Auth,\n type ValidateLegacyJWT,\n} from '../../auth/auth.ts';\nimport type {ZeroConfig} from '../../config/zero-config.ts';\nimport {compileUrlPattern} from '../../custom/fetch.ts';\nimport {ProtocolErrorWithLevel} from '../../types/error-with-level.ts';\nimport type {ConnectParams} from '../../workers/connect-params.ts';\n\ntype ConnectionState = 'provisional' | 'validated';\n\n/**\n * Normalized user identity shared by live connection state and group auth state.\n * `id: null` means logged out.\n */\ntype UserState = {readonly id: string | null};\n\n/**\n * Delineates the two paths for validating a connection: either server can validate\n * the user's identity and return a definitive userID to trust, or we fall back to\n * trusting the one provided by the client in the incoming query params.\n */\nexport type ConnectionValidation =\n | {kind: 'client-fallback'}\n | {kind: 'server-validated'; validatedUserID: string | null};\n\n/**\n * Identifies one live websocket for a client slot.\n */\nexport type ConnectionSelector = {\n readonly clientID: string;\n readonly wsID: string;\n};\n\ntype FetchConfig = ZeroConfig['query'];\n\nexport type HeaderOptions = {\n readonly apiKey?: string | undefined;\n /** Allowlisted headers provided in the client options. */\n readonly customHeaders?: Readonly<Record<string, string>> | undefined;\n /** Allowlisted headers from the incoming HTTP request. */\n readonly requestHeaders?: Readonly<Record<string, string>> | undefined;\n readonly cookie?: string | undefined;\n readonly origin?: string | undefined;\n};\n\ntype ConnectionFetchContext = {\n readonly url: string | undefined;\n readonly allowedUrlPatterns: readonly URLPattern[] | undefined;\n readonly headerOptions: HeaderOptions;\n};\n\n/**\n * A snapshot of one live connection tracked by the manager.\n *\n * `revalidateAt` is only populated while the connection is `validated`.\n */\nexport type ConnectionContext = {\n readonly state: ConnectionState;\n\n readonly clientID: string;\n readonly wsID: string;\n readonly user: UserState;\n\n readonly auth: Auth | undefined;\n\n readonly profileID: string | null;\n readonly baseCookie: string | null;\n readonly protocolVersion: number;\n\n readonly revision: number;\n\n readonly revalidateAt: number | undefined;\n\n readonly insertionOrder: number;\n\n readonly queryContext: ConnectionFetchContext;\n readonly mutateContext: ConnectionFetchContext;\n};\n\n/**\n * Group-scoped auth state shared across the live connections.\n *\n * The background connection is the validated connection currently used for\n * shared background work. Retransform happens on a group level, and uses\n * the background connection's credential to refetch the latest queries.\n */\nexport type GroupAuthState = {\n readonly pinnedUser: UserState | undefined;\n\n readonly backgroundConnection: ConnectionSelector | undefined;\n readonly retransformAt: number | undefined;\n // Defer all maintenance in case a transient failure occurs.\n readonly maintenanceNotBeforeAt: number | undefined;\n};\n\nexport type ConnectionContextManager = {\n registerConnection(\n selector: ConnectionSelector,\n connectParams: ConnectParams,\n auth?: Auth,\n ): Readonly<ConnectionContext>;\n\n initConnection(\n selector: ConnectionSelector,\n body: InitConnectionBody,\n ): Readonly<ConnectionContext>;\n\n updateAuth(\n selector: ConnectionSelector,\n body: UpdateAuthBody,\n ): Promise<Readonly<ConnectionContext>>;\n\n validateConnection(\n selector: ConnectionSelector,\n revision: number,\n validation: ConnectionValidation,\n ):\n | Readonly<{\n connection: ConnectionContext;\n group: GroupAuthState;\n }>\n | undefined;\n\n failConnection(\n selector: ConnectionSelector,\n revision: number,\n ): Readonly<ConnectionContext> | undefined;\n closeConnection(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined;\n\n markBackgroundRetransformSuccess(\n selector: ConnectionSelector,\n revision: number,\n ): void;\n\n setSharedRetransformReady(ready: boolean): void;\n\n deferMaintenance(kind: 'revalidate' | 'retransform'): void;\n\n getConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined;\n mustGetConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext>;\n\n getBackgroundConnectionContext(): Readonly<ConnectionContext> | undefined;\n mustGetBackgroundConnectionContext(): Readonly<ConnectionContext>;\n\n getGroupState(): Readonly<GroupAuthState>;\n\n planMaintenance(): {\n dueRevalidations: Readonly<ConnectionContext>[];\n dueRetransform: boolean;\n earliestDeadlineAt: number | undefined;\n };\n};\n\n/**\n * State machine for the auth state of a single `ViewSyncerService`.\n *\n * Connections are registered as `provisional`, optionally backfilled with\n * `initConnection` metadata, and then promoted to `validated` once their\n * effective `userID` is confirmed as valid. The manager also tracks which\n * validated connection currently serves as the group's background connection.\n */\nexport class ConnectionContextManagerImpl implements ConnectionContextManager {\n readonly #lc: LogContext;\n\n // The live connection records, keyed by clientID\n readonly #connections = new Map<string, ConnectionContext>();\n #group: GroupAuthState = {\n pinnedUser: undefined,\n backgroundConnection: undefined,\n retransformAt: undefined,\n maintenanceNotBeforeAt: undefined,\n };\n\n readonly #validateLegacyJWT: ValidateLegacyJWT | undefined;\n\n readonly #now: () => number;\n readonly #revalidateIntervalMs: number | undefined;\n readonly #retransformIntervalMs: number | undefined;\n readonly #queryConfig: FetchConfig | undefined;\n readonly #pushConfig: FetchConfig | undefined;\n #sharedRetransformReady = false;\n #nextInsertionOrder = 0;\n\n constructor(\n lc: LogContext,\n revalidateIntervalSeconds?: number,\n retransformIntervalSeconds?: number,\n queryConfig?: FetchConfig,\n pushConfig?: FetchConfig,\n validateLegacyJWT?: ValidateLegacyJWT,\n now?: () => number,\n ) {\n this.#lc = lc;\n this.#now = now ?? Date.now;\n this.#revalidateIntervalMs =\n revalidateIntervalSeconds === undefined\n ? undefined\n : revalidateIntervalSeconds * 1000;\n this.#retransformIntervalMs =\n retransformIntervalSeconds === undefined\n ? undefined\n : retransformIntervalSeconds * 1000;\n this.#queryConfig = queryConfig;\n this.#pushConfig = pushConfig;\n this.#validateLegacyJWT = validateLegacyJWT;\n }\n\n /**\n * Creates or replaces the live record for a websocket connection.\n *\n * Re-registering the same `clientID` drops the old socket record and starts\n * the replacement back in `provisional` state.\n */\n registerConnection(\n selector: ConnectionSelector,\n connectParams: ConnectParams,\n auth?: Auth,\n ): Readonly<ConnectionContext> {\n this.#removeConnection(selector);\n\n const getContext = (type: 'query' | 'mutate'): ConnectionFetchContext => {\n const config = type === 'query' ? this.#queryConfig : this.#pushConfig;\n\n return {\n url: config?.url?.[0],\n allowedUrlPatterns: config?.url?.map(compileUrlPattern),\n headerOptions: {\n customHeaders: undefined,\n requestHeaders: filterHeaders(\n connectParams.requestHeaders,\n config?.allowedRequestHeaders,\n ),\n origin: connectParams.origin,\n apiKey: config?.apiKey,\n cookie: config?.forwardCookies ? connectParams.httpCookie : undefined,\n },\n };\n };\n\n const connection: ConnectionContext = {\n state: 'provisional',\n\n clientID: connectParams.clientID,\n wsID: connectParams.wsID,\n revision: 0,\n user: {id: connectParams.userID ?? null},\n auth,\n\n profileID: connectParams.profileID,\n baseCookie: connectParams.baseCookie,\n protocolVersion: connectParams.protocolVersion,\n\n revalidateAt: undefined,\n\n queryContext: getContext('query'),\n mutateContext: getContext('mutate'),\n\n insertionOrder: ++this.#nextInsertionOrder,\n };\n this.#storeConnection(connection);\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n return connection;\n }\n\n /**\n * Backfills `initConnection` data for sockets that were registered before the\n * client could send its full init payload.\n *\n * This updates metadata only; it does not validate the connection.\n */\n initConnection(\n selector: ConnectionSelector,\n body: InitConnectionBody,\n ): Readonly<ConnectionContext> {\n const connection = this.#mustGetConnectionContext(selector);\n\n let queryContext = connection.queryContext;\n let mutateContext = connection.mutateContext;\n\n if (body.userQueryURL) {\n queryContext = {\n ...queryContext,\n url: body.userQueryURL,\n };\n }\n if (body.userQueryHeaders) {\n queryContext = {\n ...queryContext,\n headerOptions: {\n ...queryContext.headerOptions,\n customHeaders: filterHeaders(\n body.userQueryHeaders,\n this.#queryConfig?.allowedClientHeaders,\n ),\n },\n };\n }\n if (body.userPushURL) {\n mutateContext = {\n ...mutateContext,\n url: body.userPushURL,\n };\n }\n if (body.userPushHeaders) {\n mutateContext = {\n ...mutateContext,\n headerOptions: {\n ...mutateContext.headerOptions,\n customHeaders: filterHeaders(\n body.userPushHeaders,\n this.#pushConfig?.allowedClientHeaders,\n ),\n },\n };\n }\n\n return this.#demoteConnection({\n ...connection,\n revision: connection.revision + 1,\n queryContext,\n mutateContext,\n });\n }\n\n /**\n * A material auth change demotes the connection back to provisional until it\n * is validated again.\n */\n async updateAuth(\n selector: ConnectionSelector,\n body: UpdateAuthBody,\n ): Promise<Readonly<ConnectionContext>> {\n const connection = this.#mustGetConnectionContext(selector);\n\n const nextAuth = await resolveAuth(\n this.#lc,\n connection.auth,\n connection.user.id,\n body.auth,\n this.#validateLegacyJWT,\n );\n\n const authChanged = !authEquals(connection.auth, nextAuth);\n if (authChanged) {\n return this.#demoteConnection({\n ...connection,\n auth: nextAuth,\n revision: connection.revision + 1,\n });\n }\n\n if (nextAuth === connection.auth) {\n return connection;\n }\n\n return this.#storeConnection({\n ...connection,\n auth: nextAuth,\n });\n }\n\n /**\n * Validates one connection against the group's pinned `userID`.\n *\n * The first successful validation binds the group `userID`. Later\n * validations must match it. Validation also refreshes the connection's\n * revalidation deadline and may pick the connection as the group\n * background connection if none is currently available. If the websocket is\n * gone by the time async validation finishes, this becomes a no-op.\n */\n validateConnection(\n selector: ConnectionSelector,\n revision: number,\n validation: ConnectionValidation,\n ):\n | Readonly<{\n connection: ConnectionContext;\n group: GroupAuthState;\n }>\n | undefined {\n const connection = this.#getConnectionContext(selector);\n if (!connection) {\n return undefined;\n }\n\n if (connection.revision !== revision) {\n this.#lc.debug?.('Skipping validateConnection for stale revision', {\n clientID: selector.clientID,\n attemptedRevision: revision,\n currentRevision: connection.revision,\n });\n return undefined;\n }\n\n let validatedUserState: UserState | undefined;\n\n // If the API server has validated the user's identity, we ensure that\n // the connection's claimed userID matches it.\n if (validation.kind === 'server-validated') {\n validatedUserState = {id: validation.validatedUserID};\n\n // Check that the ws connection userID provided by the client\n // matches the validated userID from the API server.\n if (connection.user.id !== validatedUserState.id) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Unauthorized,\n message:\n 'Connection userID does not match validated server userID.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n }\n\n // The incoming user state is either the validated user state from the server\n // or the WS client's claimed user state if no server validation occurred.\n const incomingUserState = validatedUserState ?? connection.user;\n\n // Once a client group is validated, every later validated connection must\n // agree with that pinned identity.\n if (\n this.#group.pinnedUser !== undefined &&\n this.#group.pinnedUser.id !== incomingUserState.id\n ) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Unauthorized,\n message:\n 'Client groups are pinned to a single userID. Connection userID does not match existing client group userID.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n\n if (this.#group.pinnedUser === undefined) {\n this.#setGroup({\n ...this.#group,\n pinnedUser: incomingUserState,\n });\n }\n\n const validatedConnection = this.#storeConnection({\n ...connection,\n state: 'validated',\n revalidateAt: this.#nextRevalidateAt(),\n });\n this.#refreshBackgroundConnectionContext(validatedConnection);\n this.#updateBackgroundRetransformDeadline(false);\n\n return {\n connection: validatedConnection,\n group: this.getGroupState(),\n };\n }\n\n /** Removes one connection due to failed auth and updates all derived background/deadline state. */\n failConnection(\n selector: ConnectionSelector,\n revision: number,\n ): ConnectionContext | undefined {\n return this.#removeConnection(selector, revision);\n }\n\n /** Removes one disconnected connection and updates all derived background/deadline state. */\n closeConnection(selector: ConnectionSelector): ConnectionContext | undefined {\n return this.#removeConnection(selector);\n }\n\n /**\n * Records a successful background retransform. This starts a fresh interval\n * from the manager clock when shared retransform is schedulable, or\n * clears the deadline if it is not.\n */\n markBackgroundRetransformSuccess(\n selector: ConnectionSelector,\n revision: number,\n ): void {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (!backgroundConnection) {\n return;\n }\n if (\n backgroundConnection.clientID !== selector.clientID ||\n backgroundConnection.wsID !== selector.wsID ||\n backgroundConnection.revision !== revision\n ) {\n return;\n }\n this.#updateBackgroundRetransformDeadline(true);\n }\n\n setSharedRetransformReady(ready: boolean): void {\n if (this.#sharedRetransformReady === ready) {\n return;\n }\n this.#sharedRetransformReady = ready;\n this.#updateBackgroundRetransformDeadline(true);\n }\n\n deferMaintenance(kind: 'revalidate' | 'retransform'): void {\n const intervalMs =\n kind === 'revalidate'\n ? this.#revalidateIntervalMs\n : this.#retransformIntervalMs;\n if (intervalMs === undefined) {\n return;\n }\n this.#setGroup({\n ...this.#group,\n maintenanceNotBeforeAt: Math.max(\n this.#group.maintenanceNotBeforeAt ?? 0,\n this.#now() + intervalMs,\n ),\n });\n }\n\n /** Returns the current live record for a client slot, if any. */\n getConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined {\n return this.#getConnectionContext(selector);\n }\n\n /** Returns the live record for one websocket or throws if it is unavailable. */\n mustGetConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> {\n return this.#mustGetConnectionContext(selector);\n }\n\n /** Returns the current background connection, if one exists. */\n getBackgroundConnectionContext(): Readonly<ConnectionContext> | undefined {\n return this.#getBackgroundConnectionContext();\n }\n\n mustGetBackgroundConnectionContext(): Readonly<ConnectionContext> {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (!backgroundConnection) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.InvalidConnectionRequest,\n message:\n 'No validated connection is available for shared query work.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n return backgroundConnection;\n }\n\n /** Returns the shared group auth state. */\n getGroupState(): Readonly<GroupAuthState> {\n return this.#group;\n }\n\n /**\n * Reports which maintenance work is currently due.\n *\n * The result is a pure snapshot: callers decide which actions to run and\n * when to wake up next. `earliestDeadlineAt` is the earliest outstanding\n * maintenance deadline, including overdue work, unless a transient failure\n * has deferred all scheduled maintenance until `maintenanceNotBeforeAt`.\n */\n planMaintenance(): {\n dueRevalidations: Readonly<ConnectionContext>[];\n dueRetransform: boolean;\n earliestDeadlineAt: number | undefined;\n } {\n const dueRevalidations: Readonly<ConnectionContext>[] = [];\n const now = this.#now();\n let earliestDeadlineAt = this.#group.retransformAt;\n\n for (const connection of this.#connections.values()) {\n if (\n connection.state !== 'validated' ||\n connection.revalidateAt === undefined\n ) {\n continue;\n }\n if (connection.revalidateAt <= now) {\n dueRevalidations.push(connection);\n }\n earliestDeadlineAt = minDefined(\n earliestDeadlineAt,\n connection.revalidateAt,\n );\n }\n\n const dueRetransform =\n this.#group.retransformAt !== undefined &&\n this.#group.retransformAt <= now;\n const maintenanceNotBeforeAt = this.#group.maintenanceNotBeforeAt;\n\n if (\n maintenanceNotBeforeAt !== undefined &&\n maintenanceNotBeforeAt > now &&\n earliestDeadlineAt !== undefined\n ) {\n return {\n dueRevalidations: [],\n dueRetransform: false,\n earliestDeadlineAt: Math.max(\n earliestDeadlineAt,\n maintenanceNotBeforeAt,\n ),\n };\n }\n\n return {\n dueRevalidations: dueRevalidations.sort(compareByInsertionOrder),\n dueRetransform,\n earliestDeadlineAt,\n };\n }\n\n #removeConnection(\n selector: ConnectionSelector,\n revision?: number,\n ): Readonly<ConnectionContext> | undefined {\n const connection = this.#getConnectionContext(selector);\n\n if (!connection) {\n return undefined;\n }\n\n // If the revision has changed, we should not remove the connection\n if (revision !== undefined && connection.revision !== revision) {\n this.#lc.debug?.('Ignoring failConnection for stale revision', {\n clientID: selector.clientID,\n wsID: selector.wsID,\n attemptedRevision: revision,\n currentRevision: connection.revision,\n });\n return undefined;\n }\n\n this.#connections.delete(connection.clientID);\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n\n return connection;\n }\n\n #demoteConnection(connection: ConnectionContext): ConnectionContext {\n const demotedConnection = this.#storeConnection({\n ...connection,\n state: 'provisional',\n revalidateAt: undefined,\n });\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n return demotedConnection;\n }\n\n /**\n * Keeps the background connection sticky while it remains validated.\n *\n * When a newly validated `preferred` connection is provided, it is promoted\n * only if there is no current validated background connection. Otherwise the\n * existing background connection stays in place until it disappears or is\n * demoted, at which point the newest validated connection is selected.\n */\n #refreshBackgroundConnectionContext(preferred?: ConnectionContext): void {\n if (preferred?.state === 'validated') {\n const currentBackgroundConnection =\n this.#getBackgroundConnectionContext();\n if (\n currentBackgroundConnection?.clientID === preferred.clientID &&\n currentBackgroundConnection.wsID === preferred.wsID\n ) {\n return;\n }\n if (currentBackgroundConnection !== undefined) {\n return;\n }\n this.#setBackgroundConnection({\n clientID: preferred.clientID,\n wsID: preferred.wsID,\n });\n this.#lc.debug?.('Selected background connection for shared auth work', {\n clientID: preferred.clientID,\n wsID: preferred.wsID,\n revision: preferred.revision,\n reason: 'preferred-validated',\n });\n return;\n }\n\n const currentBackgroundConnection = this.#getBackgroundConnectionContext();\n if (currentBackgroundConnection?.state === 'validated') {\n return;\n }\n\n const nextBackgroundConnection = [...this.#connections.values()]\n .filter(connection => connection.state === 'validated')\n .sort(comparePreferredValidatedConnection)\n .at(0);\n this.#setBackgroundConnection(\n nextBackgroundConnection\n ? {\n clientID: nextBackgroundConnection.clientID,\n wsID: nextBackgroundConnection.wsID,\n }\n : undefined,\n );\n if (nextBackgroundConnection) {\n this.#lc.debug?.('Selected background connection for shared auth work', {\n clientID: nextBackgroundConnection.clientID,\n wsID: nextBackgroundConnection.wsID,\n revision: nextBackgroundConnection.revision,\n reason: 'fallback-validated',\n });\n }\n }\n\n #getBackgroundConnectionContext(): ConnectionContext | undefined {\n const backgroundConnection = this.#group.backgroundConnection;\n if (!backgroundConnection) {\n return undefined;\n }\n return this.#getConnectionContext(backgroundConnection);\n }\n\n #getConnectionContext(\n selector: ConnectionSelector,\n ): ConnectionContext | undefined {\n const connection = this.#connections.get(selector.clientID);\n if (!connection) {\n return undefined;\n }\n if (connection.wsID !== selector.wsID) {\n return undefined;\n }\n return connection;\n }\n\n #mustGetConnectionContext(selector: ConnectionSelector): ConnectionContext {\n const connection = this.#getConnectionContext(selector);\n\n if (!connection) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.InvalidConnectionRequest,\n message:\n 'Connection auth state was not available for this websocket.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n\n return connection;\n }\n\n #storeConnection(connection: ConnectionContext): ConnectionContext {\n this.#connections.set(connection.clientID, connection);\n return connection;\n }\n\n #setGroup(group: GroupAuthState): GroupAuthState {\n this.#group = group;\n return group;\n }\n\n #setBackgroundConnection(\n backgroundConnection: ConnectionSelector | undefined,\n ) {\n if (\n sameConnectionSelector(\n this.#group.backgroundConnection,\n backgroundConnection,\n )\n ) {\n return;\n }\n this.#setGroup({\n ...this.#group,\n backgroundConnection: backgroundConnection\n ? {...backgroundConnection}\n : undefined,\n });\n }\n\n /**\n * Keeps the group background retransform deadline coherent with current\n * schedulability.\n *\n * When `reset` is false, this seeds a deadline only when shared retransform\n * is now possible and no deadline exists yet, preserving any existing\n * cadence. When `reset` is true, it starts a fresh interval from `#now()` if\n * retransform is schedulable for the current ready ViewSyncer instance, or\n * clears the deadline if it is not.\n */\n #updateBackgroundRetransformDeadline(reset: boolean) {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (\n !backgroundConnection ||\n this.#retransformIntervalMs === undefined ||\n !this.#sharedRetransformReady\n ) {\n if (this.#group.retransformAt !== undefined) {\n this.#setGroup({\n ...this.#group,\n retransformAt: undefined,\n });\n }\n return;\n }\n\n if (reset || this.#group.retransformAt === undefined) {\n this.#setGroup({\n ...this.#group,\n retransformAt: this.#now() + this.#retransformIntervalMs,\n });\n }\n }\n\n #nextRevalidateAt() {\n return this.#revalidateIntervalMs === undefined\n ? undefined\n : this.#now() + this.#revalidateIntervalMs;\n }\n}\n\nfunction compareByInsertionOrder(\n a: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n b: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n) {\n return a.insertionOrder - b.insertionOrder || a.wsID.localeCompare(b.wsID);\n}\n\nfunction comparePreferredValidatedConnection(\n a: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n b: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n) {\n return b.insertionOrder - a.insertionOrder || b.wsID.localeCompare(a.wsID);\n}\n\nfunction minDefined(a: number | undefined, b: number | undefined) {\n if (a === undefined) {\n return b;\n }\n if (b === undefined) {\n return a;\n }\n return Math.min(a, b);\n}\n\nfunction sameConnectionSelector(\n a: ConnectionSelector | undefined,\n b: ConnectionSelector | undefined,\n) {\n return a?.clientID === b?.clientID && a?.wsID === b?.wsID;\n}\n\nfunction filterHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n allowedHeaders: readonly string[] | undefined,\n) {\n if (!headers || !allowedHeaders || allowedHeaders.length === 0) {\n return undefined;\n }\n\n const allowed = new Set(allowedHeaders.map(header => header.toLowerCase()));\n let filtered: Record<string, string> | undefined;\n for (const [key, value] of Object.entries(headers)) {\n if (allowed.has(key.toLowerCase())) {\n filtered ??= {};\n filtered[key] = value;\n }\n }\n return filtered;\n}\n"],"mappings":"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"}
@@ -985,6 +985,6 @@ var RowsVersionBehindError = class extends Error {
985
985
  }
986
986
  };
987
987
  //#endregion
988
- export { CVRStore, ClientNotFoundError, ConcurrentModificationException, InvalidClientSchemaError, OwnershipError, RowsVersionBehindError, checkVersion };
988
+ export { CVRStore, ClientNotFoundError, ConcurrentModificationException, OwnershipError, RowsVersionBehindError, checkVersion };
989
989
 
990
990
  //# sourceMappingURL=cvr-store.js.map