@rocicorp/zero 1.2.0 → 1.3.0-canary.0

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 (303) hide show
  1. package/out/analyze-query/src/bin-analyze.js +25 -25
  2. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  3. package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
  4. package/out/ast-to-zql/src/ast-to-zql.js +2 -1
  5. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  6. package/out/replicache/src/btree/node.d.ts.map +1 -1
  7. package/out/replicache/src/btree/node.js +2 -2
  8. package/out/replicache/src/btree/node.js.map +1 -1
  9. package/out/replicache/src/connection-loop.js +3 -3
  10. package/out/replicache/src/connection-loop.js.map +1 -1
  11. package/out/replicache/src/deleted-clients.d.ts +0 -4
  12. package/out/replicache/src/deleted-clients.d.ts.map +1 -1
  13. package/out/replicache/src/deleted-clients.js +1 -1
  14. package/out/replicache/src/deleted-clients.js.map +1 -1
  15. package/out/replicache/src/hash.d.ts.map +1 -1
  16. package/out/replicache/src/hash.js.map +1 -1
  17. package/out/replicache/src/process-scheduler.d.ts.map +1 -1
  18. package/out/replicache/src/process-scheduler.js.map +1 -1
  19. package/out/replicache/src/request-idle.js +1 -1
  20. package/out/replicache/src/request-idle.js.map +1 -1
  21. package/out/replicache/src/sync/patch.d.ts +1 -1
  22. package/out/replicache/src/sync/patch.d.ts.map +1 -1
  23. package/out/replicache/src/sync/patch.js +1 -1
  24. package/out/replicache/src/sync/patch.js.map +1 -1
  25. package/out/shared/src/arrays.d.ts.map +1 -1
  26. package/out/shared/src/arrays.js +1 -2
  27. package/out/shared/src/arrays.js.map +1 -1
  28. package/out/shared/src/bigint-json.js +1 -1
  29. package/out/shared/src/bigint-json.js.map +1 -1
  30. package/out/shared/src/btree-set.js +1 -1
  31. package/out/shared/src/btree-set.js.map +1 -1
  32. package/out/shared/src/iterables.d.ts +7 -0
  33. package/out/shared/src/iterables.d.ts.map +1 -1
  34. package/out/shared/src/iterables.js +10 -1
  35. package/out/shared/src/iterables.js.map +1 -1
  36. package/out/shared/src/logging.d.ts.map +1 -1
  37. package/out/shared/src/logging.js +10 -9
  38. package/out/shared/src/logging.js.map +1 -1
  39. package/out/shared/src/options.js +1 -1
  40. package/out/shared/src/options.js.map +1 -1
  41. package/out/shared/src/sorted-entries.d.ts +2 -0
  42. package/out/shared/src/sorted-entries.d.ts.map +1 -0
  43. package/out/shared/src/sorted-entries.js +9 -0
  44. package/out/shared/src/sorted-entries.js.map +1 -0
  45. package/out/shared/src/tdigest-schema.d.ts.map +1 -1
  46. package/out/shared/src/tdigest-schema.js.map +1 -1
  47. package/out/shared/src/tdigest.d.ts.map +1 -1
  48. package/out/shared/src/tdigest.js +7 -7
  49. package/out/shared/src/tdigest.js.map +1 -1
  50. package/out/shared/src/valita.d.ts.map +1 -1
  51. package/out/shared/src/valita.js +1 -1
  52. package/out/shared/src/valita.js.map +1 -1
  53. package/out/z2s/src/sql.d.ts +2 -2
  54. package/out/z2s/src/sql.d.ts.map +1 -1
  55. package/out/z2s/src/sql.js +3 -3
  56. package/out/z2s/src/sql.js.map +1 -1
  57. package/out/zero/package.js +6 -7
  58. package/out/zero/package.js.map +1 -1
  59. package/out/zero/src/pg.js +1 -1
  60. package/out/zero/src/server.js +1 -1
  61. package/out/zero-cache/src/auth/auth.d.ts +8 -26
  62. package/out/zero-cache/src/auth/auth.d.ts.map +1 -1
  63. package/out/zero-cache/src/auth/auth.js +57 -82
  64. package/out/zero-cache/src/auth/auth.js.map +1 -1
  65. package/out/zero-cache/src/auth/jwt.d.ts +3 -3
  66. package/out/zero-cache/src/auth/jwt.d.ts.map +1 -1
  67. package/out/zero-cache/src/auth/jwt.js.map +1 -1
  68. package/out/zero-cache/src/auth/load-permissions.js +1 -1
  69. package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
  70. package/out/zero-cache/src/config/zero-config.d.ts +38 -2
  71. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  72. package/out/zero-cache/src/config/zero-config.js +56 -1
  73. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  74. package/out/zero-cache/src/custom/fetch.d.ts +2 -9
  75. package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
  76. package/out/zero-cache/src/custom/fetch.js +11 -4
  77. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  78. package/out/zero-cache/src/custom-queries/transform-query.d.ts +20 -9
  79. package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
  80. package/out/zero-cache/src/custom-queries/transform-query.js +74 -37
  81. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  82. package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
  83. package/out/zero-cache/src/db/migration-lite.js +1 -1
  84. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  85. package/out/zero-cache/src/db/migration.d.ts.map +1 -1
  86. package/out/zero-cache/src/db/migration.js +1 -1
  87. package/out/zero-cache/src/db/migration.js.map +1 -1
  88. package/out/zero-cache/src/db/pg-copy-binary.d.ts +101 -0
  89. package/out/zero-cache/src/db/pg-copy-binary.d.ts.map +1 -0
  90. package/out/zero-cache/src/db/pg-copy-binary.js +381 -0
  91. package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -0
  92. package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
  93. package/out/zero-cache/src/db/transaction-pool.js +3 -0
  94. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  95. package/out/zero-cache/src/db/warmup.d.ts.map +1 -1
  96. package/out/zero-cache/src/db/warmup.js +3 -1
  97. package/out/zero-cache/src/db/warmup.js.map +1 -1
  98. package/out/zero-cache/src/server/anonymous-otel-start.d.ts.map +1 -1
  99. package/out/zero-cache/src/server/anonymous-otel-start.js +2 -1
  100. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  101. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  102. package/out/zero-cache/src/server/change-streamer.js +5 -2
  103. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  104. package/out/zero-cache/src/server/inspector-delegate.d.ts +2 -2
  105. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  106. package/out/zero-cache/src/server/inspector-delegate.js +4 -4
  107. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  108. package/out/zero-cache/src/server/main.js +1 -1
  109. package/out/zero-cache/src/server/main.js.map +1 -1
  110. package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
  111. package/out/zero-cache/src/server/reaper.js +4 -1
  112. package/out/zero-cache/src/server/reaper.js.map +1 -1
  113. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  114. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  115. package/out/zero-cache/src/server/syncer.js +41 -20
  116. package/out/zero-cache/src/server/syncer.js.map +1 -1
  117. package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
  118. package/out/zero-cache/src/server/worker-urls.js +2 -1
  119. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  120. package/out/zero-cache/src/services/change-source/change-source.d.ts +4 -0
  121. package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
  122. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  123. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +3 -2
  124. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  125. package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
  126. package/out/zero-cache/src/services/change-source/custom/change-source.js +5 -2
  127. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  128. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  129. package/out/zero-cache/src/services/change-source/pg/change-source.js +13 -4
  130. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  131. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +3 -1
  132. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  133. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +91 -9
  134. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  135. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +2 -2
  136. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  137. package/out/zero-cache/src/services/change-streamer/broadcast.js +1 -1
  138. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  139. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +3 -0
  140. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  141. package/out/zero-cache/src/services/life-cycle.d.ts +5 -4
  142. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  143. package/out/zero-cache/src/services/life-cycle.js +11 -11
  144. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  145. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  146. package/out/zero-cache/src/services/litestream/commands.js +5 -5
  147. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  148. package/out/zero-cache/src/services/mutagen/pusher.d.ts +20 -20
  149. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  150. package/out/zero-cache/src/services/mutagen/pusher.js +91 -104
  151. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  152. package/out/zero-cache/src/services/replicator/change-processor.js +1 -1
  153. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  154. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  155. package/out/zero-cache/src/services/view-syncer/client-schema.d.ts.map +1 -1
  156. package/out/zero-cache/src/services/view-syncer/client-schema.js +4 -3
  157. package/out/zero-cache/src/services/view-syncer/client-schema.js.map +1 -1
  158. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +168 -0
  159. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.map +1 -0
  160. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js +385 -0
  161. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -0
  162. package/out/zero-cache/src/services/view-syncer/cvr-store.js +2 -2
  163. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  164. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  165. package/out/zero-cache/src/services/view-syncer/cvr.js +5 -4
  166. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  167. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +2 -3
  168. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
  169. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +3 -3
  170. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  171. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  172. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +5 -3
  173. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  174. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
  175. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +13 -7
  176. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  177. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +3 -1
  178. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  179. package/out/zero-cache/src/services/view-syncer/snapshotter.js +6 -9
  180. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  181. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +24 -26
  182. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  183. package/out/zero-cache/src/services/view-syncer/view-syncer.js +236 -124
  184. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  185. package/out/zero-cache/src/types/lite.d.ts.map +1 -1
  186. package/out/zero-cache/src/types/lite.js +3 -2
  187. package/out/zero-cache/src/types/lite.js.map +1 -1
  188. package/out/zero-cache/src/types/pg-types.js +4 -1
  189. package/out/zero-cache/src/types/pg-types.js.map +1 -1
  190. package/out/zero-cache/src/types/pg-versions.d.ts +3 -0
  191. package/out/zero-cache/src/types/pg-versions.d.ts.map +1 -0
  192. package/out/zero-cache/src/types/pg-versions.js +7 -0
  193. package/out/zero-cache/src/types/pg-versions.js.map +1 -0
  194. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  195. package/out/zero-cache/src/types/pg.js +6 -1
  196. package/out/zero-cache/src/types/pg.js.map +1 -1
  197. package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
  198. package/out/zero-cache/src/types/subscription.js +2 -2
  199. package/out/zero-cache/src/types/subscription.js.map +1 -1
  200. package/out/zero-cache/src/workers/connect-params.d.ts +1 -1
  201. package/out/zero-cache/src/workers/connect-params.d.ts.map +1 -1
  202. package/out/zero-cache/src/workers/connect-params.js +1 -1
  203. package/out/zero-cache/src/workers/connect-params.js.map +1 -1
  204. package/out/zero-cache/src/workers/connection.js +2 -2
  205. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts +2 -1
  206. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  207. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +64 -38
  208. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  209. package/out/zero-cache/src/workers/syncer.d.ts +2 -1
  210. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  211. package/out/zero-cache/src/workers/syncer.js +70 -31
  212. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  213. package/out/zero-client/src/client/connection.d.ts +4 -4
  214. package/out/zero-client/src/client/connection.d.ts.map +1 -1
  215. package/out/zero-client/src/client/connection.js.map +1 -1
  216. package/out/zero-client/src/client/http-string.d.ts.map +1 -1
  217. package/out/zero-client/src/client/http-string.js.map +1 -1
  218. package/out/zero-client/src/client/metrics.d.ts.map +1 -1
  219. package/out/zero-client/src/client/metrics.js +2 -1
  220. package/out/zero-client/src/client/metrics.js.map +1 -1
  221. package/out/zero-client/src/client/options.d.ts +34 -5
  222. package/out/zero-client/src/client/options.d.ts.map +1 -1
  223. package/out/zero-client/src/client/options.js.map +1 -1
  224. package/out/zero-client/src/client/server-option.js +1 -1
  225. package/out/zero-client/src/client/server-option.js.map +1 -1
  226. package/out/zero-client/src/client/version.js +1 -1
  227. package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
  228. package/out/zero-client/src/client/zero-poke-handler.js +1 -1
  229. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  230. package/out/zero-client/src/client/zero.d.ts +4 -3
  231. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  232. package/out/zero-client/src/client/zero.js +33 -11
  233. package/out/zero-client/src/client/zero.js.map +1 -1
  234. package/out/zero-pg/src/mod.js +1 -1
  235. package/out/zero-protocol/src/ast.d.ts.map +1 -1
  236. package/out/zero-protocol/src/ast.js.map +1 -1
  237. package/out/zero-protocol/src/change-desired-queries.d.ts +4 -0
  238. package/out/zero-protocol/src/change-desired-queries.d.ts.map +1 -1
  239. package/out/zero-protocol/src/change-desired-queries.js +4 -1
  240. package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
  241. package/out/zero-protocol/src/connect.d.ts +4 -0
  242. package/out/zero-protocol/src/connect.d.ts.map +1 -1
  243. package/out/zero-protocol/src/connect.js +2 -1
  244. package/out/zero-protocol/src/connect.js.map +1 -1
  245. package/out/zero-protocol/src/primary-key.d.ts.map +1 -1
  246. package/out/zero-protocol/src/primary-key.js.map +1 -1
  247. package/out/zero-protocol/src/protocol-version.d.ts +1 -1
  248. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  249. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  250. package/out/zero-protocol/src/push.d.ts +4 -0
  251. package/out/zero-protocol/src/push.d.ts.map +1 -1
  252. package/out/zero-protocol/src/push.js +2 -1
  253. package/out/zero-protocol/src/push.js.map +1 -1
  254. package/out/zero-protocol/src/up.d.ts +3 -0
  255. package/out/zero-protocol/src/up.d.ts.map +1 -1
  256. package/out/zero-react/src/zero-provider.d.ts.map +1 -1
  257. package/out/zero-react/src/zero-provider.js +11 -5
  258. package/out/zero-react/src/zero-provider.js.map +1 -1
  259. package/out/zero-schema/src/name-mapper.js +1 -1
  260. package/out/zero-schema/src/name-mapper.js.map +1 -1
  261. package/out/zero-server/src/mod.js +1 -1
  262. package/out/zero-server/src/process-mutations.d.ts.map +1 -1
  263. package/out/zero-server/src/process-mutations.js +2 -1
  264. package/out/zero-server/src/process-mutations.js.map +1 -1
  265. package/out/zero-server/src/push-processor.d.ts +1 -0
  266. package/out/zero-server/src/push-processor.d.ts.map +1 -1
  267. package/out/zero-server/src/push-processor.js +3 -2
  268. package/out/zero-server/src/push-processor.js.map +1 -1
  269. package/out/zero-solid/src/use-zero.d.ts.map +1 -1
  270. package/out/zero-solid/src/use-zero.js +8 -9
  271. package/out/zero-solid/src/use-zero.js.map +1 -1
  272. package/out/zql/src/builder/like.js +2 -1
  273. package/out/zql/src/builder/like.js.map +1 -1
  274. package/out/zql/src/ivm/data.d.ts.map +1 -1
  275. package/out/zql/src/ivm/data.js +6 -15
  276. package/out/zql/src/ivm/data.js.map +1 -1
  277. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  278. package/out/zql/src/ivm/memory-source.js +14 -8
  279. package/out/zql/src/ivm/memory-source.js.map +1 -1
  280. package/out/zql/src/query/complete-ordering.js +1 -1
  281. package/out/zql/src/query/complete-ordering.js.map +1 -1
  282. package/out/zql/src/query/query-impl.d.ts.map +1 -1
  283. package/out/zql/src/query/query-impl.js +2 -2
  284. package/out/zql/src/query/query-impl.js.map +1 -1
  285. package/out/zql/src/query/query-registry.d.ts.map +1 -1
  286. package/out/zql/src/query/query-registry.js +2 -1
  287. package/out/zql/src/query/query-registry.js.map +1 -1
  288. package/out/zql/src/query/ttl.js +1 -1
  289. package/out/zql/src/query/ttl.js.map +1 -1
  290. package/out/zqlite/src/internal/sql.d.ts +2 -2
  291. package/out/zqlite/src/internal/sql.d.ts.map +1 -1
  292. package/out/zqlite/src/internal/sql.js +1 -2
  293. package/out/zqlite/src/internal/sql.js.map +1 -1
  294. package/out/zqlite/src/sqlite-cost-model.d.ts +1 -1
  295. package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
  296. package/out/zqlite/src/sqlite-cost-model.js +1 -1
  297. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  298. package/out/zqlite/src/sqlite-stat-fanout.js +1 -1
  299. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  300. package/out/zqlite/src/table-source.d.ts.map +1 -1
  301. package/out/zqlite/src/table-source.js +8 -12
  302. package/out/zqlite/src/table-source.js.map +1 -1
  303. package/package.json +6 -7
@@ -3,36 +3,43 @@ import { InvalidPush } from "../../../zero-protocol/src/error-kind-enum.js";
3
3
  import { ZeroCache } from "../../../zero-protocol/src/error-origin-enum.js";
4
4
  import { startAsyncSpan, startSpan } from "../../../otel/src/span.js";
5
5
  import { version } from "../../../otel/src/version.js";
6
+ import "../services/view-syncer/connection-context-manager.js";
6
7
  import "../services/view-syncer/view-syncer.js";
7
8
  import { Lock } from "@rocicorp/lock";
8
- import { trace } from "@opentelemetry/api";
9
+ import { ROOT_CONTEXT, context, propagation, trace } from "@opentelemetry/api";
9
10
  //#region ../zero-cache/src/workers/syncer-ws-message-handler.ts
10
11
  var tracer = trace.getTracer("syncer-ws-server", version);
12
+ /**
13
+ * Wraps a function in an OTEL context extracted from a W3C traceparent header.
14
+ * This enables distributed tracing from the client through zero-cache to
15
+ * the user's API server.
16
+ */
17
+ function withTraceparent(traceparent, fn) {
18
+ if (!traceparent) return fn();
19
+ const extracted = propagation.extract(ROOT_CONTEXT, { traceparent });
20
+ return context.with(extracted, fn);
21
+ }
11
22
  var SyncerWsMessageHandler = class {
12
23
  #viewSyncer;
13
24
  #mutagen;
14
25
  #mutationLock;
15
26
  #lc;
16
27
  #clientGroupID;
17
- #syncContext;
28
+ #connectionSelector;
29
+ #contextManager;
18
30
  #pusher;
19
- constructor(lc, connectParams, viewSyncer, mutagen, pusher) {
20
- const { clientGroupID, clientID, profileID, wsID, baseCookie, protocolVersion, httpCookie, origin, userID } = connectParams;
31
+ constructor(lc, connectParams, contextManager, viewSyncer, mutagen, pusher) {
32
+ const { clientGroupID, clientID, wsID } = connectParams;
21
33
  this.#viewSyncer = viewSyncer;
22
34
  this.#mutagen = mutagen;
35
+ this.#contextManager = contextManager;
23
36
  this.#mutationLock = new Lock();
24
37
  this.#lc = lc.withContext("connection").withContext("clientID", clientID).withContext("clientGroupID", clientGroupID).withContext("wsID", wsID);
25
38
  this.#clientGroupID = clientGroupID;
26
39
  this.#pusher = pusher;
27
- this.#syncContext = {
40
+ this.#connectionSelector = {
28
41
  clientID,
29
- profileID,
30
- wsID,
31
- baseCookie,
32
- protocolVersion,
33
- httpCookie,
34
- origin,
35
- userID
42
+ wsID
36
43
  };
37
44
  }
38
45
  async handleMessage(msg) {
@@ -46,8 +53,8 @@ var SyncerWsMessageHandler = class {
46
53
  case "pull":
47
54
  lc.error?.("Pull is not supported by Zero");
48
55
  break;
49
- case "push": return startAsyncSpan(tracer, "connection.push", async () => {
50
- const { clientGroupID, mutations, auth: pushAuth } = msg[1];
56
+ case "push": return withTraceparent(msg[1].traceparent, () => startAsyncSpan(tracer, "connection.push", async () => {
57
+ const { clientGroupID, mutations } = msg[1];
51
58
  if (clientGroupID !== this.#clientGroupID) return [{
52
59
  type: "fatal",
53
60
  error: {
@@ -56,18 +63,33 @@ var SyncerWsMessageHandler = class {
56
63
  origin: ZeroCache
57
64
  }
58
65
  }];
59
- if (pushAuth) await viewSyncer.updateAuth(this.#syncContext, ["updateAuth", { auth: pushAuth }]);
60
66
  if (mutations.length === 0) return [{ type: "ok" }];
61
67
  if (mutations[0].type === "custom") {
62
- assert(this.#pusher, "A ZERO_MUTATE_URL must be set in order to process custom mutations.");
63
- return [this.#pusher.enqueuePush(this.#syncContext.clientID, msg[1], viewSyncer.auth?.raw, this.#syncContext.httpCookie, this.#syncContext.origin)];
68
+ if (!this.#pusher) return [{
69
+ type: "fatal",
70
+ error: {
71
+ kind: InvalidPush,
72
+ message: "A ZERO_MUTATE_URL must be set in order to process custom mutations.",
73
+ origin: ZeroCache
74
+ }
75
+ }];
76
+ return [this.#pusher.enqueuePush(this.#connectionSelector, msg[1])];
64
77
  }
65
- const auth = viewSyncer.auth;
78
+ const mutagen = this.#mutagen;
79
+ if (!mutagen) return [{
80
+ type: "fatal",
81
+ error: {
82
+ kind: InvalidPush,
83
+ message: `Support for legacy CRUD mutations is disabled`,
84
+ origin: ZeroCache
85
+ }
86
+ }];
87
+ const auth = this.#contextManager.mustGetConnectionContext(this.#connectionSelector).auth;
66
88
  assert(auth?.type !== "opaque", "Only JWT auth is supported for CRUD mutations");
67
89
  return [await this.#mutationLock.withLock(async () => {
68
90
  const errors = [];
69
91
  for (const mutation of mutations) {
70
- const maybeError = await this.#mutagen.processMutation(mutation, auth?.decoded, this.#pusher !== void 0);
92
+ const maybeError = await mutagen.processMutation(mutation, auth?.decoded, this.#pusher !== void 0);
71
93
  if (maybeError !== void 0) errors.push({
72
94
  kind: maybeError[0],
73
95
  message: maybeError[1],
@@ -80,39 +102,43 @@ var SyncerWsMessageHandler = class {
80
102
  };
81
103
  return { type: "ok" };
82
104
  })];
83
- });
105
+ }));
84
106
  case "changeDesiredQueries":
85
- await startAsyncSpan(tracer, "connection.changeDesiredQueries", () => viewSyncer.changeDesiredQueries(this.#syncContext, msg));
107
+ await withTraceparent(msg[1].traceparent, () => startAsyncSpan(tracer, "connection.changeDesiredQueries", () => viewSyncer.changeDesiredQueries(this.#connectionSelector, msg)));
86
108
  break;
87
109
  case "updateAuth":
88
110
  await startAsyncSpan(tracer, "connection.updateAuth", async () => {
89
- await viewSyncer.updateAuth(this.#syncContext, msg);
111
+ const initialConnection = this.#contextManager.mustGetConnectionContext(this.#connectionSelector);
112
+ const authRevisionChanged = (await this.#contextManager.updateAuth(this.#connectionSelector, msg[1])).revision !== initialConnection.revision;
113
+ await viewSyncer.updateAuth(this.#connectionSelector, msg, authRevisionChanged);
90
114
  });
91
115
  break;
92
116
  case "deleteClients": {
93
- const deletedClientIDs = await startAsyncSpan(tracer, "connection.deleteClients", () => viewSyncer.deleteClients(this.#syncContext, msg));
94
- if (this.#pusher && deletedClientIDs.length > 0) await this.#pusher.deleteClientMutations(deletedClientIDs);
117
+ const deletedClientIDs = await startAsyncSpan(tracer, "connection.deleteClients", () => viewSyncer.deleteClients(this.#connectionSelector, msg));
118
+ if (this.#pusher && deletedClientIDs.length > 0) await this.#pusher.deleteClientMutations(this.#connectionSelector, deletedClientIDs);
95
119
  break;
96
120
  }
97
- case "initConnection": {
98
- const ret = [{
99
- type: "stream",
100
- source: "viewSyncer",
101
- stream: startSpan(tracer, "connection.initConnection", () => viewSyncer.initConnection(this.#syncContext, msg))
102
- }];
103
- if (this.#pusher) ret.push({
104
- type: "stream",
105
- source: "pusher",
106
- stream: this.#pusher.initConnection(this.#syncContext.clientID, this.#syncContext.wsID, msg[1].userPushURL, msg[1].userPushHeaders, () => viewSyncer.clearAuth())
121
+ case "initConnection":
122
+ this.#contextManager.initConnection(this.#connectionSelector, msg[1]);
123
+ return withTraceparent(msg[1].traceparent, () => {
124
+ const ret = [{
125
+ type: "stream",
126
+ source: "viewSyncer",
127
+ stream: startSpan(tracer, "connection.initConnection", () => viewSyncer.initConnection(this.#connectionSelector, msg))
128
+ }];
129
+ if (this.#pusher) ret.push({
130
+ type: "stream",
131
+ source: "pusher",
132
+ stream: this.#pusher.initConnection(this.#connectionSelector)
133
+ });
134
+ return ret;
107
135
  });
108
- return ret;
109
- }
110
136
  case "closeConnection": break;
111
137
  case "inspect":
112
- await startAsyncSpan(tracer, "connection.inspect", () => viewSyncer.inspect(this.#syncContext, msg));
138
+ await startAsyncSpan(tracer, "connection.inspect", () => viewSyncer.inspect(this.#connectionSelector, msg));
113
139
  break;
114
140
  case "ackMutationResponses":
115
- if (this.#pusher) await this.#pusher.ackMutationResponses(msg[1]);
141
+ if (this.#pusher) await this.#pusher.ackMutationResponses(this.#connectionSelector, msg[1]);
116
142
  break;
117
143
  default: unreachable(msgType);
118
144
  }
@@ -1 +1 @@
1
- {"version":3,"file":"syncer-ws-message-handler.js","names":["#viewSyncer","#mutagen","#mutationLock","#lc","#clientGroupID","#syncContext","#pusher"],"sources":["../../../../../zero-cache/src/workers/syncer-ws-message-handler.ts"],"sourcesContent":["import {trace} from '@opentelemetry/api';\nimport {Lock} from '@rocicorp/lock';\nimport type {LogContext} from '@rocicorp/logger';\nimport {startAsyncSpan, startSpan} from '../../../otel/src/span.ts';\nimport {version} from '../../../otel/src/version.ts';\nimport {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport type {Upstream} from '../../../zero-protocol/src/up.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport {\n type SyncContext,\n type ViewSyncer,\n} from '../services/view-syncer/view-syncer.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport type {HandlerResult, MessageHandler} from './connection.ts';\n\nconst tracer = trace.getTracer('syncer-ws-server', version);\n\nexport class SyncerWsMessageHandler implements MessageHandler {\n readonly #viewSyncer: ViewSyncer;\n readonly #mutagen: Mutagen;\n readonly #mutationLock: Lock;\n readonly #lc: LogContext;\n readonly #clientGroupID: string;\n readonly #syncContext: SyncContext;\n readonly #pusher: Pusher | undefined;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n viewSyncer: ViewSyncer,\n mutagen: Mutagen,\n pusher: Pusher | undefined,\n ) {\n const {\n clientGroupID,\n clientID,\n profileID,\n wsID,\n baseCookie,\n protocolVersion,\n httpCookie,\n origin,\n userID,\n } = connectParams;\n this.#viewSyncer = viewSyncer;\n this.#mutagen = mutagen;\n this.#mutationLock = new Lock();\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#clientGroupID = clientGroupID;\n this.#pusher = pusher;\n this.#syncContext = {\n clientID,\n profileID,\n wsID,\n baseCookie,\n protocolVersion,\n httpCookie,\n origin,\n userID,\n };\n }\n\n async handleMessage(msg: Upstream): Promise<HandlerResult[]> {\n const lc = this.#lc;\n const msgType = msg[0];\n const viewSyncer = this.#viewSyncer;\n switch (msgType) {\n case 'ping':\n lc.error?.('Ping is not supported at this layer by Zero');\n break;\n case 'pull':\n lc.error?.('Pull is not supported by Zero');\n break;\n case 'push': {\n return startAsyncSpan<HandlerResult[]>(\n tracer,\n 'connection.push',\n async () => {\n const {clientGroupID, mutations, auth: pushAuth} = msg[1];\n if (clientGroupID !== this.#clientGroupID) {\n return [\n {\n type: 'fatal',\n error: {\n kind: ErrorKind.InvalidPush,\n message:\n `clientGroupID in mutation \"${clientGroupID}\" does not match ` +\n `clientGroupID of connection \"${this.#clientGroupID}`,\n origin: ErrorOrigin.ZeroCache,\n },\n } satisfies HandlerResult,\n ];\n }\n\n // for backwards compatibility, if the push contains auth, we update it\n if (pushAuth) {\n await viewSyncer.updateAuth(this.#syncContext, [\n 'updateAuth',\n {auth: pushAuth},\n ]);\n }\n\n if (mutations.length === 0) {\n return [\n {\n type: 'ok',\n },\n ];\n }\n\n // The client only ever sends 1 mutation per push.\n // #pusher will throw if it sees a CRUD mutation.\n // #mutagen will throw if it see a custom mutation.\n if (mutations[0].type === 'custom') {\n assert(\n this.#pusher,\n 'A ZERO_MUTATE_URL must be set in order to process custom mutations.',\n );\n return [\n this.#pusher.enqueuePush(\n this.#syncContext.clientID,\n msg[1],\n viewSyncer.auth?.raw,\n this.#syncContext.httpCookie,\n this.#syncContext.origin,\n ),\n ];\n }\n\n const auth = viewSyncer.auth;\n assert(\n auth?.type !== 'opaque',\n 'Only JWT auth is supported for CRUD mutations',\n );\n\n // Hold a connection-level lock while processing mutations so that:\n // 1. Mutations are processed in the order in which they are received and\n // 2. A single view syncer connection cannot hog multiple upstream connections.\n const ret = await this.#mutationLock.withLock(async () => {\n const errors: ErrorBody[] = [];\n for (const mutation of mutations) {\n const maybeError = await this.#mutagen.processMutation(\n mutation,\n auth?.decoded,\n this.#pusher !== undefined,\n );\n if (maybeError !== undefined) {\n errors.push({\n kind: maybeError[0],\n message: maybeError[1],\n origin: ErrorOrigin.ZeroCache,\n });\n }\n }\n if (errors.length > 0) {\n return {type: 'transient', errors} satisfies HandlerResult;\n }\n return {type: 'ok'} satisfies HandlerResult;\n });\n return [ret];\n },\n );\n }\n case 'changeDesiredQueries':\n await startAsyncSpan(tracer, 'connection.changeDesiredQueries', () =>\n viewSyncer.changeDesiredQueries(this.#syncContext, msg),\n );\n break;\n case 'updateAuth':\n await startAsyncSpan(tracer, 'connection.updateAuth', async () => {\n await viewSyncer.updateAuth(this.#syncContext, msg);\n });\n break;\n case 'deleteClients': {\n const deletedClientIDs = await startAsyncSpan(\n tracer,\n 'connection.deleteClients',\n () => viewSyncer.deleteClients(this.#syncContext, msg),\n );\n if (this.#pusher && deletedClientIDs.length > 0) {\n await this.#pusher.deleteClientMutations(deletedClientIDs);\n }\n break;\n }\n case 'initConnection': {\n const ret: HandlerResult[] = [\n {\n type: 'stream',\n source: 'viewSyncer',\n stream: startSpan(tracer, 'connection.initConnection', () =>\n viewSyncer.initConnection(this.#syncContext, msg),\n ),\n },\n ];\n\n // Given we support both CRUD and Custom mutators,\n // we do not initialize the `pusher` unless the user has opted\n // into custom mutations. We detect that by checking\n // if the pushURL has been set.\n if (this.#pusher) {\n ret.push({\n type: 'stream',\n source: 'pusher',\n stream: this.#pusher.initConnection(\n this.#syncContext.clientID,\n this.#syncContext.wsID,\n msg[1].userPushURL,\n msg[1].userPushHeaders,\n () => viewSyncer.clearAuth(),\n ),\n });\n }\n\n return ret;\n }\n case 'closeConnection':\n // This message is deprecated and no longer used.\n break;\n\n case 'inspect':\n await startAsyncSpan(tracer, 'connection.inspect', () =>\n viewSyncer.inspect(this.#syncContext, msg),\n );\n break;\n\n case 'ackMutationResponses':\n if (this.#pusher) {\n await this.#pusher.ackMutationResponses(msg[1]);\n }\n break;\n\n default:\n unreachable(msgType);\n }\n\n return [{type: 'ok'}];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"syncer-ws-message-handler.js","names":["#viewSyncer","#mutagen","#mutationLock","#lc","#clientGroupID","#connectionSelector","#contextManager","#pusher"],"sources":["../../../../../zero-cache/src/workers/syncer-ws-message-handler.ts"],"sourcesContent":["import {ROOT_CONTEXT, context, propagation, trace} from '@opentelemetry/api';\nimport {Lock} from '@rocicorp/lock';\nimport type {LogContext} from '@rocicorp/logger';\nimport {startAsyncSpan, startSpan} from '../../../otel/src/span.ts';\nimport {version} from '../../../otel/src/version.ts';\nimport {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport type {Upstream} from '../../../zero-protocol/src/up.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport {\n type ConnectionContextManager,\n type ConnectionSelector,\n} from '../services/view-syncer/connection-context-manager.ts';\nimport {type ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport type {HandlerResult, MessageHandler} from './connection.ts';\n\nconst tracer = trace.getTracer('syncer-ws-server', version);\n\n/**\n * Wraps a function in an OTEL context extracted from a W3C traceparent header.\n * This enables distributed tracing from the client through zero-cache to\n * the user's API server.\n */\nfunction withTraceparent<T>(traceparent: string | undefined, fn: () => T): T {\n if (!traceparent) {\n return fn();\n }\n const extracted = propagation.extract(ROOT_CONTEXT, {traceparent});\n return context.with(extracted, fn);\n}\n\nexport class SyncerWsMessageHandler implements MessageHandler {\n readonly #viewSyncer: ViewSyncer;\n readonly #mutagen: Mutagen | undefined;\n readonly #mutationLock: Lock;\n readonly #lc: LogContext;\n readonly #clientGroupID: string;\n readonly #connectionSelector: ConnectionSelector;\n readonly #contextManager: ConnectionContextManager;\n readonly #pusher: Pusher | undefined;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n contextManager: ConnectionContextManager,\n viewSyncer: ViewSyncer,\n mutagen: Mutagen | undefined,\n pusher: Pusher | undefined,\n ) {\n const {clientGroupID, clientID, wsID} = connectParams;\n this.#viewSyncer = viewSyncer;\n this.#mutagen = mutagen;\n this.#contextManager = contextManager;\n this.#mutationLock = new Lock();\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#clientGroupID = clientGroupID;\n this.#pusher = pusher;\n this.#connectionSelector = {\n clientID,\n wsID,\n };\n }\n\n async handleMessage(msg: Upstream): Promise<HandlerResult[]> {\n const lc = this.#lc;\n const msgType = msg[0];\n const viewSyncer = this.#viewSyncer;\n switch (msgType) {\n case 'ping':\n lc.error?.('Ping is not supported at this layer by Zero');\n break;\n case 'pull':\n lc.error?.('Pull is not supported by Zero');\n break;\n case 'push': {\n return withTraceparent(msg[1].traceparent, () =>\n startAsyncSpan<HandlerResult[]>(\n tracer,\n 'connection.push',\n async () => {\n const {clientGroupID, mutations} = msg[1];\n if (clientGroupID !== this.#clientGroupID) {\n return [\n {\n type: 'fatal',\n error: {\n kind: ErrorKind.InvalidPush,\n message:\n `clientGroupID in mutation \"${clientGroupID}\" does not match ` +\n `clientGroupID of connection \"${this.#clientGroupID}`,\n origin: ErrorOrigin.ZeroCache,\n },\n } satisfies HandlerResult,\n ];\n }\n\n if (mutations.length === 0) {\n return [\n {\n type: 'ok',\n },\n ];\n }\n\n // The client only ever sends 1 mutation per push.\n // #pusher will throw if it sees a CRUD mutation.\n // #mutagen will throw if it see a custom mutation.\n if (mutations[0].type === 'custom') {\n if (!this.#pusher) {\n return [\n {\n type: 'fatal',\n error: {\n kind: ErrorKind.InvalidPush,\n message:\n 'A ZERO_MUTATE_URL must be set in order to process custom mutations.',\n origin: ErrorOrigin.ZeroCache,\n },\n } satisfies HandlerResult,\n ];\n }\n return [\n this.#pusher.enqueuePush(this.#connectionSelector, msg[1]),\n ];\n }\n\n const mutagen = this.#mutagen;\n if (!mutagen) {\n return [\n {\n type: 'fatal',\n error: {\n kind: ErrorKind.InvalidPush,\n message: `Support for legacy CRUD mutations is disabled`,\n origin: ErrorOrigin.ZeroCache,\n },\n } satisfies HandlerResult,\n ];\n }\n\n const auth = this.#contextManager.mustGetConnectionContext(\n this.#connectionSelector,\n ).auth;\n assert(\n auth?.type !== 'opaque',\n 'Only JWT auth is supported for CRUD mutations',\n );\n\n // Hold a connection-level lock while processing mutations so that:\n // 1. Mutations are processed in the order in which they are received and\n // 2. A single view syncer connection cannot hog multiple upstream connections.\n const ret = await this.#mutationLock.withLock(async () => {\n const errors: ErrorBody[] = [];\n for (const mutation of mutations) {\n const maybeError = await mutagen.processMutation(\n mutation,\n auth?.decoded,\n this.#pusher !== undefined,\n );\n if (maybeError !== undefined) {\n errors.push({\n kind: maybeError[0],\n message: maybeError[1],\n origin: ErrorOrigin.ZeroCache,\n });\n }\n }\n if (errors.length > 0) {\n return {type: 'transient', errors} satisfies HandlerResult;\n }\n return {type: 'ok'} satisfies HandlerResult;\n });\n return [ret];\n },\n ),\n );\n }\n case 'changeDesiredQueries':\n await withTraceparent(msg[1].traceparent, () =>\n startAsyncSpan(tracer, 'connection.changeDesiredQueries', () =>\n viewSyncer.changeDesiredQueries(this.#connectionSelector, msg),\n ),\n );\n break;\n case 'updateAuth':\n await startAsyncSpan(tracer, 'connection.updateAuth', async () => {\n const initialConnection =\n this.#contextManager.mustGetConnectionContext(\n this.#connectionSelector,\n );\n const updatedConnection = await this.#contextManager.updateAuth(\n this.#connectionSelector,\n msg[1],\n );\n const authRevisionChanged =\n updatedConnection.revision !== initialConnection.revision;\n\n await viewSyncer.updateAuth(\n this.#connectionSelector,\n msg,\n authRevisionChanged,\n );\n });\n break;\n case 'deleteClients': {\n const deletedClientIDs = await startAsyncSpan(\n tracer,\n 'connection.deleteClients',\n () => viewSyncer.deleteClients(this.#connectionSelector, msg),\n );\n if (this.#pusher && deletedClientIDs.length > 0) {\n await this.#pusher.deleteClientMutations(\n this.#connectionSelector,\n deletedClientIDs,\n );\n }\n break;\n }\n case 'initConnection': {\n this.#contextManager.initConnection(this.#connectionSelector, msg[1]);\n return withTraceparent(msg[1].traceparent, () => {\n const ret: HandlerResult[] = [\n {\n type: 'stream',\n source: 'viewSyncer',\n stream: startSpan(tracer, 'connection.initConnection', () =>\n viewSyncer.initConnection(this.#connectionSelector, msg),\n ),\n },\n ];\n\n // Given we support both CRUD and Custom mutators,\n // we do not initialize the `pusher` unless the user has opted\n // into custom mutations. We detect that by checking\n // if the pushURL has been set.\n if (this.#pusher) {\n ret.push({\n type: 'stream',\n source: 'pusher',\n stream: this.#pusher.initConnection(this.#connectionSelector),\n });\n }\n\n return ret;\n });\n }\n case 'closeConnection':\n // This message is deprecated and no longer used.\n break;\n\n case 'inspect':\n await startAsyncSpan(tracer, 'connection.inspect', () =>\n viewSyncer.inspect(this.#connectionSelector, msg),\n );\n break;\n\n case 'ackMutationResponses':\n if (this.#pusher) {\n await this.#pusher.ackMutationResponses(\n this.#connectionSelector,\n msg[1],\n );\n }\n break;\n\n default:\n unreachable(msgType);\n }\n\n return [{type: 'ok'}];\n }\n}\n"],"mappings":"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"}
@@ -6,6 +6,7 @@ import type { Mutagen } from '../services/mutagen/mutagen.ts';
6
6
  import type { Pusher } from '../services/mutagen/pusher.ts';
7
7
  import type { ReplicaState } from '../services/replicator/replicator.ts';
8
8
  import type { ActivityBasedService, Service, SingletonService } from '../services/service.ts';
9
+ import type { ConnectionContextManager } from '../services/view-syncer/connection-context-manager.ts';
9
10
  import { DrainCoordinator } from '../services/view-syncer/drain-coordinator.ts';
10
11
  import type { ViewSyncer } from '../services/view-syncer/view-syncer.ts';
11
12
  import type { Worker } from '../types/processes.ts';
@@ -23,7 +24,7 @@ export type SyncerWorkerData = {
23
24
  export declare class Syncer implements SingletonService {
24
25
  #private;
25
26
  readonly id: string;
26
- constructor(lc: LogContext, config: ZeroConfig, viewSyncerFactory: (id: string, sub: Subscription<ReplicaState>, drainCoordinator: DrainCoordinator, validateLegacyJWT: ValidateLegacyJWT | undefined) => ViewSyncer & ActivityBasedService, mutagenFactory: (id: string) => Mutagen & Service, pusherFactory: ((id: string) => Pusher & Service) | undefined, parent: Worker);
27
+ constructor(lc: LogContext, config: ZeroConfig, viewSyncerFactory: (id: string, sub: Subscription<ReplicaState>, drainCoordinator: DrainCoordinator) => ViewSyncer & ActivityBasedService, mutagenFactory: ((id: string) => Mutagen & Service) | undefined, pusherFactory: ((id: string, contextManager: ConnectionContextManager) => Pusher & Service) | undefined, parent: Worker, validateLegacyJWT: ValidateLegacyJWT | undefined);
27
28
  run(): Promise<void>;
28
29
  /**
29
30
  * Graceful shutdown involves shutting down view syncers one at a time, pausing
@@ -1 +1 @@
1
- {"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/syncer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAC,KAAK,iBAAiB,EAAC,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAMzD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAC1D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,sCAAsC,CAAC;AAEvE,OAAO,KAAK,EACV,oBAAoB,EACpB,OAAO,EACP,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAC,gBAAgB,EAAC,MAAM,8CAA8C,CAAC;AAC9E,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AAO3D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,WAAW,CAAC;CAC7B,CAAC;AA4BF;;;;;;GAMG;AACH,qBAAa,MAAO,YAAW,gBAAgB;;IAC7C,QAAQ,CAAC,EAAE,SAAmB;gBAa5B,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,UAAU,EAClB,iBAAiB,EAAE,CACjB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,YAAY,CAAC,YAAY,CAAC,EAC/B,gBAAgB,EAAE,gBAAgB,EAClC,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,KAC7C,UAAU,GAAG,oBAAoB,EACtC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,EACjD,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,GAAG,SAAS,EAC7D,MAAM,EAAE,MAAM;IA4IhB,GAAG;IAIH;;;;;OAKG;IACG,KAAK;IAqBX,IAAI;CA4BL"}
1
+ {"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/syncer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AASrD,OAAO,EAAyB,KAAK,iBAAiB,EAAC,MAAM,iBAAiB,CAAC;AAE/E,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAOzD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAC1D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,sCAAsC,CAAC;AAEvE,OAAO,KAAK,EACV,oBAAoB,EACpB,OAAO,EACP,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uDAAuD,CAAC;AACpG,OAAO,EAAC,gBAAgB,EAAC,MAAM,8CAA8C,CAAC;AAC9E,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AAO3D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,WAAW,CAAC;CAC7B,CAAC;AA4BF;;;;;;GAMG;AACH,qBAAa,MAAO,YAAW,gBAAgB;;IAC7C,QAAQ,CAAC,EAAE,SAAmB;gBAc5B,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,UAAU,EAClB,iBAAiB,EAAE,CACjB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,YAAY,CAAC,YAAY,CAAC,EAC/B,gBAAgB,EAAE,gBAAgB,KAC/B,UAAU,GAAG,oBAAoB,EACtC,cAAc,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,GAAG,SAAS,EAC/D,aAAa,EACT,CAAC,CACC,EAAE,EAAE,MAAM,EACV,cAAc,EAAE,wBAAwB,KACrC,MAAM,GAAG,OAAO,CAAC,GACtB,SAAS,EACb,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,iBAAiB,GAAG,SAAS;IAsOlD,GAAG;IAIH;;;;;OAKG;IACG,KAAK;IAqBX,IAAI;CAKL"}
@@ -1,10 +1,14 @@
1
1
  import { promiseVoid } from "../../../shared/src/resolved-promises.js";
2
+ import { Unauthorized } from "../../../zero-protocol/src/error-kind-enum.js";
3
+ import { ZeroCache } from "../../../zero-protocol/src/error-origin-enum.js";
4
+ import { ProtocolError, isProtocolError } from "../../../zero-protocol/src/error.js";
2
5
  import "../config/zero-config.js";
3
6
  import { installWebSocketReceiver } from "../types/websocket-handoff.js";
7
+ import { getOrCreateGauge } from "../observability/metrics.js";
4
8
  import { createNotifierFrom, subscribeTo } from "./replicator.js";
5
9
  import { recordConnectionAttempted, recordConnectionSuccess, setActiveClientGroupsGetter } from "../server/anonymous-otel-start.js";
6
- import "../auth/auth.js";
7
- import { tokenConfigOptions, verifyToken } from "../auth/jwt.js";
10
+ import { resolveAuth } from "../auth/auth.js";
11
+ import { tokenConfigOptions } from "../auth/jwt.js";
8
12
  import { ServiceRunner } from "../services/runner.js";
9
13
  import { DrainCoordinator } from "../services/view-syncer/drain-coordinator.js";
10
14
  import { Connection, sendError } from "./connection.js";
@@ -47,34 +51,72 @@ var Syncer = class {
47
51
  #wss;
48
52
  #stopped = resolver();
49
53
  #config;
50
- constructor(lc, config, viewSyncerFactory, mutagenFactory, pusherFactory, parent) {
54
+ #validateLegacyJWT;
55
+ constructor(lc, config, viewSyncerFactory, mutagenFactory, pusherFactory, parent, validateLegacyJWT) {
51
56
  this.#config = config;
57
+ this.#validateLegacyJWT = validateLegacyJWT;
52
58
  const notifier = createNotifierFrom(lc, parent);
53
59
  subscribeTo(lc, parent);
54
60
  this.#lc = lc;
55
- this.#viewSyncers = new ServiceRunner(lc, (id) => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator, this.#validateLegacyJWT()), (v) => v.keepalive());
56
- this.#mutagens = new ServiceRunner(lc, mutagenFactory, (m) => m.hasRefs());
57
- if (pusherFactory) this.#pushers = new ServiceRunner(lc, pusherFactory, (p) => p.hasRefs());
61
+ this.#viewSyncers = new ServiceRunner(lc, (id) => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator), (v) => v.keepalive());
62
+ if (mutagenFactory) this.#mutagens = new ServiceRunner(lc, mutagenFactory, (m) => m.hasRefs());
63
+ if (pusherFactory) this.#pushers = new ServiceRunner(lc, (id) => pusherFactory(id, this.#viewSyncers.getService(id).contextManager), (p) => p.hasRefs());
58
64
  this.#parent = parent;
59
65
  this.#wss = new WebSocketServer(getWebSocketServerOptions(config));
60
66
  installWebSocketReceiver(lc, this.#wss, this.#createConnection, this.#parent);
61
67
  setActiveClientGroupsGetter(() => this.#viewSyncers.size);
68
+ getOrCreateGauge("sync", "active-client-groups", "Number of active client groups").addCallback((result) => result.observe(this.#viewSyncers.size));
69
+ getOrCreateGauge("sync", "queries", "Active queries (pipelines) across all client groups").addCallback((result) => {
70
+ let total = 0;
71
+ for (const vs of this.#viewSyncers.getServices()) total += vs.queryCount;
72
+ result.observe(total);
73
+ });
74
+ getOrCreateGauge("sync", "rows", "Tracked rows across all client groups").addCallback((result) => {
75
+ let total = 0;
76
+ for (const vs of this.#viewSyncers.getServices()) total += vs.rowCount;
77
+ result.observe(total);
78
+ });
62
79
  }
63
80
  #createConnection = async (ws, params) => {
64
81
  this.#lc.debug?.("creating connection", params.clientGroupID, params.clientID);
65
82
  recordConnectionAttempted();
66
83
  const { clientID, clientGroupID, auth, userID } = params;
67
- if (auth !== void 0 && auth !== "") {
84
+ const hasProvidedAuth = auth !== void 0 && auth !== "";
85
+ if (hasProvidedAuth) {
68
86
  const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});
69
87
  const hasPushOrMutate = this.#config?.push?.url !== void 0 || this.#config?.mutate?.url !== void 0;
70
88
  const hasQueries = this.#config?.query?.url !== void 0 || this.#config?.getQueries?.url !== void 0;
71
89
  if (!(tokenOptions.length === 1) && !(hasPushOrMutate && hasQueries)) throw new Error("Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: " + JSON.stringify(tokenOptions) + ". You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options.");
72
90
  }
91
+ let initialAuth;
92
+ try {
93
+ initialAuth = await resolveAuth(this.#lc.withContext("clientGroupID", clientGroupID).withContext("clientID", clientID), void 0, userID, auth, this.#validateLegacyJWT);
94
+ } catch (e) {
95
+ if (isProtocolError(e)) {
96
+ this.#lc.warn?.("Rejecting sync connection during initial auth resolution", {
97
+ clientGroupID,
98
+ clientID,
99
+ userID,
100
+ hasProvidedAuth,
101
+ errorKind: e.message
102
+ });
103
+ sendError(this.#lc, ws, e.errorBody);
104
+ ws.close(3e3, e.errorBody.message);
105
+ return;
106
+ }
107
+ throw e;
108
+ }
73
109
  const viewSyncer = this.#viewSyncers.getService(clientGroupID);
74
- const authResult = await viewSyncer.initAuthSession(userID, auth);
75
- if (!authResult.ok) {
76
- sendError(this.#lc, ws, authResult.error);
77
- ws.close(3e3, authResult.error.message);
110
+ const contextManager = viewSyncer.contextManager;
111
+ const group = contextManager.getGroupState();
112
+ if (group.validated && group.userID !== userID) {
113
+ const error = new ProtocolError({
114
+ kind: Unauthorized,
115
+ message: "Client groups are pinned to a single userID. Connection userID does not match existing client group userID.",
116
+ origin: ZeroCache
117
+ });
118
+ sendError(this.#lc, ws, error.errorBody);
119
+ ws.close(3e3, error.message);
78
120
  return;
79
121
  }
80
122
  const existing = this.#connections.get(clientID);
@@ -82,19 +124,31 @@ var Syncer = class {
82
124
  this.#lc.debug?.(`client ${clientID} already connected, closing existing connection`);
83
125
  existing.close(`replaced by ${params.wsID}`);
84
126
  }
85
- const mutagen = this.#mutagens.getService(clientGroupID);
127
+ contextManager.registerConnection({
128
+ clientID,
129
+ wsID: params.wsID
130
+ }, params, initialAuth);
131
+ const mutagen = this.#mutagens?.getService(clientGroupID);
86
132
  const pusher = this.#pushers?.getService(clientGroupID);
87
- mutagen.ref();
133
+ mutagen?.ref();
88
134
  pusher?.ref();
89
135
  let connection;
90
136
  try {
91
- connection = new Connection(this.#lc, params, ws, new SyncerWsMessageHandler(this.#lc, params, viewSyncer, mutagen, pusher), () => {
137
+ connection = new Connection(this.#lc, params, ws, new SyncerWsMessageHandler(this.#lc, params, contextManager, viewSyncer, mutagen, pusher), () => {
138
+ contextManager.closeConnection({
139
+ clientID,
140
+ wsID: params.wsID
141
+ });
92
142
  if (this.#connections.get(clientID) === connection) this.#connections.delete(clientID);
93
- mutagen.unref();
143
+ mutagen?.unref();
94
144
  pusher?.unref();
95
145
  });
96
146
  } catch (e) {
97
- mutagen.unref();
147
+ contextManager.closeConnection({
148
+ clientID,
149
+ wsID: params.wsID
150
+ });
151
+ mutagen?.unref();
98
152
  pusher?.unref();
99
153
  throw e;
100
154
  }
@@ -133,21 +187,6 @@ var Syncer = class {
133
187
  this.#stopped.resolve();
134
188
  return promiseVoid;
135
189
  }
136
- /** @deprecated used in JWT validation */
137
- #validateLegacyJWT() {
138
- if (tokenConfigOptions(this.#config.auth ?? {}).length !== 1) return;
139
- return async (token, { userID }) => {
140
- return {
141
- type: "jwt",
142
- raw: token,
143
- decoded: await verifyToken(this.#config.auth, token, {
144
- subject: userID,
145
- ...this.#config.auth?.issuer && { issuer: this.#config.auth.issuer },
146
- ...this.#config.auth?.audience && { audience: this.#config.auth.audience }
147
- })
148
- };
149
- };
150
- }
151
190
  };
152
191
  //#endregion
153
192
  export { Syncer };
@@ -1 +1 @@
1
- {"version":3,"file":"syncer.js","names":["#lc","#viewSyncers","#mutagens","#pushers","#connections","#drainCoordinator","#parent","#wss","#stopped","#config","#validateLegacyJWT","#createConnection"],"sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {pid} from 'node:process';\nimport type {MessagePort} from 'node:worker_threads';\nimport {WebSocketServer, type ServerOptions, type WebSocket} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {type ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions, verifyToken} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.ts';\nimport {\n recordConnectionAttempted,\n recordConnectionSuccess,\n setActiveClientGroupsGetter,\n} from '../server/anonymous-otel-start.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {ServiceRunner} from '../services/runner.ts';\nimport type {\n ActivityBasedService,\n Service,\n SingletonService,\n} from '../services/service.ts';\nimport {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {Worker} from '../types/processes.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {installWebSocketReceiver} from '../types/websocket-handoff.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {Connection, sendError} from './connection.ts';\nimport {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type SyncerWorkerData = {\n replicatorPort: MessagePort;\n};\n\nfunction getWebSocketServerOptions(config: ZeroConfig): ServerOptions {\n const options: ServerOptions = {\n noServer: true,\n maxPayload: config.websocketMaxPayloadBytes,\n };\n\n if (config.websocketCompression) {\n options.perMessageDeflate = true;\n\n if (config.websocketCompressionOptions) {\n try {\n const compressionOptions = JSON.parse(\n config.websocketCompressionOptions,\n );\n options.perMessageDeflate = compressionOptions;\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n }\n }\n\n return options;\n}\n\n/**\n * The Syncer worker receives websocket handoffs for \"/sync\" connections\n * from the Dispatcher in the main thread, and creates websocket\n * {@link Connection}s with a corresponding {@link ViewSyncer}, {@link Mutagen},\n * and {@link Subscription} to version notifications from the Replicator\n * worker.\n */\nexport class Syncer implements SingletonService {\n readonly id = `syncer-${pid}`;\n readonly #lc: LogContext;\n readonly #viewSyncers: ServiceRunner<ViewSyncer & ActivityBasedService>;\n readonly #mutagens: ServiceRunner<Mutagen & Service>;\n readonly #pushers: ServiceRunner<Pusher & Service> | undefined;\n readonly #connections = new Map<string, Connection>();\n readonly #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: (id: string) => Mutagen & Service,\n pusherFactory: ((id: string) => Pusher & Service) | undefined,\n parent: Worker,\n ) {\n this.#config = config;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent);\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id =>\n viewSyncerFactory(\n id,\n notifier.subscribe(),\n this.#drainCoordinator,\n this.#validateLegacyJWT(),\n ),\n v => v.keepalive(),\n );\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(lc, pusherFactory, p => p.hasRefs());\n }\n this.#parent = parent;\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#createConnection,\n this.#parent,\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\n this.#lc.debug?.(\n 'creating connection',\n params.clientGroupID,\n params.clientID,\n );\n recordConnectionAttempted();\n const {clientID, clientGroupID, auth, userID} = params;\n const hasProvidedAuth = auth !== undefined && auth !== '';\n\n if (hasProvidedAuth) {\n const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});\n\n const hasPushOrMutate =\n this.#config?.push?.url !== undefined ||\n this.#config?.mutate?.url !== undefined;\n const hasQueries =\n this.#config?.query?.url !== undefined ||\n this.#config?.getQueries?.url !== undefined;\n\n // must either have one of the token options set or have custom mutations & queries enabled\n const hasExactlyOneTokenOption = tokenOptions.length === 1;\n const hasCustomEndpoints = hasPushOrMutate && hasQueries;\n if (!hasExactlyOneTokenOption && !hasCustomEndpoints) {\n throw new Error(\n 'Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: ' +\n JSON.stringify(tokenOptions) +\n '. You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options.',\n );\n }\n }\n\n const viewSyncer = this.#viewSyncers.getService(clientGroupID);\n\n // Verify JWT BEFORE touching existing connections - prevents unauthenticated\n // attackers from force-disconnecting legitimate users via DoS\n const authResult = await viewSyncer.initAuthSession(userID, auth);\n if (!authResult.ok) {\n sendError(this.#lc, ws, authResult.error);\n ws.close(3000, authResult.error.message);\n return;\n }\n\n // Only check for and close existing connections AFTER auth is validated\n const existing = this.#connections.get(clientID);\n if (existing) {\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\n }\n\n const mutagen = this.#mutagens.getService(clientGroupID);\n const pusher = this.#pushers?.getService(clientGroupID);\n // a new connection is using the mutagen and pusher. Bump their ref counts.\n mutagen.ref();\n pusher?.ref();\n\n let connection: Connection;\n try {\n connection = new Connection(\n this.#lc,\n params,\n ws,\n new SyncerWsMessageHandler(\n this.#lc,\n params,\n viewSyncer,\n mutagen,\n pusher,\n ),\n () => {\n if (this.#connections.get(clientID) === connection) {\n this.#connections.delete(clientID);\n }\n // Connection is closed. We can unref the mutagen and pusher.\n // If their ref counts are zero, they will stop themselves and set themselves invalid.\n mutagen.unref();\n pusher?.unref();\n },\n );\n } catch (e) {\n mutagen.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n connection.init() && recordConnectionSuccess();\n\n if (params.initConnectionMsg) {\n this.#lc.debug?.(\n 'handling init connection message from sec header',\n params.clientGroupID,\n params.clientID,\n );\n await connection.handleInitConnection(\n JSON.stringify(params.initConnectionMsg),\n );\n }\n };\n\n run() {\n return this.#stopped.promise;\n }\n\n /**\n * Graceful shutdown involves shutting down view syncers one at a time, pausing\n * for the duration of view syncer's hydration between each one. This paces the\n * disconnects to avoid creating a backlog of hydrations in the receiving server\n * when the clients reconnect.\n */\n async drain() {\n const start = Date.now();\n this.#lc.info?.(`draining ${this.#viewSyncers.size} view-syncers`);\n\n this.#drainCoordinator.drainNextIn(0);\n\n while (this.#viewSyncers.size) {\n await this.#drainCoordinator.forceDrainTimeout;\n\n // Pick an arbitrary view syncer to force drain.\n for (const vs of this.#viewSyncers.getServices()) {\n this.#lc.debug?.(`draining view-syncer ${vs.id} (forced)`);\n // When this drain or an elective drain completes, the forceDrainTimeout will\n // resolve after the next drain interval.\n void vs.stop();\n break;\n }\n }\n this.#lc.info?.(`finished draining (${Date.now() - start} ms)`);\n }\n\n stop() {\n this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n\n /** @deprecated used in JWT validation */\n #validateLegacyJWT(): ValidateLegacyJWT | undefined {\n const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});\n if (tokenOptions.length !== 1) {\n return undefined;\n }\n\n return async (token, {userID}) => {\n const decoded = await verifyToken(this.#config.auth, token, {\n subject: userID,\n ...(this.#config.auth?.issuer && {issuer: this.#config.auth.issuer}),\n ...(this.#config.auth?.audience && {\n audience: this.#config.auth.audience,\n }),\n });\n return {\n type: 'jwt',\n raw: token,\n decoded,\n };\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"syncer.js","names":["#lc","#viewSyncers","#mutagens","#pushers","#connections","#drainCoordinator","#parent","#wss","#stopped","#config","#validateLegacyJWT","#createConnection"],"sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {pid} from 'node:process';\nimport type {MessagePort} from 'node:worker_threads';\nimport {WebSocketServer, type ServerOptions, type WebSocket} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {\n isProtocolError,\n ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {resolveAuth, type Auth, type ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.ts';\nimport {\n recordConnectionAttempted,\n recordConnectionSuccess,\n setActiveClientGroupsGetter,\n} from '../server/anonymous-otel-start.ts';\nimport {getOrCreateGauge} from '../observability/metrics.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {ServiceRunner} from '../services/runner.ts';\nimport type {\n ActivityBasedService,\n Service,\n SingletonService,\n} from '../services/service.ts';\nimport type {ConnectionContextManager} from '../services/view-syncer/connection-context-manager.ts';\nimport {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {Worker} from '../types/processes.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {installWebSocketReceiver} from '../types/websocket-handoff.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {Connection, sendError} from './connection.ts';\nimport {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type SyncerWorkerData = {\n replicatorPort: MessagePort;\n};\n\nfunction getWebSocketServerOptions(config: ZeroConfig): ServerOptions {\n const options: ServerOptions = {\n noServer: true,\n maxPayload: config.websocketMaxPayloadBytes,\n };\n\n if (config.websocketCompression) {\n options.perMessageDeflate = true;\n\n if (config.websocketCompressionOptions) {\n try {\n const compressionOptions = JSON.parse(\n config.websocketCompressionOptions,\n );\n options.perMessageDeflate = compressionOptions;\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n }\n }\n\n return options;\n}\n\n/**\n * The Syncer worker receives websocket handoffs for \"/sync\" connections\n * from the Dispatcher in the main thread, and creates websocket\n * {@link Connection}s with a corresponding {@link ViewSyncer}, {@link Mutagen},\n * and {@link Subscription} to version notifications from the Replicator\n * worker.\n */\nexport class Syncer implements SingletonService {\n readonly id = `syncer-${pid}`;\n readonly #lc: LogContext;\n readonly #viewSyncers: ServiceRunner<ViewSyncer & ActivityBasedService>;\n readonly #mutagens: ServiceRunner<Mutagen & Service> | undefined;\n readonly #pushers: ServiceRunner<Pusher & Service> | undefined;\n readonly #connections = new Map<string, Connection>();\n readonly #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n readonly #validateLegacyJWT: ValidateLegacyJWT | undefined;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: ((id: string) => Mutagen & Service) | undefined,\n pusherFactory:\n | ((\n id: string,\n contextManager: ConnectionContextManager,\n ) => Pusher & Service)\n | undefined,\n parent: Worker,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n ) {\n this.#config = config;\n this.#validateLegacyJWT = validateLegacyJWT;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent);\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator),\n v => v.keepalive(),\n );\n if (mutagenFactory) {\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n }\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(\n lc,\n id =>\n pusherFactory(id, this.#viewSyncers.getService(id).contextManager),\n p => p.hasRefs(),\n );\n }\n this.#parent = parent;\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#createConnection,\n this.#parent,\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n\n getOrCreateGauge(\n 'sync',\n 'active-client-groups',\n 'Number of active client groups',\n ).addCallback(result => result.observe(this.#viewSyncers.size));\n\n getOrCreateGauge(\n 'sync',\n 'queries',\n 'Active queries (pipelines) across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.queryCount;\n }\n result.observe(total);\n });\n\n getOrCreateGauge(\n 'sync',\n 'rows',\n 'Tracked rows across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.rowCount;\n }\n result.observe(total);\n });\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\n this.#lc.debug?.(\n 'creating connection',\n params.clientGroupID,\n params.clientID,\n );\n recordConnectionAttempted();\n const {clientID, clientGroupID, auth, userID} = params;\n const hasProvidedAuth = auth !== undefined && auth !== '';\n\n if (hasProvidedAuth) {\n const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});\n\n const hasPushOrMutate =\n this.#config?.push?.url !== undefined ||\n this.#config?.mutate?.url !== undefined;\n const hasQueries =\n this.#config?.query?.url !== undefined ||\n this.#config?.getQueries?.url !== undefined;\n\n // must either have one of the token options set or have custom mutations & queries enabled\n const hasExactlyOneTokenOption = tokenOptions.length === 1;\n const hasCustomEndpoints = hasPushOrMutate && hasQueries;\n if (!hasExactlyOneTokenOption && !hasCustomEndpoints) {\n throw new Error(\n 'Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: ' +\n JSON.stringify(tokenOptions) +\n '. You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options.',\n );\n }\n }\n\n let initialAuth: Auth | undefined;\n\n // Verify JWT BEFORE touching existing connections - prevents unauthenticated\n // attackers from force-disconnecting legitimate users via DoS.\n try {\n initialAuth = await resolveAuth(\n this.#lc\n .withContext('clientGroupID', clientGroupID)\n .withContext('clientID', clientID),\n // no previous auth, since this is a new connection, and resolveAuth is\n // connection scoped, not client group scoped\n undefined,\n userID,\n auth,\n this.#validateLegacyJWT,\n );\n } catch (e) {\n if (isProtocolError(e)) {\n this.#lc.warn?.(\n 'Rejecting sync connection during initial auth resolution',\n {\n clientGroupID,\n clientID,\n userID,\n hasProvidedAuth,\n errorKind: e.message,\n },\n );\n sendError(this.#lc, ws, e.errorBody);\n ws.close(3000, e.errorBody.message);\n return;\n }\n throw e;\n }\n\n const viewSyncer = this.#viewSyncers.getService(clientGroupID);\n const contextManager = viewSyncer.contextManager;\n const group = contextManager.getGroupState();\n\n // TODO(0xcadams): we only check for user ID mismatch here if the group is\n // already validated. This prevents wrong-user reconnects from evicting a\n // healthy connection, but it does not protect against same-user reconnects\n // with an invalid opaque token. The long-term fix is to keep the replacement\n // connection pending until its auth is fully validated, and only then replace\n // the existing socket.\n if (group.validated && group.userID !== userID) {\n const error = new ProtocolError({\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 sendError(this.#lc, ws, error.errorBody);\n ws.close(3000, error.message);\n return;\n }\n\n // Check for and close existing connections AFTER auth is validated\n const existing = this.#connections.get(clientID);\n if (existing) {\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\n }\n\n contextManager.registerConnection(\n {clientID, wsID: params.wsID},\n params,\n initialAuth,\n );\n\n const mutagen = this.#mutagens?.getService(clientGroupID);\n const pusher = this.#pushers?.getService(clientGroupID);\n // a new connection is using the mutagen and pusher. Bump their ref counts.\n mutagen?.ref();\n pusher?.ref();\n\n let connection: Connection;\n try {\n connection = new Connection(\n this.#lc,\n params,\n ws,\n new SyncerWsMessageHandler(\n this.#lc,\n params,\n contextManager,\n viewSyncer,\n mutagen,\n pusher,\n ),\n () => {\n contextManager.closeConnection({\n clientID,\n wsID: params.wsID,\n });\n if (this.#connections.get(clientID) === connection) {\n this.#connections.delete(clientID);\n }\n // Connection is closed. We can unref the mutagen and pusher.\n // If their ref counts are zero, they will stop themselves and set themselves invalid.\n mutagen?.unref();\n pusher?.unref();\n },\n );\n } catch (e) {\n contextManager.closeConnection({clientID, wsID: params.wsID});\n mutagen?.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n connection.init() && recordConnectionSuccess();\n\n if (params.initConnectionMsg) {\n this.#lc.debug?.(\n 'handling init connection message from sec header',\n params.clientGroupID,\n params.clientID,\n );\n await connection.handleInitConnection(\n JSON.stringify(params.initConnectionMsg),\n );\n }\n };\n\n run() {\n return this.#stopped.promise;\n }\n\n /**\n * Graceful shutdown involves shutting down view syncers one at a time, pausing\n * for the duration of view syncer's hydration between each one. This paces the\n * disconnects to avoid creating a backlog of hydrations in the receiving server\n * when the clients reconnect.\n */\n async drain() {\n const start = Date.now();\n this.#lc.info?.(`draining ${this.#viewSyncers.size} view-syncers`);\n\n this.#drainCoordinator.drainNextIn(0);\n\n while (this.#viewSyncers.size) {\n await this.#drainCoordinator.forceDrainTimeout;\n\n // Pick an arbitrary view syncer to force drain.\n for (const vs of this.#viewSyncers.getServices()) {\n this.#lc.debug?.(`draining view-syncer ${vs.id} (forced)`);\n // When this drain or an elective drain completes, the forceDrainTimeout will\n // resolve after the next drain interval.\n void vs.stop();\n break;\n }\n }\n this.#lc.info?.(`finished draining (${Date.now() - start} ms)`);\n }\n\n stop() {\n this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n}\n"],"mappings":"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"}
@@ -69,23 +69,23 @@ export interface Connection {
69
69
  * Resumes the connection from a terminal state.
70
70
  *
71
71
  * If called when not in a terminal state, this method does nothing.
72
+ * To clear existing auth credentials, create a new Zero instance with `auth` omitted.
72
73
  *
73
74
  * @param opts - Optional connection options
74
75
  * @param opts.auth - Token to use for authentication. If provided, this overrides
75
76
  * the stored auth credential for this connection attempt.
76
- * If `null` or `undefined`, the stored auth credential is cleared.
77
77
  * @returns A promise that resolves once the connection state has transitioned to connecting.
78
78
  */
79
79
  connect(opts?: {
80
- auth: string | null | undefined;
80
+ auth: string;
81
81
  }): Promise<void>;
82
82
  }
83
83
  export declare class ConnectionImpl implements Connection {
84
84
  #private;
85
- constructor(connectionManager: ConnectionManager, lc: LogContext, setAuth: (auth: string | null | undefined) => void);
85
+ constructor(connectionManager: ConnectionManager, lc: LogContext, setAuth: (auth: string) => void);
86
86
  get state(): Source<ConnectionState>;
87
87
  connect(opts?: {
88
- auth: string | null | undefined;
88
+ auth: string;
89
89
  }): Promise<void>;
90
90
  }
91
91
  export declare class ConnectionSource extends Subscribable<ConnectionState> implements Source<ConnectionState> {
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAC,YAAY,EAAC,MAAM,qCAAqC,CAAC;AAGjE,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;CACnB,GACD;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EACF;QACE,IAAI,EAAE,QAAQ,CAAC;QACf,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GACD;QACE,IAAI,EAAE,OAAO,CAAC;QACd,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GACD;QACE,IAAI,EAAE,YAAY,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACP,GACD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpB;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACrD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC;IAExC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;KAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED,qBAAa,cAAe,YAAW,UAAU;;gBAO7C,iBAAiB,EAAE,iBAAiB,EACpC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,KAAK,IAAI;IAQpD,IAAI,KAAK,IAAI,MAAM,CAAC,eAAe,CAAC,CAEnC;IAEK,OAAO,CAAC,IAAI,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;KAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAwCvE;AAED,qBAAa,gBACX,SAAQ,YAAY,CAAC,eAAe,CACpC,YAAW,MAAM,CAAC,eAAe,CAAC;;gBAItB,iBAAiB,EAAE,iBAAiB;IAchD,IAAI,OAAO,IAAI,eAAe,CAE7B;CA0DF"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAC,YAAY,EAAC,MAAM,qCAAqC,CAAC;AAGjE,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;CACnB,GACD;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EACF;QACE,IAAI,EAAE,QAAQ,CAAC;QACf,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GACD;QACE,IAAI,EAAE,OAAO,CAAC;QACd,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GACD;QACE,IAAI,EAAE,YAAY,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACP,GACD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpB;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACrD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC;IAExC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,qBAAa,cAAe,YAAW,UAAU;;gBAO7C,iBAAiB,EAAE,iBAAiB,EACpC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;IAQjC,IAAI,KAAK,IAAI,MAAM,CAAC,eAAe,CAAC,CAEnC;IAEK,OAAO,CAAC,IAAI,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAwCpD;AAED,qBAAa,gBACX,SAAQ,YAAY,CAAC,eAAe,CACpC,YAAW,MAAM,CAAC,eAAe,CAAC;;gBAItB,iBAAiB,EAAE,iBAAiB;IAchD,IAAI,OAAO,IAAI,eAAe,CAE7B;CA0DF"}
@@ -1 +1 @@
1
- {"version":3,"file":"connection.js","names":["#connectionManager","#lc","#source","#setAuth","#state","#mapConnectionManagerState"],"sources":["../../../../../zero-client/src/client/connection.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport {Subscribable} from '../../../shared/src/subscribable.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ClientErrorKind} from './client-error-kind.ts';\nimport type {\n ConnectionManager,\n ConnectionManagerState,\n} from './connection-manager.ts';\nimport {ConnectionStatus} from './connection-status.ts';\n\n/**\n * The current connection state of the Zero instance. One of the following states:\n *\n * - `connecting`: The client is actively trying to connect every 5 seconds.\n * - `disconnected`: The client is now in an \"offline\" state. It will continue\n * to try to connect every 5 seconds.\n * - `connected`: The client has opened a successful connection to the server.\n * - `needs-auth`: Authentication is invalid or expired. No connection retries will be made\n * until the host application calls `connect()`.\n * - `error`: A fatal error occurred. No connection retries will be made until the host\n * application calls `connect()` again.\n * - `closed`: The client was shut down (for example via `zero.close()`). This is\n * a terminal state, and a new Zero instance must be created to reconnect.\n */\nexport type ConnectionState =\n | {\n name: 'disconnected';\n reason: string;\n }\n | {\n name: 'connecting';\n reason?: string;\n }\n | {\n name: 'connected';\n }\n | {\n name: 'needs-auth';\n reason:\n | {\n type: 'mutate';\n status: 401 | 403;\n body?: string;\n }\n | {\n type: 'query';\n status: 401 | 403;\n body?: string;\n }\n | {\n type: 'zero-cache';\n reason: string;\n };\n }\n | {\n name: 'error';\n reason: string;\n }\n | {\n name: 'closed';\n reason: string;\n };\n\nexport interface Source<T> {\n /**\n * The current state value.\n */\n readonly current: T;\n\n /**\n * Subscribe to state changes.\n *\n * @param listener - Called when the state changes with the new state value.\n * @returns A function to unsubscribe from state changes.\n */\n subscribe(listener: (state: T) => void): () => void;\n}\n\n/**\n * Connection API for managing Zero's connection lifecycle.\n */\nexport interface Connection {\n /**\n * The current connection state as a subscribable value.\n */\n readonly state: Source<ConnectionState>;\n\n /**\n * Resumes the connection from a terminal state.\n *\n * If called when not in a terminal state, this method does nothing.\n *\n * @param opts - Optional connection options\n * @param opts.auth - Token to use for authentication. If provided, this overrides\n * the stored auth credential for this connection attempt.\n * If `null` or `undefined`, the stored auth credential is cleared.\n * @returns A promise that resolves once the connection state has transitioned to connecting.\n */\n connect(opts?: {auth: string | null | undefined}): Promise<void>;\n}\n\nexport class ConnectionImpl implements Connection {\n readonly #connectionManager: ConnectionManager;\n readonly #lc: LogContext;\n readonly #source: ConnectionSource;\n readonly #setAuth: (auth: string | null | undefined) => void;\n\n constructor(\n connectionManager: ConnectionManager,\n lc: LogContext,\n setAuth: (auth: string | null | undefined) => void,\n ) {\n this.#connectionManager = connectionManager;\n this.#lc = lc;\n this.#source = new ConnectionSource(connectionManager);\n this.#setAuth = setAuth;\n }\n\n get state(): Source<ConnectionState> {\n return this.#source;\n }\n\n async connect(opts?: {auth: string | null | undefined}): Promise<void> {\n const lc = this.#lc.withContext('connect');\n\n if (opts && 'auth' in opts) {\n lc.debug?.('Updating auth credential from connect()');\n this.#setAuth(opts.auth);\n }\n\n // if the connection is disconnected due to a missing cacheURL, we don't allow a reconnect\n if (\n this.#connectionManager.state.name === ConnectionStatus.Disconnected &&\n this.#connectionManager.state.reason.kind ===\n ClientErrorKind.NoSocketOrigin\n ) {\n lc.error?.(\n 'connect() called but the connection is disconnected due to a missing cacheURL. No reconnect will be attempted.',\n );\n return;\n }\n\n // only allow connect() to be called from a terminal state\n if (!this.#connectionManager.isInTerminalState()) {\n lc.debug?.(\n 'connect() called but not in a terminal state. Current state:',\n this.#connectionManager.state.name,\n );\n return;\n }\n\n lc.info?.(\n `Resuming connection from state: ${this.#connectionManager.state.name}`,\n );\n\n this.#connectionManager.requestConnect();\n if (this.#connectionManager.state.name === ConnectionStatus.Connecting) {\n return;\n }\n\n await this.#connectionManager.waitForStateChange();\n }\n}\n\nexport class ConnectionSource\n extends Subscribable<ConnectionState>\n implements Source<ConnectionState>\n{\n #state: ConnectionState;\n\n constructor(connectionManager: ConnectionManager) {\n super();\n this.#state = this.#mapConnectionManagerState(connectionManager.state);\n\n // Subscribe to ConnectionManager immediately to keep #state in sync.\n // This ensures `current` always returns the correct state, even if\n // external code hasn't subscribed yet (fixes race condition where\n // connection completes before React subscribes).\n connectionManager.subscribe(state => {\n this.#state = this.#mapConnectionManagerState(state);\n this.notify(this.#state);\n });\n }\n\n get current(): ConnectionState {\n return this.#state;\n }\n\n #mapConnectionManagerState(state: ConnectionManagerState): ConnectionState {\n switch (state.name) {\n case ConnectionStatus.Closed:\n return {\n name: 'closed',\n reason: state.reason.message,\n };\n case ConnectionStatus.Connected:\n return {\n name: 'connected',\n };\n case ConnectionStatus.Connecting:\n return {\n name: 'connecting',\n ...(state.reason?.message ? {reason: state.reason.message} : {}),\n };\n case ConnectionStatus.Disconnected:\n return {\n name: 'disconnected',\n reason: state.reason.message,\n };\n case ConnectionStatus.Error:\n return {\n name: 'error',\n reason: state.reason.message,\n };\n case ConnectionStatus.NeedsAuth:\n return {\n name: 'needs-auth',\n reason:\n state.reason.errorBody.kind === ErrorKind.PushFailed\n ? {\n type: 'mutate',\n status: state.reason.errorBody.status,\n ...(state.reason.errorBody.bodyPreview\n ? {body: state.reason.errorBody.bodyPreview}\n : {}),\n }\n : state.reason.errorBody.kind === ErrorKind.TransformFailed\n ? {\n type: 'query',\n status: state.reason.errorBody.status,\n ...(state.reason.errorBody.bodyPreview\n ? {body: state.reason.errorBody.bodyPreview}\n : {}),\n }\n : {\n type: 'zero-cache',\n reason: state.reason.message,\n },\n };\n\n default:\n unreachable(state);\n }\n }\n}\n"],"mappings":";;;;;;AAsGA,IAAa,iBAAb,MAAkD;CAChD;CACA;CACA;CACA;CAEA,YACE,mBACA,IACA,SACA;AACA,QAAA,oBAA0B;AAC1B,QAAA,KAAW;AACX,QAAA,SAAe,IAAI,iBAAiB,kBAAkB;AACtD,QAAA,UAAgB;;CAGlB,IAAI,QAAiC;AACnC,SAAO,MAAA;;CAGT,MAAM,QAAQ,MAAyD;EACrE,MAAM,KAAK,MAAA,GAAS,YAAY,UAAU;AAE1C,MAAI,QAAQ,UAAU,MAAM;AAC1B,MAAG,QAAQ,0CAA0C;AACrD,SAAA,QAAc,KAAK,KAAK;;AAI1B,MACE,MAAA,kBAAwB,MAAM,SAAS,kBACvC,MAAA,kBAAwB,MAAM,OAAO,SACnC,kBACF;AACA,MAAG,QACD,iHACD;AACD;;AAIF,MAAI,CAAC,MAAA,kBAAwB,mBAAmB,EAAE;AAChD,MAAG,QACD,gEACA,MAAA,kBAAwB,MAAM,KAC/B;AACD;;AAGF,KAAG,OACD,mCAAmC,MAAA,kBAAwB,MAAM,OAClE;AAED,QAAA,kBAAwB,gBAAgB;AACxC,MAAI,MAAA,kBAAwB,MAAM,SAAS,aACzC;AAGF,QAAM,MAAA,kBAAwB,oBAAoB;;;AAItD,IAAa,mBAAb,cACU,aAEV;CACE;CAEA,YAAY,mBAAsC;AAChD,SAAO;AACP,QAAA,QAAc,MAAA,0BAAgC,kBAAkB,MAAM;AAMtE,oBAAkB,WAAU,UAAS;AACnC,SAAA,QAAc,MAAA,0BAAgC,MAAM;AACpD,QAAK,OAAO,MAAA,MAAY;IACxB;;CAGJ,IAAI,UAA2B;AAC7B,SAAO,MAAA;;CAGT,2BAA2B,OAAgD;AACzE,UAAQ,MAAM,MAAd;GACE,KAAK,OACH,QAAO;IACL,MAAM;IACN,QAAQ,MAAM,OAAO;IACtB;GACH,KAAK,UACH,QAAO,EACL,MAAM,aACP;GACH,KAAK,WACH,QAAO;IACL,MAAM;IACN,GAAI,MAAM,QAAQ,UAAU,EAAC,QAAQ,MAAM,OAAO,SAAQ,GAAG,EAAE;IAChE;GACH,KAAK,aACH,QAAO;IACL,MAAM;IACN,QAAQ,MAAM,OAAO;IACtB;GACH,KAAK,MACH,QAAO;IACL,MAAM;IACN,QAAQ,MAAM,OAAO;IACtB;GACH,KAAK,UACH,QAAO;IACL,MAAM;IACN,QACE,MAAM,OAAO,UAAU,SAAS,eAC5B;KACE,MAAM;KACN,QAAQ,MAAM,OAAO,UAAU;KAC/B,GAAI,MAAM,OAAO,UAAU,cACvB,EAAC,MAAM,MAAM,OAAO,UAAU,aAAY,GAC1C,EAAE;KACP,GACD,MAAM,OAAO,UAAU,SAAS,oBAC9B;KACE,MAAM;KACN,QAAQ,MAAM,OAAO,UAAU;KAC/B,GAAI,MAAM,OAAO,UAAU,cACvB,EAAC,MAAM,MAAM,OAAO,UAAU,aAAY,GAC1C,EAAE;KACP,GACD;KACE,MAAM;KACN,QAAQ,MAAM,OAAO;KACtB;IACV;GAEH,QACE,aAAY,MAAM"}
1
+ {"version":3,"file":"connection.js","names":["#connectionManager","#lc","#source","#setAuth","#state","#mapConnectionManagerState"],"sources":["../../../../../zero-client/src/client/connection.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport {Subscribable} from '../../../shared/src/subscribable.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ClientErrorKind} from './client-error-kind.ts';\nimport type {\n ConnectionManager,\n ConnectionManagerState,\n} from './connection-manager.ts';\nimport {ConnectionStatus} from './connection-status.ts';\n\n/**\n * The current connection state of the Zero instance. One of the following states:\n *\n * - `connecting`: The client is actively trying to connect every 5 seconds.\n * - `disconnected`: The client is now in an \"offline\" state. It will continue\n * to try to connect every 5 seconds.\n * - `connected`: The client has opened a successful connection to the server.\n * - `needs-auth`: Authentication is invalid or expired. No connection retries will be made\n * until the host application calls `connect()`.\n * - `error`: A fatal error occurred. No connection retries will be made until the host\n * application calls `connect()` again.\n * - `closed`: The client was shut down (for example via `zero.close()`). This is\n * a terminal state, and a new Zero instance must be created to reconnect.\n */\nexport type ConnectionState =\n | {\n name: 'disconnected';\n reason: string;\n }\n | {\n name: 'connecting';\n reason?: string;\n }\n | {\n name: 'connected';\n }\n | {\n name: 'needs-auth';\n reason:\n | {\n type: 'mutate';\n status: 401 | 403;\n body?: string;\n }\n | {\n type: 'query';\n status: 401 | 403;\n body?: string;\n }\n | {\n type: 'zero-cache';\n reason: string;\n };\n }\n | {\n name: 'error';\n reason: string;\n }\n | {\n name: 'closed';\n reason: string;\n };\n\nexport interface Source<T> {\n /**\n * The current state value.\n */\n readonly current: T;\n\n /**\n * Subscribe to state changes.\n *\n * @param listener - Called when the state changes with the new state value.\n * @returns A function to unsubscribe from state changes.\n */\n subscribe(listener: (state: T) => void): () => void;\n}\n\n/**\n * Connection API for managing Zero's connection lifecycle.\n */\nexport interface Connection {\n /**\n * The current connection state as a subscribable value.\n */\n readonly state: Source<ConnectionState>;\n\n /**\n * Resumes the connection from a terminal state.\n *\n * If called when not in a terminal state, this method does nothing.\n * To clear existing auth credentials, create a new Zero instance with `auth` omitted.\n *\n * @param opts - Optional connection options\n * @param opts.auth - Token to use for authentication. If provided, this overrides\n * the stored auth credential for this connection attempt.\n * @returns A promise that resolves once the connection state has transitioned to connecting.\n */\n connect(opts?: {auth: string}): Promise<void>;\n}\n\nexport class ConnectionImpl implements Connection {\n readonly #connectionManager: ConnectionManager;\n readonly #lc: LogContext;\n readonly #source: ConnectionSource;\n readonly #setAuth: (auth: string) => void;\n\n constructor(\n connectionManager: ConnectionManager,\n lc: LogContext,\n setAuth: (auth: string) => void,\n ) {\n this.#connectionManager = connectionManager;\n this.#lc = lc;\n this.#source = new ConnectionSource(connectionManager);\n this.#setAuth = setAuth;\n }\n\n get state(): Source<ConnectionState> {\n return this.#source;\n }\n\n async connect(opts?: {auth: string}): Promise<void> {\n const lc = this.#lc.withContext('connect');\n\n if (opts && 'auth' in opts) {\n lc.debug?.('Updating auth credential from connect()');\n this.#setAuth(opts.auth);\n }\n\n // if the connection is disconnected due to a missing cacheURL, we don't allow a reconnect\n if (\n this.#connectionManager.state.name === ConnectionStatus.Disconnected &&\n this.#connectionManager.state.reason.kind ===\n ClientErrorKind.NoSocketOrigin\n ) {\n lc.error?.(\n 'connect() called but the connection is disconnected due to a missing cacheURL. No reconnect will be attempted.',\n );\n return;\n }\n\n // only allow connect() to be called from a terminal state\n if (!this.#connectionManager.isInTerminalState()) {\n lc.debug?.(\n 'connect() called but not in a terminal state. Current state:',\n this.#connectionManager.state.name,\n );\n return;\n }\n\n lc.info?.(\n `Resuming connection from state: ${this.#connectionManager.state.name}`,\n );\n\n this.#connectionManager.requestConnect();\n if (this.#connectionManager.state.name === ConnectionStatus.Connecting) {\n return;\n }\n\n await this.#connectionManager.waitForStateChange();\n }\n}\n\nexport class ConnectionSource\n extends Subscribable<ConnectionState>\n implements Source<ConnectionState>\n{\n #state: ConnectionState;\n\n constructor(connectionManager: ConnectionManager) {\n super();\n this.#state = this.#mapConnectionManagerState(connectionManager.state);\n\n // Subscribe to ConnectionManager immediately to keep #state in sync.\n // This ensures `current` always returns the correct state, even if\n // external code hasn't subscribed yet (fixes race condition where\n // connection completes before React subscribes).\n connectionManager.subscribe(state => {\n this.#state = this.#mapConnectionManagerState(state);\n this.notify(this.#state);\n });\n }\n\n get current(): ConnectionState {\n return this.#state;\n }\n\n #mapConnectionManagerState(state: ConnectionManagerState): ConnectionState {\n switch (state.name) {\n case ConnectionStatus.Closed:\n return {\n name: 'closed',\n reason: state.reason.message,\n };\n case ConnectionStatus.Connected:\n return {\n name: 'connected',\n };\n case ConnectionStatus.Connecting:\n return {\n name: 'connecting',\n ...(state.reason?.message ? {reason: state.reason.message} : {}),\n };\n case ConnectionStatus.Disconnected:\n return {\n name: 'disconnected',\n reason: state.reason.message,\n };\n case ConnectionStatus.Error:\n return {\n name: 'error',\n reason: state.reason.message,\n };\n case ConnectionStatus.NeedsAuth:\n return {\n name: 'needs-auth',\n reason:\n state.reason.errorBody.kind === ErrorKind.PushFailed\n ? {\n type: 'mutate',\n status: state.reason.errorBody.status,\n ...(state.reason.errorBody.bodyPreview\n ? {body: state.reason.errorBody.bodyPreview}\n : {}),\n }\n : state.reason.errorBody.kind === ErrorKind.TransformFailed\n ? {\n type: 'query',\n status: state.reason.errorBody.status,\n ...(state.reason.errorBody.bodyPreview\n ? {body: state.reason.errorBody.bodyPreview}\n : {}),\n }\n : {\n type: 'zero-cache',\n reason: state.reason.message,\n },\n };\n\n default:\n unreachable(state);\n }\n }\n}\n"],"mappings":";;;;;;AAsGA,IAAa,iBAAb,MAAkD;CAChD;CACA;CACA;CACA;CAEA,YACE,mBACA,IACA,SACA;AACA,QAAA,oBAA0B;AAC1B,QAAA,KAAW;AACX,QAAA,SAAe,IAAI,iBAAiB,kBAAkB;AACtD,QAAA,UAAgB;;CAGlB,IAAI,QAAiC;AACnC,SAAO,MAAA;;CAGT,MAAM,QAAQ,MAAsC;EAClD,MAAM,KAAK,MAAA,GAAS,YAAY,UAAU;AAE1C,MAAI,QAAQ,UAAU,MAAM;AAC1B,MAAG,QAAQ,0CAA0C;AACrD,SAAA,QAAc,KAAK,KAAK;;AAI1B,MACE,MAAA,kBAAwB,MAAM,SAAS,kBACvC,MAAA,kBAAwB,MAAM,OAAO,SACnC,kBACF;AACA,MAAG,QACD,iHACD;AACD;;AAIF,MAAI,CAAC,MAAA,kBAAwB,mBAAmB,EAAE;AAChD,MAAG,QACD,gEACA,MAAA,kBAAwB,MAAM,KAC/B;AACD;;AAGF,KAAG,OACD,mCAAmC,MAAA,kBAAwB,MAAM,OAClE;AAED,QAAA,kBAAwB,gBAAgB;AACxC,MAAI,MAAA,kBAAwB,MAAM,SAAS,aACzC;AAGF,QAAM,MAAA,kBAAwB,oBAAoB;;;AAItD,IAAa,mBAAb,cACU,aAEV;CACE;CAEA,YAAY,mBAAsC;AAChD,SAAO;AACP,QAAA,QAAc,MAAA,0BAAgC,kBAAkB,MAAM;AAMtE,oBAAkB,WAAU,UAAS;AACnC,SAAA,QAAc,MAAA,0BAAgC,MAAM;AACpD,QAAK,OAAO,MAAA,MAAY;IACxB;;CAGJ,IAAI,UAA2B;AAC7B,SAAO,MAAA;;CAGT,2BAA2B,OAAgD;AACzE,UAAQ,MAAM,MAAd;GACE,KAAK,OACH,QAAO;IACL,MAAM;IACN,QAAQ,MAAM,OAAO;IACtB;GACH,KAAK,UACH,QAAO,EACL,MAAM,aACP;GACH,KAAK,WACH,QAAO;IACL,MAAM;IACN,GAAI,MAAM,QAAQ,UAAU,EAAC,QAAQ,MAAM,OAAO,SAAQ,GAAG,EAAE;IAChE;GACH,KAAK,aACH,QAAO;IACL,MAAM;IACN,QAAQ,MAAM,OAAO;IACtB;GACH,KAAK,MACH,QAAO;IACL,MAAM;IACN,QAAQ,MAAM,OAAO;IACtB;GACH,KAAK,UACH,QAAO;IACL,MAAM;IACN,QACE,MAAM,OAAO,UAAU,SAAS,eAC5B;KACE,MAAM;KACN,QAAQ,MAAM,OAAO,UAAU;KAC/B,GAAI,MAAM,OAAO,UAAU,cACvB,EAAC,MAAM,MAAM,OAAO,UAAU,aAAY,GAC1C,EAAE;KACP,GACD,MAAM,OAAO,UAAU,SAAS,oBAC9B;KACE,MAAM;KACN,QAAQ,MAAM,OAAO,UAAU;KAC/B,GAAI,MAAM,OAAO,UAAU,cACvB,EAAC,MAAM,MAAM,OAAO,UAAU,aAAY,GAC1C,EAAE;KACP,GACD;KACE,MAAM;KACN,QAAQ,MAAM,OAAO;KACtB;IACV;GAEH,QACE,aAAY,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"http-string.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/http-string.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAAG,OAAO,EAAE,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC;AAEvD,MAAM,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC;AAEnD,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,CAEpD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ,GAAG,UAAU,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,UAAU,CAEvE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,QAAQ,CAEnE;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,GAAG,QAAQ,EACxD,GAAG,EAAE,CAAC,EACN,QAAQ,EAAE,IAAI,MAAM,EAAE,GACrB,CAAC,CAEH"}
1
+ {"version":3,"file":"http-string.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/http-string.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAAG,OAAO,EAAE,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC;AAEvD,MAAM,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC;AAEnD,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,CAEpD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ,GAAG,UAAU,CAEtD;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,UAAU,CAEvE;AAID,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,QAAQ,CAEnE;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,GAAG,QAAQ,EACxD,GAAG,EAAE,CAAC,EACN,QAAQ,EAAE,IAAI,MAAM,EAAE,GACrB,CAAC,CAEH"}
@@ -1 +1 @@
1
- {"version":3,"file":"http-string.js","names":[],"sources":["../../../../../zero-client/src/client/http-string.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\n\nexport type HTTPString = `http${'' | 's'}://${string}`;\n\nexport type WSString = `ws${'' | 's'}://${string}`;\n\nexport function toWSString(url: HTTPString): WSString {\n return ('ws' + url.slice(4)) as WSString;\n}\n\nexport function toHTTPString(url: WSString): HTTPString {\n return ('http' + url.slice(2)) as HTTPString;\n}\n\nexport function assertHTTPString(url: string): asserts url is HTTPString {\n assert(/^https?:\\/\\//.test(url), () => `Expected HTTP(S) URL, got \"${url}\"`);\n}\n\nexport function assertWSString(url: string): asserts url is WSString {\n assert(/^wss?:\\/\\//.test(url), () => `Expected WS(S) URL, got \"${url}\"`);\n}\n\nexport function appendPath<T extends HTTPString | WSString>(\n url: T,\n toAppend: `/${string}`,\n): T {\n return (url + (url.endsWith('/') ? toAppend.substring(1) : toAppend)) as T;\n}\n"],"mappings":";AAMA,SAAgB,WAAW,KAA2B;AACpD,QAAQ,OAAO,IAAI,MAAM,EAAE;;AAe7B,SAAgB,WACd,KACA,UACG;AACH,QAAQ,OAAO,IAAI,SAAS,IAAI,GAAG,SAAS,UAAU,EAAE,GAAG"}
1
+ {"version":3,"file":"http-string.js","names":[],"sources":["../../../../../zero-client/src/client/http-string.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\n\nexport type HTTPString = `http${'' | 's'}://${string}`;\n\nexport type WSString = `ws${'' | 's'}://${string}`;\n\nexport function toWSString(url: HTTPString): WSString {\n return ('ws' + url.slice(4)) as WSString;\n}\n\nexport function toHTTPString(url: WSString): HTTPString {\n return ('http' + url.slice(2)) as HTTPString;\n}\n\nconst httpsRe = /^https?:\\/\\//;\n\nexport function assertHTTPString(url: string): asserts url is HTTPString {\n assert(httpsRe.test(url), () => `Expected HTTP(S) URL, got \"${url}\"`);\n}\n\nconst wssRe = /^wss?:\\/\\//;\n\nexport function assertWSString(url: string): asserts url is WSString {\n assert(wssRe.test(url), () => `Expected WS(S) URL, got \"${url}\"`);\n}\n\nexport function appendPath<T extends HTTPString | WSString>(\n url: T,\n toAppend: `/${string}`,\n): T {\n return (url + (url.endsWith('/') ? toAppend.substring(1) : toAppend)) as T;\n}\n"],"mappings":";AAMA,SAAgB,WAAW,KAA2B;AACpD,QAAQ,OAAO,IAAI,MAAM,EAAE;;AAmB7B,SAAgB,WACd,KACA,UACG;AACH,QAAQ,OAAO,IAAI,SAAS,IAAI,GAAG,SAAS,UAAU,EAAE,GAAG"}
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,YAAY,CAAC;AAOpB,eAAO,MAAM,qBAAqB,QAAa,CAAC;AAEhD,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AASxC,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,2CAEzD;AAYD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAcnE;AAED,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;AAEjE,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,EAAE,EAAE,UAAU,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,qBAAa,aAAa;;gBAOZ,IAAI,EAAE,oBAAoB;IAiCtC,QAAQ,CAAC,eAAe,QAEtB;IAMF,QAAQ,CAAC,gBAAgB,QAKvB;IAqCF,YAAY,CAAC,eAAe,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM;IAOlE,gCAAgC;IAmBhC,eAAe,CAAC,MAAM,EAAE,SAAS;IAOjC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAM;IAIvB,KAAK;IA4BX,IAAI;CAaL;AAMD,6DAA6D;AAC7D,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AACF;;;;;GAKG;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAMvC,KAAK,SAAS,GAAG;IACf,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC,GAAG,SAAS,CAAC;CACxD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAM,YAAW,SAAS;;gBAIzB,IAAI,EAAE,MAAM;IAIxB,GAAG,CAAC,KAAK,EAAE,MAAM;IAIjB,GAAG;IAIH,KAAK;IAIL,KAAK;;;;CASN;AAMD;;;;;;;;;;;;GAYG;AACH,qBAAa,KAAM,YAAW,SAAS;;gBAKzB,MAAM,EAAE,MAAM,EAAE,YAAY,UAAQ;IAKhD,GAAG,CAAC,KAAK,EAAE,MAAM;IAIjB,GAAG;IAIH,KAAK;IAIL,KAAK;;;;CAYN"}
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,YAAY,CAAC;AAOpB,eAAO,MAAM,qBAAqB,QAAa,CAAC;AAEhD,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AASxC,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,2CAEzD;AAWD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAcnE;AAED,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;AAEjE,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,EAAE,EAAE,UAAU,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,qBAAa,aAAa;;gBAOZ,IAAI,EAAE,oBAAoB;IAiCtC,QAAQ,CAAC,eAAe,QAEtB;IAMF,QAAQ,CAAC,gBAAgB,QAKvB;IAqCF,YAAY,CAAC,eAAe,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM;IAOlE,gCAAgC;IAmBhC,eAAe,CAAC,MAAM,EAAE,SAAS;IAOjC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAM;IAIvB,KAAK;IA4BX,IAAI;CAaL;AAMD,6DAA6D;AAC7D,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AACF;;;;;GAKG;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAMvC,KAAK,SAAS,GAAG;IACf,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC,GAAG,SAAS,CAAC;CACxD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAM,YAAW,SAAS;;gBAIzB,IAAI,EAAE,MAAM;IAIxB,GAAG,CAAC,KAAK,EAAE,MAAM;IAIjB,GAAG;IAIH,KAAK;IAIL,KAAK;;;;CASN;AAMD;;;;;;;;;;;;GAYG;AACH,qBAAa,KAAM,YAAW,SAAS;;gBAKzB,MAAM,EAAE,MAAM,EAAE,YAAY,UAAQ;IAKhD,GAAG,CAAC,KAAK,EAAE,MAAM;IAIjB,GAAG;IAIH,KAAK;IAIL,KAAK;;;;CAYN"}