@rocicorp/zero 1.6.0-canary.11 → 1.6.0-canary.13

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 (659) hide show
  1. package/README.md +3 -28
  2. package/out/_virtual/{_@oxc-project_runtime@0.130.0 → _@oxc-project_runtime@0.122.0}/helpers/usingCtx.js +1 -1
  3. package/out/_virtual/_rolldown/runtime.js +1 -12
  4. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  5. package/out/analyze-query/src/bin-analyze.js +1 -6
  6. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  7. package/out/analyze-query/src/bin-transform.js.map +1 -1
  8. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  9. package/out/ast-to-zql/src/bin.js.map +1 -1
  10. package/out/ast-to-zql/src/format.js.map +1 -1
  11. package/out/datadog/src/datadog-log-sink.js.map +1 -1
  12. package/out/otel/src/enabled.js.map +1 -1
  13. package/out/otel/src/log-options.js.map +1 -1
  14. package/out/otel/src/maybe-time.js.map +1 -1
  15. package/out/otel/src/span.js.map +1 -1
  16. package/out/replicache/src/async-iterable-to-array.js.map +1 -1
  17. package/out/replicache/src/bg-interval.js.map +1 -1
  18. package/out/replicache/src/btree/diff.js.map +1 -1
  19. package/out/replicache/src/btree/node.js.map +1 -1
  20. package/out/replicache/src/btree/read.js.map +1 -1
  21. package/out/replicache/src/btree/splice.js.map +1 -1
  22. package/out/replicache/src/btree/write.js +3 -6
  23. package/out/replicache/src/btree/write.js.map +1 -1
  24. package/out/replicache/src/call-default-fetch.js.map +1 -1
  25. package/out/replicache/src/connection-loop-delegates.js.map +1 -1
  26. package/out/replicache/src/connection-loop.js.map +1 -1
  27. package/out/replicache/src/cookies.js.map +1 -1
  28. package/out/replicache/src/dag/chunk.js.map +1 -1
  29. package/out/replicache/src/dag/gc.js.map +1 -1
  30. package/out/replicache/src/dag/key.js.map +1 -1
  31. package/out/replicache/src/dag/lazy-store.js.map +1 -1
  32. package/out/replicache/src/dag/store-impl.js.map +1 -1
  33. package/out/replicache/src/dag/store.js.map +1 -1
  34. package/out/replicache/src/dag/visitor.js.map +1 -1
  35. package/out/replicache/src/db/commit.js.map +1 -1
  36. package/out/replicache/src/db/index.js.map +1 -1
  37. package/out/replicache/src/db/read.js.map +1 -1
  38. package/out/replicache/src/db/rebase.js.map +1 -1
  39. package/out/replicache/src/db/write.js.map +1 -1
  40. package/out/replicache/src/deleted-clients.js.map +1 -1
  41. package/out/replicache/src/error-responses.js.map +1 -1
  42. package/out/replicache/src/frozen-json.js.map +1 -1
  43. package/out/replicache/src/get-default-puller.js.map +1 -1
  44. package/out/replicache/src/get-default-pusher.js.map +1 -1
  45. package/out/replicache/src/get-kv-store-provider.js.map +1 -1
  46. package/out/replicache/src/hash.js.map +1 -1
  47. package/out/replicache/src/http-request-info.js.map +1 -1
  48. package/out/replicache/src/index-defs.js.map +1 -1
  49. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  50. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +1 -1
  51. package/out/replicache/src/kv/idb-store.js.map +1 -1
  52. package/out/replicache/src/kv/mem-store.js.map +1 -1
  53. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  54. package/out/replicache/src/kv/read-impl.js.map +1 -1
  55. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  56. package/out/replicache/src/kv/sqlite-store.js +1 -4
  57. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  58. package/out/replicache/src/kv/throw-if-closed.js.map +1 -1
  59. package/out/replicache/src/kv/write-impl-base.js.map +1 -1
  60. package/out/replicache/src/kv/write-impl.js.map +1 -1
  61. package/out/replicache/src/lazy.js.map +1 -1
  62. package/out/replicache/src/log-options.js.map +1 -1
  63. package/out/replicache/src/make-idb-name.js.map +1 -1
  64. package/out/replicache/src/new-client-channel.js.map +1 -1
  65. package/out/replicache/src/on-persist-channel.js.map +1 -1
  66. package/out/replicache/src/patch-operation.js.map +1 -1
  67. package/out/replicache/src/pending-mutations.js.map +1 -1
  68. package/out/replicache/src/persist/client-gc.js.map +1 -1
  69. package/out/replicache/src/persist/client-group-gc.js.map +1 -1
  70. package/out/replicache/src/persist/client-groups.js +0 -40
  71. package/out/replicache/src/persist/client-groups.js.map +1 -1
  72. package/out/replicache/src/persist/clients.js +0 -28
  73. package/out/replicache/src/persist/clients.js.map +1 -1
  74. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  75. package/out/replicache/src/persist/gather-mem-only-visitor.js.map +1 -1
  76. package/out/replicache/src/persist/gather-not-cached-visitor.js.map +1 -1
  77. package/out/replicache/src/persist/heartbeat.js.map +1 -1
  78. package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
  79. package/out/replicache/src/persist/idb-databases-store.js.map +1 -1
  80. package/out/replicache/src/persist/make-client-id.js.map +1 -1
  81. package/out/replicache/src/persist/persist.js.map +1 -1
  82. package/out/replicache/src/persist/refresh.js.map +1 -1
  83. package/out/replicache/src/process-scheduler.js.map +1 -1
  84. package/out/replicache/src/pusher.js.map +1 -1
  85. package/out/replicache/src/replicache-impl.js.map +1 -1
  86. package/out/replicache/src/report-error.js.map +1 -1
  87. package/out/replicache/src/request-idle.js.map +1 -1
  88. package/out/replicache/src/scan-iterator.js.map +1 -1
  89. package/out/replicache/src/scan-options.js.map +1 -1
  90. package/out/replicache/src/set-interval-with-signal.js.map +1 -1
  91. package/out/replicache/src/subscriptions.js.map +1 -1
  92. package/out/replicache/src/sync/diff.js.map +1 -1
  93. package/out/replicache/src/sync/ids.js.map +1 -1
  94. package/out/replicache/src/sync/patch.js.map +1 -1
  95. package/out/replicache/src/sync/pull-error.js.map +1 -1
  96. package/out/replicache/src/sync/pull.js.map +1 -1
  97. package/out/replicache/src/sync/push.js.map +1 -1
  98. package/out/replicache/src/sync/request-id.js.map +1 -1
  99. package/out/replicache/src/to-error.js.map +1 -1
  100. package/out/replicache/src/transaction-closed-error.js.map +1 -1
  101. package/out/replicache/src/transactions.js.map +1 -1
  102. package/out/replicache/src/with-transactions.js.map +1 -1
  103. package/out/shared/src/abort-error.js.map +1 -1
  104. package/out/shared/src/arrays.js.map +1 -1
  105. package/out/shared/src/asserts.js.map +1 -1
  106. package/out/shared/src/bigint-json.js.map +1 -1
  107. package/out/shared/src/binary-search.js.map +1 -1
  108. package/out/shared/src/broadcast-channel.js.map +1 -1
  109. package/out/shared/src/browser-env.js.map +1 -1
  110. package/out/shared/src/btree-set.js.map +1 -1
  111. package/out/shared/src/cache.js.map +1 -1
  112. package/out/shared/src/centroid.js.map +1 -1
  113. package/out/shared/src/custom-key-map.js.map +1 -1
  114. package/out/shared/src/custom-key-set.js.map +1 -1
  115. package/out/shared/src/deep-clone.js.map +1 -1
  116. package/out/shared/src/deep-merge.js.map +1 -1
  117. package/out/shared/src/document-visible.js.map +1 -1
  118. package/out/shared/src/dotenv.js.map +1 -1
  119. package/out/shared/src/error.js.map +1 -1
  120. package/out/shared/src/hash.js.map +1 -1
  121. package/out/shared/src/iterables.js.map +1 -1
  122. package/out/shared/src/json-schema.js.map +1 -1
  123. package/out/shared/src/json.js.map +1 -1
  124. package/out/shared/src/logging-test-utils.js.map +1 -1
  125. package/out/shared/src/logging.js.map +1 -1
  126. package/out/shared/src/map.js.map +1 -1
  127. package/out/shared/src/must.js.map +1 -1
  128. package/out/shared/src/object-traversal.js.map +1 -1
  129. package/out/shared/src/objects.js.map +1 -1
  130. package/out/shared/src/options.js.map +1 -1
  131. package/out/shared/src/parse-big-int.js.map +1 -1
  132. package/out/shared/src/promise-race.js.map +1 -1
  133. package/out/shared/src/queue.d.ts.map +1 -1
  134. package/out/shared/src/queue.js +21 -15
  135. package/out/shared/src/queue.js.map +1 -1
  136. package/out/shared/src/rand.js.map +1 -1
  137. package/out/shared/src/random-uint64.js.map +1 -1
  138. package/out/shared/src/random-values.js.map +1 -1
  139. package/out/shared/src/record-proxy.js.map +1 -1
  140. package/out/shared/src/resolved-promises.js.map +1 -1
  141. package/out/shared/src/sentinels.js.map +1 -1
  142. package/out/shared/src/set-utils.js.map +1 -1
  143. package/out/shared/src/size-of-value.js.map +1 -1
  144. package/out/shared/src/sleep.js.map +1 -1
  145. package/out/shared/src/sorted-entries.js.map +1 -1
  146. package/out/shared/src/string-compare.js.map +1 -1
  147. package/out/shared/src/subscribable.js.map +1 -1
  148. package/out/shared/src/tdigest-schema.js.map +1 -1
  149. package/out/shared/src/tdigest.js.map +1 -1
  150. package/out/shared/src/valita.js.map +1 -1
  151. package/out/z2s/src/compiler.js.map +1 -1
  152. package/out/z2s/src/sql.js.map +1 -1
  153. package/out/zero/package.js +23 -23
  154. package/out/zero/package.js.map +1 -1
  155. package/out/zero/src/build-schema.js.map +1 -1
  156. package/out/zero/src/zero-cache-dev.js.map +1 -1
  157. package/out/zero/src/zero-out.js.map +1 -1
  158. package/out/zero-cache/src/auth/auth.js.map +1 -1
  159. package/out/zero-cache/src/auth/jwt.js.map +1 -1
  160. package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
  161. package/out/zero-cache/src/auth/read-authorizer.js.map +1 -1
  162. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  163. package/out/zero-cache/src/config/network.js.map +1 -1
  164. package/out/zero-cache/src/config/normalize.js.map +1 -1
  165. package/out/zero-cache/src/config/server-context.js.map +1 -1
  166. package/out/zero-cache/src/config/zero-config.js +0 -5
  167. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  168. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  169. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  170. package/out/zero-cache/src/db/create.js.map +1 -1
  171. package/out/zero-cache/src/db/delete-lite-db.js.map +1 -1
  172. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  173. package/out/zero-cache/src/db/migration-lite.js +0 -19
  174. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  175. package/out/zero-cache/src/db/migration.js +0 -19
  176. package/out/zero-cache/src/db/migration.js.map +1 -1
  177. package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -1
  178. package/out/zero-cache/src/db/pg-copy.js.map +1 -1
  179. package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
  180. package/out/zero-cache/src/db/pg-type-parser.js.map +1 -1
  181. package/out/zero-cache/src/db/run-transaction.js.map +1 -1
  182. package/out/zero-cache/src/db/specs.js.map +1 -1
  183. package/out/zero-cache/src/db/statements.js.map +1 -1
  184. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  185. package/out/zero-cache/src/db/warmup.js.map +1 -1
  186. package/out/zero-cache/src/observability/events.js.map +1 -1
  187. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  188. package/out/zero-cache/src/scripts/decommission.js.map +1 -1
  189. package/out/zero-cache/src/scripts/deploy-permissions.js.map +1 -1
  190. package/out/zero-cache/src/scripts/permissions.js.map +1 -1
  191. package/out/zero-cache/src/server/anonymous-otel-start.js +10 -11
  192. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  193. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  194. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  195. package/out/zero-cache/src/server/logging.js.map +1 -1
  196. package/out/zero-cache/src/server/main.js.map +1 -1
  197. package/out/zero-cache/src/server/mutator.js.map +1 -1
  198. package/out/zero-cache/src/server/otel-diag-logger.js.map +1 -1
  199. package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
  200. package/out/zero-cache/src/server/otel-start.js +1 -1
  201. package/out/zero-cache/src/server/otel-start.js.map +1 -1
  202. package/out/zero-cache/src/server/priority-op.js.map +1 -1
  203. package/out/zero-cache/src/server/reaper.js.map +1 -1
  204. package/out/zero-cache/src/server/replicator.js.map +1 -1
  205. package/out/zero-cache/src/server/runner/main.js.map +1 -1
  206. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  207. package/out/zero-cache/src/server/runner/runtime.js.map +1 -1
  208. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  209. package/out/zero-cache/src/server/shadow-syncer.js.map +1 -1
  210. package/out/zero-cache/src/server/syncer.js.map +1 -1
  211. package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
  212. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  213. package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
  214. package/out/zero-cache/src/services/analyze.js +2 -5
  215. package/out/zero-cache/src/services/analyze.js.map +1 -1
  216. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  217. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  218. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  219. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  220. package/out/zero-cache/src/services/change-source/pg/backfill-metadata.js.map +1 -1
  221. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  222. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  223. package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
  224. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  225. package/out/zero-cache/src/services/change-source/pg/logical-replication/binary-reader.js.map +1 -1
  226. package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js.map +1 -1
  227. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  228. package/out/zero-cache/src/services/change-source/pg/lsn.js.map +1 -1
  229. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  230. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  231. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  232. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  233. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  234. package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
  235. package/out/zero-cache/src/services/change-source/protocol/current/control.js.map +1 -1
  236. package/out/zero-cache/src/services/change-source/protocol/current/data.js +0 -2
  237. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  238. package/out/zero-cache/src/services/change-source/protocol/current/downstream.js.map +1 -1
  239. package/out/zero-cache/src/services/change-source/protocol/current/json.js.map +1 -1
  240. package/out/zero-cache/src/services/change-source/protocol/current/status.js.map +1 -1
  241. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  242. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  243. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  244. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  245. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  246. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  247. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  248. package/out/zero-cache/src/services/change-streamer/replica-monitor.js.map +1 -1
  249. package/out/zero-cache/src/services/change-streamer/schema/init.js +25 -21
  250. package/out/zero-cache/src/services/change-streamer/schema/init.js.map +1 -1
  251. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  252. package/out/zero-cache/src/services/change-streamer/snapshot.js +0 -15
  253. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  254. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  255. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  256. package/out/zero-cache/src/services/heapz.js.map +1 -1
  257. package/out/zero-cache/src/services/http-service.js.map +1 -1
  258. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  259. package/out/zero-cache/src/services/limiter/sliding-window-limiter.js.map +1 -1
  260. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  261. package/out/zero-cache/src/services/mutagen/error.js.map +1 -1
  262. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  263. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  264. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  265. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  266. package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
  267. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  268. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  269. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  270. package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -1
  271. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  272. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
  273. package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
  274. package/out/zero-cache/src/services/replicator/schema/table-metadata.js.map +1 -1
  275. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  276. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  277. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  278. package/out/zero-cache/src/services/run-ast.js +0 -1
  279. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  280. package/out/zero-cache/src/services/runner.js.map +1 -1
  281. package/out/zero-cache/src/services/running-state.js.map +1 -1
  282. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
  283. package/out/zero-cache/src/services/statz.js.map +1 -1
  284. package/out/zero-cache/src/services/view-syncer/active-users-gauge.js.map +1 -1
  285. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  286. package/out/zero-cache/src/services/view-syncer/client-schema.js.map +1 -1
  287. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  288. package/out/zero-cache/src/services/view-syncer/cvr-purger.d.ts.map +1 -1
  289. package/out/zero-cache/src/services/view-syncer/cvr-purger.js +1 -2
  290. package/out/zero-cache/src/services/view-syncer/cvr-purger.js.map +1 -1
  291. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  292. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  293. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  294. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +14 -0
  295. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
  296. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +25 -2
  297. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  298. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  299. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  300. package/out/zero-cache/src/services/view-syncer/row-set-signature.js.map +1 -1
  301. package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
  302. package/out/zero-cache/src/services/view-syncer/schema/init.js +113 -97
  303. package/out/zero-cache/src/services/view-syncer/schema/init.js.map +1 -1
  304. package/out/zero-cache/src/services/view-syncer/schema/types.js +1 -103
  305. package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
  306. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  307. package/out/zero-cache/src/services/view-syncer/tracer.js.map +1 -1
  308. package/out/zero-cache/src/services/view-syncer/ttl-clock.js.map +1 -1
  309. package/out/zero-cache/src/services/view-syncer/view-syncer.js +1 -4
  310. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  311. package/out/zero-cache/src/types/configuration-error.js.map +1 -1
  312. package/out/zero-cache/src/types/error-with-level.js.map +1 -1
  313. package/out/zero-cache/src/types/http.js.map +1 -1
  314. package/out/zero-cache/src/types/lexi-version.js.map +1 -1
  315. package/out/zero-cache/src/types/lite.js.map +1 -1
  316. package/out/zero-cache/src/types/names.js.map +1 -1
  317. package/out/zero-cache/src/types/pg-data-type.js.map +1 -1
  318. package/out/zero-cache/src/types/pg.js.map +1 -1
  319. package/out/zero-cache/src/types/processes.js.map +1 -1
  320. package/out/zero-cache/src/types/profiler.js.map +1 -1
  321. package/out/zero-cache/src/types/row-key.js.map +1 -1
  322. package/out/zero-cache/src/types/shards.js.map +1 -1
  323. package/out/zero-cache/src/types/sql.js.map +1 -1
  324. package/out/zero-cache/src/types/state-version.js.map +1 -1
  325. package/out/zero-cache/src/types/streams.js.map +1 -1
  326. package/out/zero-cache/src/types/strings.js.map +1 -1
  327. package/out/zero-cache/src/types/subscription.js.map +1 -1
  328. package/out/zero-cache/src/types/timeout.js.map +1 -1
  329. package/out/zero-cache/src/types/url-params.js.map +1 -1
  330. package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
  331. package/out/zero-cache/src/types/ws.js.map +1 -1
  332. package/out/zero-cache/src/workers/connect-params.js.map +1 -1
  333. package/out/zero-cache/src/workers/connection.js.map +1 -1
  334. package/out/zero-cache/src/workers/mutator.js.map +1 -1
  335. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  336. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  337. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  338. package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
  339. package/out/zero-client/src/client/connection-manager.js +1 -2
  340. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  341. package/out/zero-client/src/client/connection.js.map +1 -1
  342. package/out/zero-client/src/client/context.js.map +1 -1
  343. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  344. package/out/zero-client/src/client/crud.js.map +1 -1
  345. package/out/zero-client/src/client/custom.js +1 -2
  346. package/out/zero-client/src/client/custom.js.map +1 -1
  347. package/out/zero-client/src/client/delete-clients-manager.js.map +1 -1
  348. package/out/zero-client/src/client/enable-analytics.js.map +1 -1
  349. package/out/zero-client/src/client/error.js.map +1 -1
  350. package/out/zero-client/src/client/http-string.js.map +1 -1
  351. package/out/zero-client/src/client/inspector/client-group.js.map +1 -1
  352. package/out/zero-client/src/client/inspector/client.js.map +1 -1
  353. package/out/zero-client/src/client/inspector/html-dialog-prompt.js.map +1 -1
  354. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  355. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  356. package/out/zero-client/src/client/inspector/query.js.map +1 -1
  357. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  358. package/out/zero-client/src/client/keys.js.map +1 -1
  359. package/out/zero-client/src/client/log-options.js.map +1 -1
  360. package/out/zero-client/src/client/make-mutate-property.js.map +1 -1
  361. package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
  362. package/out/zero-client/src/client/metrics.js.map +1 -1
  363. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  364. package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
  365. package/out/zero-client/src/client/options.js.map +1 -1
  366. package/out/zero-client/src/client/query-manager.js.map +1 -1
  367. package/out/zero-client/src/client/reload-error-handler.js.map +1 -1
  368. package/out/zero-client/src/client/server-option.js.map +1 -1
  369. package/out/zero-client/src/client/version.js +1 -1
  370. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  371. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  372. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  373. package/out/zero-client/src/client/zero.js +32 -58
  374. package/out/zero-client/src/client/zero.js.map +1 -1
  375. package/out/zero-client/src/util/nanoid.js.map +1 -1
  376. package/out/zero-client/src/util/socket.d.ts +3 -0
  377. package/out/zero-client/src/util/socket.d.ts.map +1 -0
  378. package/out/zero-client/src/util/socket.js +8 -0
  379. package/out/zero-client/src/util/socket.js.map +1 -0
  380. package/out/zero-protocol/src/analyze-query-result.js +0 -3
  381. package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
  382. package/out/zero-protocol/src/application-error.js.map +1 -1
  383. package/out/zero-protocol/src/ast.js.map +1 -1
  384. package/out/zero-protocol/src/change-desired-queries.js +0 -1
  385. package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
  386. package/out/zero-protocol/src/client-schema.js.map +1 -1
  387. package/out/zero-protocol/src/close-connection.js.map +1 -1
  388. package/out/zero-protocol/src/connect.js +0 -7
  389. package/out/zero-protocol/src/connect.js.map +1 -1
  390. package/out/zero-protocol/src/custom-queries.js.map +1 -1
  391. package/out/zero-protocol/src/data.js.map +1 -1
  392. package/out/zero-protocol/src/delete-clients.js.map +1 -1
  393. package/out/zero-protocol/src/down.js.map +1 -1
  394. package/out/zero-protocol/src/error.js +0 -7
  395. package/out/zero-protocol/src/error.js.map +1 -1
  396. package/out/zero-protocol/src/inspect-down.js.map +1 -1
  397. package/out/zero-protocol/src/inspect-up.js +0 -1
  398. package/out/zero-protocol/src/inspect-up.js.map +1 -1
  399. package/out/zero-protocol/src/mutate-server.js.map +1 -1
  400. package/out/zero-protocol/src/mutation-id.js.map +1 -1
  401. package/out/zero-protocol/src/mutation.js.map +1 -1
  402. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  403. package/out/zero-protocol/src/ping.js.map +1 -1
  404. package/out/zero-protocol/src/poke.js +0 -4
  405. package/out/zero-protocol/src/poke.js.map +1 -1
  406. package/out/zero-protocol/src/pong.js.map +1 -1
  407. package/out/zero-protocol/src/primary-key.js.map +1 -1
  408. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  409. package/out/zero-protocol/src/pull.js.map +1 -1
  410. package/out/zero-protocol/src/push.js +0 -16
  411. package/out/zero-protocol/src/push.js.map +1 -1
  412. package/out/zero-protocol/src/queries-patch.js.map +1 -1
  413. package/out/zero-protocol/src/query-hash.js.map +1 -1
  414. package/out/zero-protocol/src/query-server.js.map +1 -1
  415. package/out/zero-protocol/src/row-patch.js.map +1 -1
  416. package/out/zero-protocol/src/up.js.map +1 -1
  417. package/out/zero-protocol/src/update-auth.js.map +1 -1
  418. package/out/zero-protocol/src/version.js.map +1 -1
  419. package/out/zero-react/src/use-connection-state.js +2 -4
  420. package/out/zero-react/src/use-connection-state.js.map +1 -1
  421. package/out/zero-react/src/use-query.js +4 -6
  422. package/out/zero-react/src/use-query.js.map +1 -1
  423. package/out/zero-react/src/use-zero-online.js +2 -4
  424. package/out/zero-react/src/use-zero-online.js.map +1 -1
  425. package/out/zero-react/src/zero-provider.js +12 -15
  426. package/out/zero-react/src/zero-provider.js.map +1 -1
  427. package/out/zero-schema/src/builder/relationship-builder.js.map +1 -1
  428. package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
  429. package/out/zero-schema/src/builder/table-builder.js.map +1 -1
  430. package/out/zero-schema/src/compiled-permissions.js.map +1 -1
  431. package/out/zero-schema/src/name-mapper.js.map +1 -1
  432. package/out/zero-schema/src/permissions.js.map +1 -1
  433. package/out/zero-schema/src/schema-config.js.map +1 -1
  434. package/out/zero-server/src/adapters/drizzle.js.map +1 -1
  435. package/out/zero-server/src/adapters/kysely.js.map +1 -1
  436. package/out/zero-server/src/adapters/pg.js +1 -1
  437. package/out/zero-server/src/adapters/pg.js.map +1 -1
  438. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  439. package/out/zero-server/src/adapters/prisma.js.map +1 -1
  440. package/out/zero-server/src/custom.js +1 -2
  441. package/out/zero-server/src/custom.js.map +1 -1
  442. package/out/zero-server/src/logging.js.map +1 -1
  443. package/out/zero-server/src/pg-query-executor.js.map +1 -1
  444. package/out/zero-server/src/process-mutations.js.map +1 -1
  445. package/out/zero-server/src/push-processor.js.map +1 -1
  446. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  447. package/out/zero-server/src/schema.js.map +1 -1
  448. package/out/zero-server/src/zql-database.js.map +1 -1
  449. package/out/zero-solid/src/solid-view.js +1 -1
  450. package/out/zero-solid/src/solid-view.js.map +1 -1
  451. package/out/zero-solid/src/use-connection-state.js +1 -1
  452. package/out/zero-solid/src/use-connection-state.js.map +1 -1
  453. package/out/zero-solid/src/use-query.js +2 -2
  454. package/out/zero-solid/src/use-query.js.map +1 -1
  455. package/out/zero-solid/src/use-zero-online.js +1 -1
  456. package/out/zero-solid/src/use-zero-online.js.map +1 -1
  457. package/out/zero-solid/src/use-zero.js +1 -1
  458. package/out/zero-solid/src/use-zero.js.map +1 -1
  459. package/out/zero-types/src/format.js.map +1 -1
  460. package/out/zero-types/src/name-mapper.js.map +1 -1
  461. package/out/zql/src/builder/builder.js.map +1 -1
  462. package/out/zql/src/builder/debug-delegate.d.ts +0 -5
  463. package/out/zql/src/builder/debug-delegate.d.ts.map +1 -1
  464. package/out/zql/src/builder/debug-delegate.js +1 -10
  465. package/out/zql/src/builder/debug-delegate.js.map +1 -1
  466. package/out/zql/src/builder/filter.js.map +1 -1
  467. package/out/zql/src/builder/like.js.map +1 -1
  468. package/out/zql/src/error.js.map +1 -1
  469. package/out/zql/src/ivm/array-view.js.map +1 -1
  470. package/out/zql/src/ivm/cap.js.map +1 -1
  471. package/out/zql/src/ivm/change.js.map +1 -1
  472. package/out/zql/src/ivm/constraint.js +1 -1
  473. package/out/zql/src/ivm/constraint.js.map +1 -1
  474. package/out/zql/src/ivm/data.js.map +1 -1
  475. package/out/zql/src/ivm/exists.js.map +1 -1
  476. package/out/zql/src/ivm/fan-in.js.map +1 -1
  477. package/out/zql/src/ivm/fan-out.js.map +1 -1
  478. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  479. package/out/zql/src/ivm/filter-push.js.map +1 -1
  480. package/out/zql/src/ivm/filter.js.map +1 -1
  481. package/out/zql/src/ivm/flipped-join.d.ts +8 -4
  482. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  483. package/out/zql/src/ivm/flipped-join.js +63 -59
  484. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  485. package/out/zql/src/ivm/join-utils.js.map +1 -1
  486. package/out/zql/src/ivm/join.js.map +1 -1
  487. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  488. package/out/zql/src/ivm/memory-source.js.map +1 -1
  489. package/out/zql/src/ivm/memory-storage.js.map +1 -1
  490. package/out/zql/src/ivm/operator.d.ts +1 -1
  491. package/out/zql/src/ivm/operator.js.map +1 -1
  492. package/out/zql/src/ivm/push-accumulated.js.map +1 -1
  493. package/out/zql/src/ivm/schema.d.ts +8 -0
  494. package/out/zql/src/ivm/schema.d.ts.map +1 -1
  495. package/out/zql/src/ivm/skip-yields.js.map +1 -1
  496. package/out/zql/src/ivm/skip.js.map +1 -1
  497. package/out/zql/src/ivm/source.js.map +1 -1
  498. package/out/zql/src/ivm/stream.js.map +1 -1
  499. package/out/zql/src/ivm/take.js.map +1 -1
  500. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  501. package/out/zql/src/ivm/union-fan-out.js.map +1 -1
  502. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  503. package/out/zql/src/mutate/crud.js.map +1 -1
  504. package/out/zql/src/mutate/custom.js.map +1 -1
  505. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  506. package/out/zql/src/mutate/mutator.js.map +1 -1
  507. package/out/zql/src/planner/planner-builder.js.map +1 -1
  508. package/out/zql/src/planner/planner-connection.js.map +1 -1
  509. package/out/zql/src/planner/planner-constraint.js.map +1 -1
  510. package/out/zql/src/planner/planner-debug.js.map +1 -1
  511. package/out/zql/src/planner/planner-fan-in.js.map +1 -1
  512. package/out/zql/src/planner/planner-fan-out.js.map +1 -1
  513. package/out/zql/src/planner/planner-graph.js.map +1 -1
  514. package/out/zql/src/planner/planner-join.d.ts.map +1 -1
  515. package/out/zql/src/planner/planner-join.js +1 -2
  516. package/out/zql/src/planner/planner-join.js.map +1 -1
  517. package/out/zql/src/planner/planner-node.js.map +1 -1
  518. package/out/zql/src/planner/planner-source.js.map +1 -1
  519. package/out/zql/src/planner/planner-terminus.js.map +1 -1
  520. package/out/zql/src/query/complete-ordering.js.map +1 -1
  521. package/out/zql/src/query/create-builder.js.map +1 -1
  522. package/out/zql/src/query/error.js.map +1 -1
  523. package/out/zql/src/query/escape-like.js.map +1 -1
  524. package/out/zql/src/query/expression.js.map +1 -1
  525. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  526. package/out/zql/src/query/metrics-delegate.js.map +1 -1
  527. package/out/zql/src/query/named.js.map +1 -1
  528. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  529. package/out/zql/src/query/query-impl.js +1 -1
  530. package/out/zql/src/query/query-impl.js.map +1 -1
  531. package/out/zql/src/query/query-internals.js.map +1 -1
  532. package/out/zql/src/query/query-registry.js.map +1 -1
  533. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  534. package/out/zql/src/query/static-query.js.map +1 -1
  535. package/out/zql/src/query/ttl.js.map +1 -1
  536. package/out/zql/src/query/validate-input.js.map +1 -1
  537. package/out/zqlite/src/database-storage.js.map +1 -1
  538. package/out/zqlite/src/db.js.map +1 -1
  539. package/out/zqlite/src/explain-queries.js.map +1 -1
  540. package/out/zqlite/src/internal/sql-inline.js.map +1 -1
  541. package/out/zqlite/src/internal/sql.js.map +1 -1
  542. package/out/zqlite/src/internal/statement-cache.js.map +1 -1
  543. package/out/zqlite/src/query-builder.js.map +1 -1
  544. package/out/zqlite/src/query-delegate.js.map +1 -1
  545. package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
  546. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  547. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  548. package/out/zqlite/src/table-source.d.ts.map +1 -1
  549. package/out/zqlite/src/table-source.js +6 -6
  550. package/out/zqlite/src/table-source.js.map +1 -1
  551. package/package.json +23 -23
  552. package/out/_virtual/__vite-optional-peer-dep_pg-native_pg.js +0 -13
  553. package/out/_virtual/__vite-optional-peer-dep_pg-native_pg.js.map +0 -1
  554. package/out/node_modules/.pnpm/@opentelemetry_semantic-conventions@1.41.1/node_modules/@opentelemetry/semantic-conventions/build/esm/stable_attributes.js +0 -12
  555. package/out/node_modules/.pnpm/@opentelemetry_semantic-conventions@1.41.1/node_modules/@opentelemetry/semantic-conventions/build/esm/stable_attributes.js.map +0 -1
  556. package/out/node_modules/.pnpm/pg-cloudflare@1.3.0/node_modules/pg-cloudflare/dist/empty.js +0 -11
  557. package/out/node_modules/.pnpm/pg-cloudflare@1.3.0/node_modules/pg-cloudflare/dist/empty.js.map +0 -1
  558. package/out/node_modules/.pnpm/pg-connection-string@2.12.0/node_modules/pg-connection-string/index.js +0 -130
  559. package/out/node_modules/.pnpm/pg-connection-string@2.12.0/node_modules/pg-connection-string/index.js.map +0 -1
  560. package/out/node_modules/.pnpm/pg-int8@1.0.1/node_modules/pg-int8/index.js +0 -62
  561. package/out/node_modules/.pnpm/pg-int8@1.0.1/node_modules/pg-int8/index.js.map +0 -1
  562. package/out/node_modules/.pnpm/pg-pool@3.13.0_pg@8.20.0/node_modules/pg-pool/index.js +0 -353
  563. package/out/node_modules/.pnpm/pg-pool@3.13.0_pg@8.20.0/node_modules/pg-pool/index.js.map +0 -1
  564. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/buffer-reader.js +0 -60
  565. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/buffer-reader.js.map +0 -1
  566. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/buffer-writer.js +0 -81
  567. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/buffer-writer.js.map +0 -1
  568. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/index.js +0 -35
  569. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/index.js.map +0 -1
  570. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/messages.js +0 -167
  571. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/messages.js.map +0 -1
  572. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/parser.js +0 -288
  573. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/parser.js.map +0 -1
  574. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/serializer.js +0 -177
  575. package/out/node_modules/.pnpm/pg-protocol@1.13.0/node_modules/pg-protocol/dist/serializer.js.map +0 -1
  576. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/index.js +0 -46
  577. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/index.js.map +0 -1
  578. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/lib/arrayParser.js +0 -16
  579. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/lib/arrayParser.js.map +0 -1
  580. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/lib/binaryParsers.js +0 -165
  581. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/lib/binaryParsers.js.map +0 -1
  582. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/lib/builtins.js +0 -81
  583. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/lib/builtins.js.map +0 -1
  584. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/lib/textParsers.js +0 -167
  585. package/out/node_modules/.pnpm/pg-types@2.2.0/node_modules/pg-types/lib/textParsers.js.map +0 -1
  586. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/esm/index.js +0 -19
  587. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/esm/index.js.map +0 -1
  588. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/client.js +0 -508
  589. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/client.js.map +0 -1
  590. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/connection-parameters.js +0 -104
  591. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/connection-parameters.js.map +0 -1
  592. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/connection.js +0 -160
  593. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/connection.js.map +0 -1
  594. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/cert-signatures.js +0 -97
  595. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/cert-signatures.js.map +0 -1
  596. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/sasl.js +0 -131
  597. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/sasl.js.map +0 -1
  598. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/utils-legacy.js +0 -39
  599. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/utils-legacy.js.map +0 -1
  600. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/utils-webcrypto.js +0 -89
  601. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/utils-webcrypto.js.map +0 -1
  602. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/utils.js +0 -13
  603. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/crypto/utils.js.map +0 -1
  604. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/defaults.js +0 -46
  605. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/defaults.js.map +0 -1
  606. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/index.js +0 -71
  607. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/index.js.map +0 -1
  608. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/native/client.js +0 -226
  609. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/native/client.js.map +0 -1
  610. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/native/index.js +0 -11
  611. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/native/index.js.map +0 -1
  612. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/native/query.js +0 -117
  613. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/native/query.js.map +0 -1
  614. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/query.js +0 -151
  615. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/query.js.map +0 -1
  616. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/result.js +0 -76
  617. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/result.js.map +0 -1
  618. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/stream.js +0 -73
  619. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/stream.js.map +0 -1
  620. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/type-overrides.js +0 -35
  621. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/type-overrides.js.map +0 -1
  622. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/utils.js +0 -118
  623. package/out/node_modules/.pnpm/pg@8.20.0/node_modules/pg/lib/utils.js.map +0 -1
  624. package/out/node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/helper.js +0 -147
  625. package/out/node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/helper.js.map +0 -1
  626. package/out/node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/index.js +0 -21
  627. package/out/node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/index.js.map +0 -1
  628. package/out/node_modules/.pnpm/postgres-array@2.0.0/node_modules/postgres-array/index.js +0 -84
  629. package/out/node_modules/.pnpm/postgres-array@2.0.0/node_modules/postgres-array/index.js.map +0 -1
  630. package/out/node_modules/.pnpm/postgres-bytea@1.0.1/node_modules/postgres-bytea/index.js +0 -28
  631. package/out/node_modules/.pnpm/postgres-bytea@1.0.1/node_modules/postgres-bytea/index.js.map +0 -1
  632. package/out/node_modules/.pnpm/postgres-date@1.0.7/node_modules/postgres-date/index.js +0 -65
  633. package/out/node_modules/.pnpm/postgres-date@1.0.7/node_modules/postgres-date/index.js.map +0 -1
  634. package/out/node_modules/.pnpm/postgres-interval@1.2.0/node_modules/postgres-interval/index.js +0 -107
  635. package/out/node_modules/.pnpm/postgres-interval@1.2.0/node_modules/postgres-interval/index.js.map +0 -1
  636. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.development.js +0 -696
  637. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
  638. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.production.min.js +0 -44
  639. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.production.min.js.map +0 -1
  640. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.development.js +0 -1585
  641. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.development.js.map +0 -1
  642. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js +0 -329
  643. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js.map +0 -1
  644. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/index.js +0 -13
  645. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/index.js.map +0 -1
  646. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/jsx-runtime.js +0 -13
  647. package/out/node_modules/.pnpm/react@18.3.1/node_modules/react/jsx-runtime.js.map +0 -1
  648. package/out/node_modules/.pnpm/solid-js@1.9.13/node_modules/solid-js/dist/server.js +0 -131
  649. package/out/node_modules/.pnpm/solid-js@1.9.13/node_modules/solid-js/dist/server.js.map +0 -1
  650. package/out/node_modules/.pnpm/solid-js@1.9.13/node_modules/solid-js/store/dist/server.js +0 -96
  651. package/out/node_modules/.pnpm/solid-js@1.9.13/node_modules/solid-js/store/dist/server.js.map +0 -1
  652. package/out/node_modules/.pnpm/split2@4.2.0/node_modules/split2/index.js +0 -95
  653. package/out/node_modules/.pnpm/split2@4.2.0/node_modules/split2/index.js.map +0 -1
  654. package/out/node_modules/.pnpm/xtend@4.0.2/node_modules/xtend/mutable.js +0 -18
  655. package/out/node_modules/.pnpm/xtend@4.0.2/node_modules/xtend/mutable.js.map +0 -1
  656. package/out/shared/src/ring-buffer.d.ts +0 -32
  657. package/out/shared/src/ring-buffer.d.ts.map +0 -1
  658. package/out/shared/src/ring-buffer.js +0 -109
  659. package/out/shared/src/ring-buffer.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","names":["#lc","#instances","#create","#isValid"],"sources":["../../../../../zero-cache/src/services/runner.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {Service} from './service.ts';\n\n/**\n * Manages the creation and lifecycle of objects that implement\n * {@link Service}.\n */\nexport class ServiceRunner<S extends Service> {\n readonly #lc: LogContext;\n readonly #instances = new Map<string, S>();\n readonly #create: (id: string) => S;\n readonly #isValid: (existing: S) => boolean;\n\n constructor(\n lc: LogContext,\n factory: (id: string) => S,\n isValid: (existing: S) => boolean = () => true,\n ) {\n this.#lc = lc;\n this.#create = factory;\n this.#isValid = isValid;\n }\n\n /**\n * Creates and runs the Service with the given `id`, returning\n * an existing one if it is still running a valid.\n */\n getService(id: string): S {\n const existing = this.#instances.get(id);\n if (existing && this.#isValid(existing)) {\n return existing;\n }\n const service = this.#create(id);\n this.#instances.set(id, service);\n void service\n .run()\n .catch(e =>\n this.#lc.error?.(\n `Error running ${service.constructor?.name} ${service.id}`,\n e,\n ),\n )\n .finally(() => {\n this.#instances.delete(id);\n });\n return service;\n }\n\n get size() {\n return this.#instances.size;\n }\n\n getServices(): Iterable<S> {\n return this.#instances.values();\n }\n}\n"],"mappings":";;;;;AAOA,IAAa,gBAAb,MAA8C;CAC5C;CACA,6BAAsB,IAAI,IAAe;CACzC;CACA;CAEA,YACE,IACA,SACA,gBAA0C,MAC1C;EACA,KAAKA,MAAM;EACX,KAAKE,UAAU;EACf,KAAKC,WAAW;CAClB;;;;;CAMA,WAAW,IAAe;EACxB,MAAM,WAAW,KAAKF,WAAW,IAAI,EAAE;EACvC,IAAI,YAAY,KAAKE,SAAS,QAAQ,GACpC,OAAO;EAET,MAAM,UAAU,KAAKD,QAAQ,EAAE;EAC/B,KAAKD,WAAW,IAAI,IAAI,OAAO;EAC/B,QACG,IAAI,EACJ,OAAM,MACL,KAAKD,IAAI,QACP,iBAAiB,QAAQ,aAAa,KAAK,GAAG,QAAQ,MACtD,CACF,CACF,EACC,cAAc;GACb,KAAKC,WAAW,OAAO,EAAE;EAC3B,CAAC;EACH,OAAO;CACT;CAEA,IAAI,OAAO;EACT,OAAO,KAAKA,WAAW;CACzB;CAEA,cAA2B;EACzB,OAAO,KAAKA,WAAW,OAAO;CAChC;AACF"}
1
+ {"version":3,"file":"runner.js","names":["#lc","#instances","#create","#isValid"],"sources":["../../../../../zero-cache/src/services/runner.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {Service} from './service.ts';\n\n/**\n * Manages the creation and lifecycle of objects that implement\n * {@link Service}.\n */\nexport class ServiceRunner<S extends Service> {\n readonly #lc: LogContext;\n readonly #instances = new Map<string, S>();\n readonly #create: (id: string) => S;\n readonly #isValid: (existing: S) => boolean;\n\n constructor(\n lc: LogContext,\n factory: (id: string) => S,\n isValid: (existing: S) => boolean = () => true,\n ) {\n this.#lc = lc;\n this.#create = factory;\n this.#isValid = isValid;\n }\n\n /**\n * Creates and runs the Service with the given `id`, returning\n * an existing one if it is still running a valid.\n */\n getService(id: string): S {\n const existing = this.#instances.get(id);\n if (existing && this.#isValid(existing)) {\n return existing;\n }\n const service = this.#create(id);\n this.#instances.set(id, service);\n void service\n .run()\n .catch(e =>\n this.#lc.error?.(\n `Error running ${service.constructor?.name} ${service.id}`,\n e,\n ),\n )\n .finally(() => {\n this.#instances.delete(id);\n });\n return service;\n }\n\n get size() {\n return this.#instances.size;\n }\n\n getServices(): Iterable<S> {\n return this.#instances.values();\n }\n}\n"],"mappings":";;;;;AAOA,IAAa,gBAAb,MAA8C;CAC5C;CACA,6BAAsB,IAAI,KAAgB;CAC1C;CACA;CAEA,YACE,IACA,SACA,gBAA0C,MAC1C;AACA,QAAA,KAAW;AACX,QAAA,SAAe;AACf,QAAA,UAAgB;;;;;;CAOlB,WAAW,IAAe;EACxB,MAAM,WAAW,MAAA,UAAgB,IAAI,GAAG;AACxC,MAAI,YAAY,MAAA,QAAc,SAAS,CACrC,QAAO;EAET,MAAM,UAAU,MAAA,OAAa,GAAG;AAChC,QAAA,UAAgB,IAAI,IAAI,QAAQ;AAC3B,UACF,KAAK,CACL,OAAM,MACL,MAAA,GAAS,QACP,iBAAiB,QAAQ,aAAa,KAAK,GAAG,QAAQ,MACtD,EACD,CACF,CACA,cAAc;AACb,SAAA,UAAgB,OAAO,GAAG;IAC1B;AACJ,SAAO;;CAGT,IAAI,OAAO;AACT,SAAO,MAAA,UAAgB;;CAGzB,cAA2B;AACzB,SAAO,MAAA,UAAgB,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"running-state.js","names":["#serviceName","#controller","#sleep","#setTimeout","#stopped","#initialRetryDelay","#maxRetryDelay","#pendingTimeouts","#retryDelay"],"sources":["../../../../../zero-cache/src/services/running-state.ts"],"sourcesContent":["import {PG_UNDEFINED_TABLE} from '@drdgvhbh/postgres-error-codes';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {AbortError} from '../../../shared/src/abort-error.ts';\nimport {sleepWithAbort} from '../../../shared/src/sleep.ts';\nimport {isPostgresError} from '../types/pg.ts';\n\nconst DEFAULT_INITIAL_RETRY_DELAY_MS = 25;\nexport const DEFAULT_MAX_RETRY_DELAY_MS = 10000;\n\nexport type RetryConfig = {\n initialRetryDelay?: number;\n maxRetryDelay?: number;\n};\n\nexport interface Cancelable {\n cancel(): void;\n}\n\nexport type UnregisterFn = () => void;\n\n/**\n * Facilitates lifecycle control with exponential backoff.\n */\nexport class RunningState {\n readonly #serviceName: string;\n readonly #controller: AbortController;\n readonly #sleep: typeof sleepWithAbort;\n readonly #setTimeout: typeof setTimeout;\n readonly #stopped: Promise<void>;\n\n readonly #initialRetryDelay: number;\n readonly #maxRetryDelay: number;\n readonly #pendingTimeouts = new Set<NodeJS.Timeout>();\n #retryDelay: number;\n\n constructor(\n serviceName: string,\n retryConfig?: RetryConfig,\n setTimeoutFn = setTimeout,\n sleeper = sleepWithAbort,\n ) {\n const {\n initialRetryDelay = DEFAULT_INITIAL_RETRY_DELAY_MS,\n maxRetryDelay = DEFAULT_MAX_RETRY_DELAY_MS,\n } = retryConfig ?? {};\n\n this.#serviceName = serviceName;\n this.#initialRetryDelay = initialRetryDelay;\n this.#maxRetryDelay = maxRetryDelay;\n this.#retryDelay = initialRetryDelay;\n\n this.#controller = new AbortController();\n this.#sleep = sleeper;\n this.#setTimeout = setTimeoutFn;\n\n const {promise, resolve} = resolver();\n this.#stopped = promise;\n this.#controller.signal.addEventListener(\n 'abort',\n () => {\n resolve();\n for (const timeout of this.#pendingTimeouts) {\n clearTimeout(timeout);\n }\n this.#pendingTimeouts.clear();\n },\n {once: true},\n );\n }\n\n get signal(): AbortSignal {\n return this.#controller.signal;\n }\n\n get retryDelay() {\n return this.#retryDelay;\n }\n\n /**\n * Returns `true` until {@link stop()} has been called.\n *\n * This is usually called as part of the service's main loop\n * conditional to determine if the next iteration should execute.\n */\n shouldRun(): boolean {\n return !this.#controller.signal.aborted;\n }\n\n /**\n * Registers a Cancelable object to be invoked when {@link stop()} is called.\n * Returns a method to unregister the object.\n */\n cancelOnStop(c: Cancelable): UnregisterFn {\n const onStop = () => c.cancel();\n this.#controller.signal.addEventListener('abort', onStop, {once: true});\n return () => this.#controller.signal.removeEventListener('abort', onStop);\n }\n\n /**\n * Sets a Timeout that is automatically cancelled if the service is cancelled.\n */\n setTimeout<TArgs extends unknown[]>(\n fn: (...args: TArgs) => void,\n timeoutMs: number,\n ...args: TArgs\n ) {\n const timeout = this.#setTimeout(() => {\n clearTimeout(timeout);\n this.#pendingTimeouts.delete(timeout);\n return fn(...args);\n }, timeoutMs);\n\n this.#pendingTimeouts.add(timeout);\n }\n\n /**\n * Returns a promise that resolves after `ms` milliseconds or when\n * the service is stopped.\n */\n async sleep(ms: number): Promise<void> {\n await Promise.race(this.#sleep(ms, this.#controller.signal));\n }\n\n /**\n * Called to stop the service. After this is called, {@link shouldRun()}\n * will return `false` and the {@link stopped()} Promise will be resolved.\n */\n stop(lc: LogContext, err?: unknown): void {\n if (this.shouldRun()) {\n const log = !err || err instanceof AbortError ? 'info' : 'error';\n lc[log]?.(`stopping ${this.#serviceName}`, err ?? '');\n this.#controller.abort();\n }\n }\n\n /**\n * Returns a Promise that resolves when {@link stop()} is called.\n */\n stopped(): Promise<void> {\n return this.#stopped;\n }\n\n /**\n * Call in response to an error or unexpected termination in the main\n * loop of the service. The returned Promise will resolve after an\n * exponential delay, or once {@link stop()} is called.\n *\n * If the supplied `err` is an `AbortError`, the service will shut down.\n */\n async backoff(lc: LogContext, err: unknown): Promise<void> {\n const delay = this.#retryDelay;\n this.#retryDelay = Math.min(delay * 2, this.#maxRetryDelay);\n\n // Common failure mode when the user deletes a pg schema out from under\n // us after init. Turn into an AbortError to force restart, which will\n // restart and re-init the schema.\n if (isPostgresError(err, PG_UNDEFINED_TABLE)) {\n err = new AbortError('undefined table', {cause: err});\n }\n\n if (err instanceof AbortError || err instanceof UnrecoverableError) {\n this.resetBackoff();\n this.stop(lc, err);\n } else if (this.shouldRun()) {\n // Use delay-based log level: higher delay means more retries\n const log: 'info' | 'warn' | 'error' =\n delay < 1000 ? 'info' : delay < 6500 ? 'warn' : 'error';\n\n lc[log]?.(`retrying ${this.#serviceName} in ${delay} ms`, err);\n await this.sleep(delay);\n }\n }\n\n /**\n * When using {@link backoff()}, this method should be called when the\n * implementation receives a healthy signal (e.g. a successful\n * response). This resets the delay used in {@link backoff()}.\n *\n * @returns The previous backoff delay\n */\n resetBackoff() {\n const prevDelay = this.#retryDelay;\n this.#retryDelay = this.#initialRetryDelay;\n return prevDelay;\n }\n}\n\n/**\n * Superclass for Errors that should bypass exponential backoff\n * and immediately shut down the server.\n */\nexport class UnrecoverableError extends Error {\n readonly name = 'UnrecoverableError';\n}\n"],"mappings":";;;;;;AAOA,IAAM,iCAAiC;AACvC,IAAa,6BAA6B;;;;AAgB1C,IAAa,eAAb,MAA0B;CACxB;CACA;CACA;CACA;CACA;CAEA;CACA;CACA,mCAA4B,IAAI,IAAoB;CACpD;CAEA,YACE,aACA,aACA,eAAe,YACf,UAAU,gBACV;EACA,MAAM,EACJ,oBAAoB,gCACpB,gBAAgB,+BACd,eAAe,CAAC;EAEpB,KAAKA,eAAe;EACpB,KAAKK,qBAAqB;EAC1B,KAAKC,iBAAiB;EACtB,KAAKE,cAAc;EAEnB,KAAKP,cAAc,IAAI,gBAAgB;EACvC,KAAKC,SAAS;EACd,KAAKC,cAAc;EAEnB,MAAM,EAAC,SAAS,YAAW,SAAS;EACpC,KAAKC,WAAW;EAChB,KAAKH,YAAY,OAAO,iBACtB,eACM;GACJ,QAAQ;GACR,KAAK,MAAM,WAAW,KAAKM,kBACzB,aAAa,OAAO;GAEtB,KAAKA,iBAAiB,MAAM;EAC9B,GACA,EAAC,MAAM,KAAI,CACb;CACF;CAEA,IAAI,SAAsB;EACxB,OAAO,KAAKN,YAAY;CAC1B;CAEA,IAAI,aAAa;EACf,OAAO,KAAKO;CACd;;;;;;;CAQA,YAAqB;EACnB,OAAO,CAAC,KAAKP,YAAY,OAAO;CAClC;;;;;CAMA,aAAa,GAA6B;EACxC,MAAM,eAAe,EAAE,OAAO;EAC9B,KAAKA,YAAY,OAAO,iBAAiB,SAAS,QAAQ,EAAC,MAAM,KAAI,CAAC;EACtE,aAAa,KAAKA,YAAY,OAAO,oBAAoB,SAAS,MAAM;CAC1E;;;;CAKA,WACE,IACA,WACA,GAAG,MACH;EACA,MAAM,UAAU,KAAKE,kBAAkB;GACrC,aAAa,OAAO;GACpB,KAAKI,iBAAiB,OAAO,OAAO;GACpC,OAAO,GAAG,GAAG,IAAI;EACnB,GAAG,SAAS;EAEZ,KAAKA,iBAAiB,IAAI,OAAO;CACnC;;;;;CAMA,MAAM,MAAM,IAA2B;EACrC,MAAM,QAAQ,KAAK,KAAKL,OAAO,IAAI,KAAKD,YAAY,MAAM,CAAC;CAC7D;;;;;CAMA,KAAK,IAAgB,KAAqB;EACxC,IAAI,KAAK,UAAU,GAAG;GAEpB,GADY,CAAC,OAAO,eAAe,aAAa,SAAS,WAC/C,YAAY,KAAKD,gBAAgB,OAAO,EAAE;GACpD,KAAKC,YAAY,MAAM;EACzB;CACF;;;;CAKA,UAAyB;EACvB,OAAO,KAAKG;CACd;;;;;;;;CASA,MAAM,QAAQ,IAAgB,KAA6B;EACzD,MAAM,QAAQ,KAAKI;EACnB,KAAKA,cAAc,KAAK,IAAI,QAAQ,GAAG,KAAKF,cAAc;EAK1D,IAAI,gBAAgB,KAAK,kBAAkB,GACzC,MAAM,IAAI,WAAW,mBAAmB,EAAC,OAAO,IAAG,CAAC;EAGtD,IAAI,eAAe,cAAc,eAAe,oBAAoB;GAClE,KAAK,aAAa;GAClB,KAAK,KAAK,IAAI,GAAG;EACnB,OAAO,IAAI,KAAK,UAAU,GAAG;GAK3B,GAFE,QAAQ,MAAO,SAAS,QAAQ,OAAO,SAAS,WAExC,YAAY,KAAKN,aAAa,MAAM,MAAM,MAAM,GAAG;GAC7D,MAAM,KAAK,MAAM,KAAK;EACxB;CACF;;;;;;;;CASA,eAAe;EACb,MAAM,YAAY,KAAKQ;EACvB,KAAKA,cAAc,KAAKH;EACxB,OAAO;CACT;AACF;;;;;AAMA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,OAAgB;AAClB"}
1
+ {"version":3,"file":"running-state.js","names":["#serviceName","#controller","#sleep","#setTimeout","#stopped","#initialRetryDelay","#maxRetryDelay","#pendingTimeouts","#retryDelay"],"sources":["../../../../../zero-cache/src/services/running-state.ts"],"sourcesContent":["import {PG_UNDEFINED_TABLE} from '@drdgvhbh/postgres-error-codes';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {AbortError} from '../../../shared/src/abort-error.ts';\nimport {sleepWithAbort} from '../../../shared/src/sleep.ts';\nimport {isPostgresError} from '../types/pg.ts';\n\nconst DEFAULT_INITIAL_RETRY_DELAY_MS = 25;\nexport const DEFAULT_MAX_RETRY_DELAY_MS = 10000;\n\nexport type RetryConfig = {\n initialRetryDelay?: number;\n maxRetryDelay?: number;\n};\n\nexport interface Cancelable {\n cancel(): void;\n}\n\nexport type UnregisterFn = () => void;\n\n/**\n * Facilitates lifecycle control with exponential backoff.\n */\nexport class RunningState {\n readonly #serviceName: string;\n readonly #controller: AbortController;\n readonly #sleep: typeof sleepWithAbort;\n readonly #setTimeout: typeof setTimeout;\n readonly #stopped: Promise<void>;\n\n readonly #initialRetryDelay: number;\n readonly #maxRetryDelay: number;\n readonly #pendingTimeouts = new Set<NodeJS.Timeout>();\n #retryDelay: number;\n\n constructor(\n serviceName: string,\n retryConfig?: RetryConfig,\n setTimeoutFn = setTimeout,\n sleeper = sleepWithAbort,\n ) {\n const {\n initialRetryDelay = DEFAULT_INITIAL_RETRY_DELAY_MS,\n maxRetryDelay = DEFAULT_MAX_RETRY_DELAY_MS,\n } = retryConfig ?? {};\n\n this.#serviceName = serviceName;\n this.#initialRetryDelay = initialRetryDelay;\n this.#maxRetryDelay = maxRetryDelay;\n this.#retryDelay = initialRetryDelay;\n\n this.#controller = new AbortController();\n this.#sleep = sleeper;\n this.#setTimeout = setTimeoutFn;\n\n const {promise, resolve} = resolver();\n this.#stopped = promise;\n this.#controller.signal.addEventListener(\n 'abort',\n () => {\n resolve();\n for (const timeout of this.#pendingTimeouts) {\n clearTimeout(timeout);\n }\n this.#pendingTimeouts.clear();\n },\n {once: true},\n );\n }\n\n get signal(): AbortSignal {\n return this.#controller.signal;\n }\n\n get retryDelay() {\n return this.#retryDelay;\n }\n\n /**\n * Returns `true` until {@link stop()} has been called.\n *\n * This is usually called as part of the service's main loop\n * conditional to determine if the next iteration should execute.\n */\n shouldRun(): boolean {\n return !this.#controller.signal.aborted;\n }\n\n /**\n * Registers a Cancelable object to be invoked when {@link stop()} is called.\n * Returns a method to unregister the object.\n */\n cancelOnStop(c: Cancelable): UnregisterFn {\n const onStop = () => c.cancel();\n this.#controller.signal.addEventListener('abort', onStop, {once: true});\n return () => this.#controller.signal.removeEventListener('abort', onStop);\n }\n\n /**\n * Sets a Timeout that is automatically cancelled if the service is cancelled.\n */\n setTimeout<TArgs extends unknown[]>(\n fn: (...args: TArgs) => void,\n timeoutMs: number,\n ...args: TArgs\n ) {\n const timeout = this.#setTimeout(() => {\n clearTimeout(timeout);\n this.#pendingTimeouts.delete(timeout);\n return fn(...args);\n }, timeoutMs);\n\n this.#pendingTimeouts.add(timeout);\n }\n\n /**\n * Returns a promise that resolves after `ms` milliseconds or when\n * the service is stopped.\n */\n async sleep(ms: number): Promise<void> {\n await Promise.race(this.#sleep(ms, this.#controller.signal));\n }\n\n /**\n * Called to stop the service. After this is called, {@link shouldRun()}\n * will return `false` and the {@link stopped()} Promise will be resolved.\n */\n stop(lc: LogContext, err?: unknown): void {\n if (this.shouldRun()) {\n const log = !err || err instanceof AbortError ? 'info' : 'error';\n lc[log]?.(`stopping ${this.#serviceName}`, err ?? '');\n this.#controller.abort();\n }\n }\n\n /**\n * Returns a Promise that resolves when {@link stop()} is called.\n */\n stopped(): Promise<void> {\n return this.#stopped;\n }\n\n /**\n * Call in response to an error or unexpected termination in the main\n * loop of the service. The returned Promise will resolve after an\n * exponential delay, or once {@link stop()} is called.\n *\n * If the supplied `err` is an `AbortError`, the service will shut down.\n */\n async backoff(lc: LogContext, err: unknown): Promise<void> {\n const delay = this.#retryDelay;\n this.#retryDelay = Math.min(delay * 2, this.#maxRetryDelay);\n\n // Common failure mode when the user deletes a pg schema out from under\n // us after init. Turn into an AbortError to force restart, which will\n // restart and re-init the schema.\n if (isPostgresError(err, PG_UNDEFINED_TABLE)) {\n err = new AbortError('undefined table', {cause: err});\n }\n\n if (err instanceof AbortError || err instanceof UnrecoverableError) {\n this.resetBackoff();\n this.stop(lc, err);\n } else if (this.shouldRun()) {\n // Use delay-based log level: higher delay means more retries\n const log: 'info' | 'warn' | 'error' =\n delay < 1000 ? 'info' : delay < 6500 ? 'warn' : 'error';\n\n lc[log]?.(`retrying ${this.#serviceName} in ${delay} ms`, err);\n await this.sleep(delay);\n }\n }\n\n /**\n * When using {@link backoff()}, this method should be called when the\n * implementation receives a healthy signal (e.g. a successful\n * response). This resets the delay used in {@link backoff()}.\n *\n * @returns The previous backoff delay\n */\n resetBackoff() {\n const prevDelay = this.#retryDelay;\n this.#retryDelay = this.#initialRetryDelay;\n return prevDelay;\n }\n}\n\n/**\n * Superclass for Errors that should bypass exponential backoff\n * and immediately shut down the server.\n */\nexport class UnrecoverableError extends Error {\n readonly name = 'UnrecoverableError';\n}\n"],"mappings":";;;;;;AAOA,IAAM,iCAAiC;AACvC,IAAa,6BAA6B;;;;AAgB1C,IAAa,eAAb,MAA0B;CACxB;CACA;CACA;CACA;CACA;CAEA;CACA;CACA,mCAA4B,IAAI,KAAqB;CACrD;CAEA,YACE,aACA,aACA,eAAe,YACf,UAAU,gBACV;EACA,MAAM,EACJ,oBAAoB,gCACpB,gBAAgB,+BACd,eAAe,EAAE;AAErB,QAAA,cAAoB;AACpB,QAAA,oBAA0B;AAC1B,QAAA,gBAAsB;AACtB,QAAA,aAAmB;AAEnB,QAAA,aAAmB,IAAI,iBAAiB;AACxC,QAAA,QAAc;AACd,QAAA,aAAmB;EAEnB,MAAM,EAAC,SAAS,YAAW,UAAU;AACrC,QAAA,UAAgB;AAChB,QAAA,WAAiB,OAAO,iBACtB,eACM;AACJ,YAAS;AACT,QAAK,MAAM,WAAW,MAAA,gBACpB,cAAa,QAAQ;AAEvB,SAAA,gBAAsB,OAAO;KAE/B,EAAC,MAAM,MAAK,CACb;;CAGH,IAAI,SAAsB;AACxB,SAAO,MAAA,WAAiB;;CAG1B,IAAI,aAAa;AACf,SAAO,MAAA;;;;;;;;CAST,YAAqB;AACnB,SAAO,CAAC,MAAA,WAAiB,OAAO;;;;;;CAOlC,aAAa,GAA6B;EACxC,MAAM,eAAe,EAAE,QAAQ;AAC/B,QAAA,WAAiB,OAAO,iBAAiB,SAAS,QAAQ,EAAC,MAAM,MAAK,CAAC;AACvE,eAAa,MAAA,WAAiB,OAAO,oBAAoB,SAAS,OAAO;;;;;CAM3E,WACE,IACA,WACA,GAAG,MACH;EACA,MAAM,UAAU,MAAA,iBAAuB;AACrC,gBAAa,QAAQ;AACrB,SAAA,gBAAsB,OAAO,QAAQ;AACrC,UAAO,GAAG,GAAG,KAAK;KACjB,UAAU;AAEb,QAAA,gBAAsB,IAAI,QAAQ;;;;;;CAOpC,MAAM,MAAM,IAA2B;AACrC,QAAM,QAAQ,KAAK,MAAA,MAAY,IAAI,MAAA,WAAiB,OAAO,CAAC;;;;;;CAO9D,KAAK,IAAgB,KAAqB;AACxC,MAAI,KAAK,WAAW,EAAE;AAEpB,MADY,CAAC,OAAO,eAAe,aAAa,SAAS,WAC/C,YAAY,MAAA,eAAqB,OAAO,GAAG;AACrD,SAAA,WAAiB,OAAO;;;;;;CAO5B,UAAyB;AACvB,SAAO,MAAA;;;;;;;;;CAUT,MAAM,QAAQ,IAAgB,KAA6B;EACzD,MAAM,QAAQ,MAAA;AACd,QAAA,aAAmB,KAAK,IAAI,QAAQ,GAAG,MAAA,cAAoB;AAK3D,MAAI,gBAAgB,KAAK,mBAAmB,CAC1C,OAAM,IAAI,WAAW,mBAAmB,EAAC,OAAO,KAAI,CAAC;AAGvD,MAAI,eAAe,cAAc,eAAe,oBAAoB;AAClE,QAAK,cAAc;AACnB,QAAK,KAAK,IAAI,IAAI;aACT,KAAK,WAAW,EAAE;AAK3B,MAFE,QAAQ,MAAO,SAAS,QAAQ,OAAO,SAAS,WAExC,YAAY,MAAA,YAAkB,MAAM,MAAM,MAAM,IAAI;AAC9D,SAAM,KAAK,MAAM,MAAM;;;;;;;;;;CAW3B,eAAe;EACb,MAAM,YAAY,MAAA;AAClB,QAAA,aAAmB,MAAA;AACnB,SAAO;;;;;;;AAQX,IAAa,qBAAb,cAAwC,MAAM;CAC5C,OAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"shadow-sync-service.js","names":["#lc","#shard","#upstreamURI","#context","#options","#state"],"sources":["../../../../../../zero-cache/src/services/shadow-sync/shadow-sync-service.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport type {ShardConfig} from '../../types/shards.ts';\nimport type {ServerContext} from '../change-source/pg/initial-sync.ts';\nimport {shadowInitialSync} from '../change-source/pg/initial-sync.ts';\nimport {RunningState} from '../running-state.ts';\nimport type {Service} from '../service.ts';\n\nconst shadowSyncRuns = getOrCreateCounter(\n 'replication',\n 'shadow-sync-runs',\n 'Number of shadow initial-sync runs, labeled by result.',\n);\n\nconst shadowSyncDuration = getOrCreateLatencyHistogram(\n 'replication',\n 'shadow-sync-duration',\n 'Wall-clock duration of a shadow initial-sync run, labeled by result.',\n);\n\nexport type ShadowSyncOptions = {\n intervalMs: number;\n sampleRate: number;\n maxRowsPerTable: number;\n textCopy?: boolean | undefined;\n};\n\nexport class ShadowSyncService implements Service {\n readonly id = 'shadow-syncer';\n\n readonly #lc: LogContext;\n readonly #shard: ShardConfig;\n readonly #upstreamURI: string;\n readonly #context: ServerContext;\n readonly #options: ShadowSyncOptions;\n readonly #state = new RunningState('shadow-syncer');\n\n constructor(\n lc: LogContext,\n shard: ShardConfig,\n upstreamURI: string,\n context: ServerContext,\n options: ShadowSyncOptions,\n ) {\n this.#lc = lc;\n this.#shard = shard;\n this.#upstreamURI = upstreamURI;\n this.#context = context;\n this.#options = options;\n }\n\n async run() {\n const {intervalMs, sampleRate, maxRowsPerTable, textCopy} = this.#options;\n\n // Why: first run fires in [intervalMs * 2/3, intervalMs) — late enough\n // that shadow sync never fires immediately on task startup, but always\n // before one full interval elapses so the canary completes at least once\n // per task lifetime (the replication manager is restarted every ~24h).\n // The last third is randomized so a fleet-wide restart does not cause\n // every task to canary simultaneously.\n const minFirstRunDelay = Math.floor((intervalMs * 2) / 3);\n const firstRunDelay =\n minFirstRunDelay +\n Math.floor(Math.random() * (intervalMs - minFirstRunDelay));\n this.#lc.info?.(\n `shadow-syncer started; first run in ${firstRunDelay} ms, then every ${intervalMs} ms`,\n );\n await this.#state.sleep(firstRunDelay);\n\n while (this.#state.shouldRun()) {\n const start = performance.now();\n try {\n await shadowInitialSync(\n this.#lc,\n this.#shard,\n this.#upstreamURI,\n {sampleRate, maxRowsPerTable},\n this.#context,\n textCopy !== undefined ? {textCopy} : undefined,\n );\n const elapsed = performance.now() - start;\n shadowSyncRuns.add(1, {result: 'success'});\n shadowSyncDuration.recordMs(elapsed, {result: 'success'});\n this.#lc.info?.(\n `shadow initial-sync completed (${elapsed.toFixed(0)} ms)`,\n );\n } catch (e) {\n const elapsed = performance.now() - start;\n shadowSyncRuns.add(1, {result: 'error'});\n shadowSyncDuration.recordMs(elapsed, {result: 'error'});\n this.#lc.error?.(\n `shadow initial-sync failed after ${elapsed.toFixed(0)} ms`,\n e,\n );\n }\n await this.#state.sleep(intervalMs);\n }\n }\n\n stop(): Promise<void> {\n this.#state.stop(this.#lc);\n return promiseVoid;\n }\n}\n"],"mappings":";;;;;AAYA,IAAM,iBAAiB,mBACrB,eACA,oBACA,wDACF;AAEA,IAAM,qBAAqB,4BACzB,eACA,wBACA,sEACF;AASA,IAAa,oBAAb,MAAkD;CAChD,KAAc;CAEd;CACA;CACA;CACA;CACA;CACA,SAAkB,IAAI,aAAa,eAAe;CAElD,YACE,IACA,OACA,aACA,SACA,SACA;EACA,KAAKA,MAAM;EACX,KAAKC,SAAS;EACd,KAAKC,eAAe;EACpB,KAAKC,WAAW;EAChB,KAAKC,WAAW;CAClB;CAEA,MAAM,MAAM;EACV,MAAM,EAAC,YAAY,YAAY,iBAAiB,aAAY,KAAKA;EAQjE,MAAM,mBAAmB,KAAK,MAAO,aAAa,IAAK,CAAC;EACxD,MAAM,gBACJ,mBACA,KAAK,MAAM,KAAK,OAAO,KAAK,aAAa,iBAAiB;EAC5D,KAAKJ,IAAI,OACP,uCAAuC,cAAc,kBAAkB,WAAW,IACpF;EACA,MAAM,KAAKK,OAAO,MAAM,aAAa;EAErC,OAAO,KAAKA,OAAO,UAAU,GAAG;GAC9B,MAAM,QAAQ,YAAY,IAAI;GAC9B,IAAI;IACF,MAAM,kBACJ,KAAKL,KACL,KAAKC,QACL,KAAKC,cACL;KAAC;KAAY;IAAe,GAC5B,KAAKC,UACL,aAAa,KAAA,IAAY,EAAC,SAAQ,IAAI,KAAA,CACxC;IACA,MAAM,UAAU,YAAY,IAAI,IAAI;IACpC,eAAe,IAAI,GAAG,EAAC,QAAQ,UAAS,CAAC;IACzC,mBAAmB,SAAS,SAAS,EAAC,QAAQ,UAAS,CAAC;IACxD,KAAKH,IAAI,OACP,kCAAkC,QAAQ,QAAQ,CAAC,EAAE,KACvD;GACF,SAAS,GAAG;IACV,MAAM,UAAU,YAAY,IAAI,IAAI;IACpC,eAAe,IAAI,GAAG,EAAC,QAAQ,QAAO,CAAC;IACvC,mBAAmB,SAAS,SAAS,EAAC,QAAQ,QAAO,CAAC;IACtD,KAAKA,IAAI,QACP,oCAAoC,QAAQ,QAAQ,CAAC,EAAE,MACvD,CACF;GACF;GACA,MAAM,KAAKK,OAAO,MAAM,UAAU;EACpC;CACF;CAEA,OAAsB;EACpB,KAAKA,OAAO,KAAK,KAAKL,GAAG;EACzB,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"shadow-sync-service.js","names":["#lc","#shard","#upstreamURI","#context","#options","#state"],"sources":["../../../../../../zero-cache/src/services/shadow-sync/shadow-sync-service.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport type {ShardConfig} from '../../types/shards.ts';\nimport type {ServerContext} from '../change-source/pg/initial-sync.ts';\nimport {shadowInitialSync} from '../change-source/pg/initial-sync.ts';\nimport {RunningState} from '../running-state.ts';\nimport type {Service} from '../service.ts';\n\nconst shadowSyncRuns = getOrCreateCounter(\n 'replication',\n 'shadow-sync-runs',\n 'Number of shadow initial-sync runs, labeled by result.',\n);\n\nconst shadowSyncDuration = getOrCreateLatencyHistogram(\n 'replication',\n 'shadow-sync-duration',\n 'Wall-clock duration of a shadow initial-sync run, labeled by result.',\n);\n\nexport type ShadowSyncOptions = {\n intervalMs: number;\n sampleRate: number;\n maxRowsPerTable: number;\n textCopy?: boolean | undefined;\n};\n\nexport class ShadowSyncService implements Service {\n readonly id = 'shadow-syncer';\n\n readonly #lc: LogContext;\n readonly #shard: ShardConfig;\n readonly #upstreamURI: string;\n readonly #context: ServerContext;\n readonly #options: ShadowSyncOptions;\n readonly #state = new RunningState('shadow-syncer');\n\n constructor(\n lc: LogContext,\n shard: ShardConfig,\n upstreamURI: string,\n context: ServerContext,\n options: ShadowSyncOptions,\n ) {\n this.#lc = lc;\n this.#shard = shard;\n this.#upstreamURI = upstreamURI;\n this.#context = context;\n this.#options = options;\n }\n\n async run() {\n const {intervalMs, sampleRate, maxRowsPerTable, textCopy} = this.#options;\n\n // Why: first run fires in [intervalMs * 2/3, intervalMs) — late enough\n // that shadow sync never fires immediately on task startup, but always\n // before one full interval elapses so the canary completes at least once\n // per task lifetime (the replication manager is restarted every ~24h).\n // The last third is randomized so a fleet-wide restart does not cause\n // every task to canary simultaneously.\n const minFirstRunDelay = Math.floor((intervalMs * 2) / 3);\n const firstRunDelay =\n minFirstRunDelay +\n Math.floor(Math.random() * (intervalMs - minFirstRunDelay));\n this.#lc.info?.(\n `shadow-syncer started; first run in ${firstRunDelay} ms, then every ${intervalMs} ms`,\n );\n await this.#state.sleep(firstRunDelay);\n\n while (this.#state.shouldRun()) {\n const start = performance.now();\n try {\n await shadowInitialSync(\n this.#lc,\n this.#shard,\n this.#upstreamURI,\n {sampleRate, maxRowsPerTable},\n this.#context,\n textCopy !== undefined ? {textCopy} : undefined,\n );\n const elapsed = performance.now() - start;\n shadowSyncRuns.add(1, {result: 'success'});\n shadowSyncDuration.recordMs(elapsed, {result: 'success'});\n this.#lc.info?.(\n `shadow initial-sync completed (${elapsed.toFixed(0)} ms)`,\n );\n } catch (e) {\n const elapsed = performance.now() - start;\n shadowSyncRuns.add(1, {result: 'error'});\n shadowSyncDuration.recordMs(elapsed, {result: 'error'});\n this.#lc.error?.(\n `shadow initial-sync failed after ${elapsed.toFixed(0)} ms`,\n e,\n );\n }\n await this.#state.sleep(intervalMs);\n }\n }\n\n stop(): Promise<void> {\n this.#state.stop(this.#lc);\n return promiseVoid;\n }\n}\n"],"mappings":";;;;;AAYA,IAAM,iBAAiB,mBACrB,eACA,oBACA,yDACD;AAED,IAAM,qBAAqB,4BACzB,eACA,wBACA,uEACD;AASD,IAAa,oBAAb,MAAkD;CAChD,KAAc;CAEd;CACA;CACA;CACA;CACA;CACA,SAAkB,IAAI,aAAa,gBAAgB;CAEnD,YACE,IACA,OACA,aACA,SACA,SACA;AACA,QAAA,KAAW;AACX,QAAA,QAAc;AACd,QAAA,cAAoB;AACpB,QAAA,UAAgB;AAChB,QAAA,UAAgB;;CAGlB,MAAM,MAAM;EACV,MAAM,EAAC,YAAY,YAAY,iBAAiB,aAAY,MAAA;EAQ5D,MAAM,mBAAmB,KAAK,MAAO,aAAa,IAAK,EAAE;EACzD,MAAM,gBACJ,mBACA,KAAK,MAAM,KAAK,QAAQ,IAAI,aAAa,kBAAkB;AAC7D,QAAA,GAAS,OACP,uCAAuC,cAAc,kBAAkB,WAAW,KACnF;AACD,QAAM,MAAA,MAAY,MAAM,cAAc;AAEtC,SAAO,MAAA,MAAY,WAAW,EAAE;GAC9B,MAAM,QAAQ,YAAY,KAAK;AAC/B,OAAI;AACF,UAAM,kBACJ,MAAA,IACA,MAAA,OACA,MAAA,aACA;KAAC;KAAY;KAAgB,EAC7B,MAAA,SACA,aAAa,KAAA,IAAY,EAAC,UAAS,GAAG,KAAA,EACvC;IACD,MAAM,UAAU,YAAY,KAAK,GAAG;AACpC,mBAAe,IAAI,GAAG,EAAC,QAAQ,WAAU,CAAC;AAC1C,uBAAmB,SAAS,SAAS,EAAC,QAAQ,WAAU,CAAC;AACzD,UAAA,GAAS,OACP,kCAAkC,QAAQ,QAAQ,EAAE,CAAC,MACtD;YACM,GAAG;IACV,MAAM,UAAU,YAAY,KAAK,GAAG;AACpC,mBAAe,IAAI,GAAG,EAAC,QAAQ,SAAQ,CAAC;AACxC,uBAAmB,SAAS,SAAS,EAAC,QAAQ,SAAQ,CAAC;AACvD,UAAA,GAAS,QACP,oCAAoC,QAAQ,QAAQ,EAAE,CAAC,MACvD,EACD;;AAEH,SAAM,MAAA,MAAY,MAAM,WAAW;;;CAIvC,OAAsB;AACpB,QAAA,MAAY,KAAK,MAAA,GAAS;AAC1B,SAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"statz.js","names":[],"sources":["../../../../../zero-cache/src/services/statz.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport type {LogContext} from '@rocicorp/logger';\nimport auth from 'basic-auth';\nimport type {FastifyReply, FastifyRequest} from 'fastify';\nimport {BigIntJSON} from '../../../shared/src/bigint-json.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {NormalizedZeroConfig as ZeroConfig} from '../config/normalize.ts';\nimport {isAdminPasswordValid} from '../config/zero-config.ts';\nimport {StatementRunner} from '../db/statements.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {getShardID, upstreamSchema} from '../types/shards.ts';\nimport {fromStateVersionString} from './change-source/pg/lsn.ts';\nimport {getReplicationState} from './replicator/schema/replication-state.ts';\n\nasync function upstreamStats(lc: LogContext, config: ZeroConfig) {\n const schema = upstreamSchema(getShardID(config));\n const sql = pgClient(lc, config.upstream.db, 'statz-upstream');\n try {\n return await getPgStats([\n [\n 'numReplicas',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"replicas\"`,\n ],\n [\n 'numClientsWithMutations',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"clients\"`,\n ],\n [\n 'numMutationsProcessed',\n sql`SELECT SUM(\"lastMutationID\") as \"c\" FROM ${sql(schema)}.\"clients\"`,\n ],\n ]);\n } finally {\n await sql.end();\n }\n}\n\nasync function cvrStats(lc: LogContext, config: ZeroConfig) {\n const schema = upstreamSchema(getShardID(config)) + '/cvr';\n const sql = pgClient(lc, config.cvr.db, 'statz-cvr');\n\n function numQueriesPerClientGroup(\n active: boolean,\n ): ReturnType<ReturnType<typeof pgClient>> {\n const filter = active\n ? sql`WHERE \"inactivatedAt\" IS NULL AND deleted = false`\n : sql`WHERE \"inactivatedAt\" IS NOT NULL AND (\"inactivatedAt\" + \"ttl\") > NOW()`;\n return sql`WITH\n group_counts AS (\n SELECT\n \"clientGroupID\",\n COUNT(*) AS num_queries\n FROM ${sql(schema)}.\"desires\"\n ${filter}\n GROUP BY \"clientGroupID\"\n ),\n -- Count distinct clientIDs per clientGroupID\n client_per_group_counts AS (\n SELECT\n \"clientGroupID\",\n COUNT(DISTINCT \"clientID\") AS num_clients\n FROM ${sql(schema)}.\"desires\"\n ${filter}\n GROUP BY \"clientGroupID\"\n )\n -- Combine all the information\n SELECT\n g.\"clientGroupID\",\n cpg.num_clients,\n g.num_queries\n FROM group_counts g\n JOIN client_per_group_counts cpg ON g.\"clientGroupID\" = cpg.\"clientGroupID\"\n ORDER BY g.num_queries DESC;`;\n }\n\n try {\n return await getPgStats([\n [\n 'totalNumQueries',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"desires\"`,\n ],\n [\n 'numUniqueQueryHashes',\n sql`SELECT COUNT(DISTINCT \"queryHash\") as \"c\" FROM ${sql(\n schema,\n )}.\"desires\"`,\n ],\n [\n 'numActiveQueries',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"desires\" WHERE \"inactivatedAt\" IS NULL AND \"deleted\" = false`,\n ],\n [\n 'numInactiveQueries',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"desires\" WHERE \"inactivatedAt\" IS NOT NULL AND (\"inactivatedAt\" + \"ttl\") > NOW()`,\n ],\n [\n 'numDeletedQueries',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"desires\" WHERE \"deleted\" = true`,\n ],\n [\n 'freshQueriesPercentiles',\n sql`WITH client_group_counts AS (\n -- Count inactive desires per clientGroupID\n SELECT\n \"clientGroupID\",\n COUNT(*) AS fresh_count\n FROM ${sql(schema)}.\"desires\"\n WHERE\n (\"inactivatedAt\" IS NOT NULL\n AND (\"inactivatedAt\" + \"ttl\") > NOW()) OR (\"inactivatedAt\" IS NULL\n AND deleted = false)\n GROUP BY \"clientGroupID\"\n )\n\n SELECT\n percentile_cont(0.50) WITHIN GROUP (ORDER BY fresh_count) AS \"p50\",\n percentile_cont(0.75) WITHIN GROUP (ORDER BY fresh_count) AS \"p75\",\n percentile_cont(0.90) WITHIN GROUP (ORDER BY fresh_count) AS \"p90\",\n percentile_cont(0.95) WITHIN GROUP (ORDER BY fresh_count) AS \"p95\",\n percentile_cont(0.99) WITHIN GROUP (ORDER BY fresh_count) AS \"p99\",\n MIN(fresh_count) AS \"min\",\n MAX(fresh_count) AS \"max\",\n AVG(fresh_count) AS \"avg\"\n FROM client_group_counts;`,\n ],\n [\n 'rowsPerClientGroupPercentiles',\n sql`WITH client_group_counts AS (\n -- Count inactive desires per clientGroupID\n SELECT\n \"clientGroupID\",\n COUNT(*) AS row_count\n FROM ${sql(schema)}.\"rows\"\n GROUP BY \"clientGroupID\"\n )\n SELECT\n percentile_cont(0.50) WITHIN GROUP (ORDER BY row_count) AS \"p50\",\n percentile_cont(0.75) WITHIN GROUP (ORDER BY row_count) AS \"p75\",\n percentile_cont(0.90) WITHIN GROUP (ORDER BY row_count) AS \"p90\",\n percentile_cont(0.95) WITHIN GROUP (ORDER BY row_count) AS \"p95\",\n percentile_cont(0.99) WITHIN GROUP (ORDER BY row_count) AS \"p99\",\n MIN(row_count) AS \"min\",\n MAX(row_count) AS \"max\",\n AVG(row_count) AS \"avg\"\n FROM client_group_counts;`,\n ],\n [\n // check for AST blowup due to DNF conversion.\n 'astSizes',\n sql`SELECT\n percentile_cont(0.25) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"25th_percentile\",\n percentile_cont(0.5) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"50th_percentile\",\n percentile_cont(0.75) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"75th_percentile\",\n percentile_cont(0.9) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"90th_percentile\",\n percentile_cont(0.95) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"95th_percentile\",\n percentile_cont(0.99) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"99th_percentile\",\n MIN(length(\"clientAST\"::text)) AS \"minimum_length\",\n MAX(length(\"clientAST\"::text)) AS \"maximum_length\",\n AVG(length(\"clientAST\"::text))::integer AS \"average_length\",\n COUNT(*) AS \"total_records\"\n FROM ${sql(schema)}.\"queries\";`,\n ],\n [\n // output the hash of the largest AST\n 'biggestAstHash',\n sql`SELECT \"queryHash\", length(\"clientAST\"::text) AS \"ast_length\"\n FROM ${sql(schema)}.\"queries\"\n ORDER BY length(\"clientAST\"::text) DESC\n LIMIT 1;`,\n ],\n [\n 'totalActiveQueriesPerClientAndClientGroup',\n numQueriesPerClientGroup(true),\n ],\n [\n 'totalInactiveQueriesPerClientAndClientGroup',\n numQueriesPerClientGroup(false),\n ],\n [\n 'totalRowsPerClientGroup',\n sql`SELECT \"clientGroupID\", COUNT(*) as \"c\" FROM ${sql(\n schema,\n )}.\"rows\" GROUP BY \"clientGroupID\" ORDER BY \"c\" DESC`,\n ],\n [\n 'numRowsPerQuery',\n sql`SELECT\n k.key AS \"queryHash\",\n COUNT(*) AS row_count\n FROM ${sql(schema)}.\"rows\" r,\n LATERAL jsonb_each(r.\"refCounts\") k\n GROUP BY k.key\n ORDER BY row_count DESC;`,\n ],\n ] satisfies [\n name: string,\n query: ReturnType<ReturnType<typeof pgClient>>,\n ][]);\n } finally {\n await sql.end();\n }\n}\n\nasync function changeLogStats(lc: LogContext, config: ZeroConfig) {\n const schema = upstreamSchema(getShardID(config)) + '/cdc';\n const sql = pgClient(lc, config.change.db, 'statz-change');\n\n try {\n return await getPgStats([\n [\n 'changeLogSize',\n sql`SELECT COUNT(*) as \"change_log_size\" FROM ${sql(schema)}.\"changeLog\"`,\n ],\n ]);\n } finally {\n await sql.end();\n }\n}\n\nfunction replicaStats(lc: LogContext, config: ZeroConfig) {\n const db = new Database(lc, config.replica.file);\n try {\n return Object.fromEntries([\n ['wal checkpoint', pick(first(db.pragma('WAL_CHECKPOINT')))],\n ['page count', pick(first(db.pragma('PAGE_COUNT')))],\n ['page size', pick(first(db.pragma('PAGE_SIZE')))],\n ['journal mode', pick(first(db.pragma('JOURNAL_MODE')))],\n ['synchronous', pick(first(db.pragma('SYNCHRONOUS')))],\n ['cache size', pick(first(db.pragma('CACHE_SIZE')))],\n ['auto vacuum', pick(first(db.pragma('AUTO_VACUUM')))],\n ['freelist count', pick(first(db.pragma('FREELIST_COUNT')))],\n ['wal autocheckpoint', pick(first(db.pragma('WAL_AUTOCHECKPOINT')))],\n ['db file stats', fs.statSync(config.replica.file)],\n ] as const);\n } finally {\n db.close();\n }\n}\n\nfunction replicationStats(lc: LogContext, config: ZeroConfig) {\n const db = new Database(lc, config.replica.file);\n try {\n return getReplicationStats(db);\n } finally {\n db.close();\n }\n}\n\nfunction getReplicationStats(db: Database) {\n const {stateVersion} = getReplicationState(new StatementRunner(db));\n const lsn = fromStateVersionString(stateVersion);\n return {lsn};\n}\n\nfunction osStats() {\n return Object.fromEntries([\n ['load avg', os.loadavg()],\n ['uptime', os.uptime()],\n ['total mem', os.totalmem()],\n ['free mem', os.freemem()],\n ['cpus', os.cpus().length],\n ['available parallelism', os.availableParallelism()],\n ['platform', os.platform()],\n ['arch', os.arch()],\n ['release', os.release()],\n ] as const);\n}\n\nasync function getPgStats(\n pendingQueries: [\n name: string,\n query: ReturnType<ReturnType<typeof pgClient>>,\n ][],\n) {\n const results = await Promise.all(\n pendingQueries.map(async ([name, query]) => [name, await query] as const),\n );\n return Object.fromEntries(results);\n}\n\ntype StatsObject = Record<string, unknown>;\n\nfunction printStats(group: string, statsObject: StatsObject): string {\n const lines: string[] = ['\\n' + header(group)];\n for (const [name, result] of Object.entries(statsObject)) {\n lines.push('\\n' + name + ': ' + BigIntJSON.stringify(result, null, 2));\n }\n lines.push('\\n');\n return lines.join('');\n}\n\n/**\n * HTTP query parameters:\n * * `group`: restricts the groups for which stats are computed\n * * `format=json`: returns the stats as a JSON object\n * * `pretty`: formats the JSON object with indentation\n */\nexport async function handleStatzRequest(\n lc: LogContext,\n config: ZeroConfig,\n req: FastifyRequest,\n res: FastifyReply,\n) {\n const credentials = auth(req);\n if (!isAdminPasswordValid(lc, config, credentials?.pass)) {\n void res\n .code(401)\n .header('WWW-Authenticate', 'Basic realm=\"Statz Protected Area\"')\n .send('Unauthorized');\n return;\n }\n\n const statsFns: Record<string, () => Promise<StatsObject> | StatsObject> = {\n upstream: () => upstreamStats(lc, config),\n cvr: () => cvrStats(lc, config),\n changeLog: () => changeLogStats(lc, config),\n replica: () => replicaStats(lc, config),\n replication: () => replicationStats(lc, config),\n os: () => osStats(),\n };\n\n async function computeStats(group: string): Promise<[string, StatsObject]> {\n try {\n return [group, await statsFns[group]()];\n } catch (e) {\n lc.error?.(`error computing ${group} stats`, e);\n return [group, {error: String(e)}];\n }\n }\n\n const query = req.query as Record<string, unknown>;\n const groups =\n typeof query.group === 'string'\n ? query.group.split(',')\n : Array.isArray(query.group)\n ? query.group\n : undefined;\n\n const stats = await Promise.all(\n groups\n ? groups.filter(g => g in statsFns).map(computeStats)\n : Object.keys(statsFns).map(computeStats),\n );\n\n if (query.format === 'json') {\n const indent = query.pretty !== undefined ? 2 : undefined;\n await res\n .header('Content-Type', 'application/json')\n .send(BigIntJSON.stringify(Object.fromEntries(stats), null, indent));\n return;\n } else {\n const body = stats\n .map(([group, statsObject]) => printStats(group, statsObject))\n .join('');\n await res.header('Content-Type', 'text/plain; charset=utf-8').send(body);\n }\n}\n\nfunction first(x: object[]): object {\n return x[0];\n}\n\nfunction pick(x: object): unknown {\n return Object.values(x)[0];\n}\n\nfunction header(name: string): string {\n return `=== ${name} ===\\n`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"statz.js","names":[],"sources":["../../../../../zero-cache/src/services/statz.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport type {LogContext} from '@rocicorp/logger';\nimport auth from 'basic-auth';\nimport type {FastifyReply, FastifyRequest} from 'fastify';\nimport {BigIntJSON} from '../../../shared/src/bigint-json.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {NormalizedZeroConfig as ZeroConfig} from '../config/normalize.ts';\nimport {isAdminPasswordValid} from '../config/zero-config.ts';\nimport {StatementRunner} from '../db/statements.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {getShardID, upstreamSchema} from '../types/shards.ts';\nimport {fromStateVersionString} from './change-source/pg/lsn.ts';\nimport {getReplicationState} from './replicator/schema/replication-state.ts';\n\nasync function upstreamStats(lc: LogContext, config: ZeroConfig) {\n const schema = upstreamSchema(getShardID(config));\n const sql = pgClient(lc, config.upstream.db, 'statz-upstream');\n try {\n return await getPgStats([\n [\n 'numReplicas',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"replicas\"`,\n ],\n [\n 'numClientsWithMutations',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"clients\"`,\n ],\n [\n 'numMutationsProcessed',\n sql`SELECT SUM(\"lastMutationID\") as \"c\" FROM ${sql(schema)}.\"clients\"`,\n ],\n ]);\n } finally {\n await sql.end();\n }\n}\n\nasync function cvrStats(lc: LogContext, config: ZeroConfig) {\n const schema = upstreamSchema(getShardID(config)) + '/cvr';\n const sql = pgClient(lc, config.cvr.db, 'statz-cvr');\n\n function numQueriesPerClientGroup(\n active: boolean,\n ): ReturnType<ReturnType<typeof pgClient>> {\n const filter = active\n ? sql`WHERE \"inactivatedAt\" IS NULL AND deleted = false`\n : sql`WHERE \"inactivatedAt\" IS NOT NULL AND (\"inactivatedAt\" + \"ttl\") > NOW()`;\n return sql`WITH\n group_counts AS (\n SELECT\n \"clientGroupID\",\n COUNT(*) AS num_queries\n FROM ${sql(schema)}.\"desires\"\n ${filter}\n GROUP BY \"clientGroupID\"\n ),\n -- Count distinct clientIDs per clientGroupID\n client_per_group_counts AS (\n SELECT\n \"clientGroupID\",\n COUNT(DISTINCT \"clientID\") AS num_clients\n FROM ${sql(schema)}.\"desires\"\n ${filter}\n GROUP BY \"clientGroupID\"\n )\n -- Combine all the information\n SELECT\n g.\"clientGroupID\",\n cpg.num_clients,\n g.num_queries\n FROM group_counts g\n JOIN client_per_group_counts cpg ON g.\"clientGroupID\" = cpg.\"clientGroupID\"\n ORDER BY g.num_queries DESC;`;\n }\n\n try {\n return await getPgStats([\n [\n 'totalNumQueries',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"desires\"`,\n ],\n [\n 'numUniqueQueryHashes',\n sql`SELECT COUNT(DISTINCT \"queryHash\") as \"c\" FROM ${sql(\n schema,\n )}.\"desires\"`,\n ],\n [\n 'numActiveQueries',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"desires\" WHERE \"inactivatedAt\" IS NULL AND \"deleted\" = false`,\n ],\n [\n 'numInactiveQueries',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"desires\" WHERE \"inactivatedAt\" IS NOT NULL AND (\"inactivatedAt\" + \"ttl\") > NOW()`,\n ],\n [\n 'numDeletedQueries',\n sql`SELECT COUNT(*) as \"c\" FROM ${sql(schema)}.\"desires\" WHERE \"deleted\" = true`,\n ],\n [\n 'freshQueriesPercentiles',\n sql`WITH client_group_counts AS (\n -- Count inactive desires per clientGroupID\n SELECT\n \"clientGroupID\",\n COUNT(*) AS fresh_count\n FROM ${sql(schema)}.\"desires\"\n WHERE\n (\"inactivatedAt\" IS NOT NULL\n AND (\"inactivatedAt\" + \"ttl\") > NOW()) OR (\"inactivatedAt\" IS NULL\n AND deleted = false)\n GROUP BY \"clientGroupID\"\n )\n\n SELECT\n percentile_cont(0.50) WITHIN GROUP (ORDER BY fresh_count) AS \"p50\",\n percentile_cont(0.75) WITHIN GROUP (ORDER BY fresh_count) AS \"p75\",\n percentile_cont(0.90) WITHIN GROUP (ORDER BY fresh_count) AS \"p90\",\n percentile_cont(0.95) WITHIN GROUP (ORDER BY fresh_count) AS \"p95\",\n percentile_cont(0.99) WITHIN GROUP (ORDER BY fresh_count) AS \"p99\",\n MIN(fresh_count) AS \"min\",\n MAX(fresh_count) AS \"max\",\n AVG(fresh_count) AS \"avg\"\n FROM client_group_counts;`,\n ],\n [\n 'rowsPerClientGroupPercentiles',\n sql`WITH client_group_counts AS (\n -- Count inactive desires per clientGroupID\n SELECT\n \"clientGroupID\",\n COUNT(*) AS row_count\n FROM ${sql(schema)}.\"rows\"\n GROUP BY \"clientGroupID\"\n )\n SELECT\n percentile_cont(0.50) WITHIN GROUP (ORDER BY row_count) AS \"p50\",\n percentile_cont(0.75) WITHIN GROUP (ORDER BY row_count) AS \"p75\",\n percentile_cont(0.90) WITHIN GROUP (ORDER BY row_count) AS \"p90\",\n percentile_cont(0.95) WITHIN GROUP (ORDER BY row_count) AS \"p95\",\n percentile_cont(0.99) WITHIN GROUP (ORDER BY row_count) AS \"p99\",\n MIN(row_count) AS \"min\",\n MAX(row_count) AS \"max\",\n AVG(row_count) AS \"avg\"\n FROM client_group_counts;`,\n ],\n [\n // check for AST blowup due to DNF conversion.\n 'astSizes',\n sql`SELECT\n percentile_cont(0.25) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"25th_percentile\",\n percentile_cont(0.5) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"50th_percentile\",\n percentile_cont(0.75) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"75th_percentile\",\n percentile_cont(0.9) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"90th_percentile\",\n percentile_cont(0.95) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"95th_percentile\",\n percentile_cont(0.99) WITHIN GROUP (ORDER BY length(\"clientAST\"::text)) AS \"99th_percentile\",\n MIN(length(\"clientAST\"::text)) AS \"minimum_length\",\n MAX(length(\"clientAST\"::text)) AS \"maximum_length\",\n AVG(length(\"clientAST\"::text))::integer AS \"average_length\",\n COUNT(*) AS \"total_records\"\n FROM ${sql(schema)}.\"queries\";`,\n ],\n [\n // output the hash of the largest AST\n 'biggestAstHash',\n sql`SELECT \"queryHash\", length(\"clientAST\"::text) AS \"ast_length\"\n FROM ${sql(schema)}.\"queries\"\n ORDER BY length(\"clientAST\"::text) DESC\n LIMIT 1;`,\n ],\n [\n 'totalActiveQueriesPerClientAndClientGroup',\n numQueriesPerClientGroup(true),\n ],\n [\n 'totalInactiveQueriesPerClientAndClientGroup',\n numQueriesPerClientGroup(false),\n ],\n [\n 'totalRowsPerClientGroup',\n sql`SELECT \"clientGroupID\", COUNT(*) as \"c\" FROM ${sql(\n schema,\n )}.\"rows\" GROUP BY \"clientGroupID\" ORDER BY \"c\" DESC`,\n ],\n [\n 'numRowsPerQuery',\n sql`SELECT\n k.key AS \"queryHash\",\n COUNT(*) AS row_count\n FROM ${sql(schema)}.\"rows\" r,\n LATERAL jsonb_each(r.\"refCounts\") k\n GROUP BY k.key\n ORDER BY row_count DESC;`,\n ],\n ] satisfies [\n name: string,\n query: ReturnType<ReturnType<typeof pgClient>>,\n ][]);\n } finally {\n await sql.end();\n }\n}\n\nasync function changeLogStats(lc: LogContext, config: ZeroConfig) {\n const schema = upstreamSchema(getShardID(config)) + '/cdc';\n const sql = pgClient(lc, config.change.db, 'statz-change');\n\n try {\n return await getPgStats([\n [\n 'changeLogSize',\n sql`SELECT COUNT(*) as \"change_log_size\" FROM ${sql(schema)}.\"changeLog\"`,\n ],\n ]);\n } finally {\n await sql.end();\n }\n}\n\nfunction replicaStats(lc: LogContext, config: ZeroConfig) {\n const db = new Database(lc, config.replica.file);\n try {\n return Object.fromEntries([\n ['wal checkpoint', pick(first(db.pragma('WAL_CHECKPOINT')))],\n ['page count', pick(first(db.pragma('PAGE_COUNT')))],\n ['page size', pick(first(db.pragma('PAGE_SIZE')))],\n ['journal mode', pick(first(db.pragma('JOURNAL_MODE')))],\n ['synchronous', pick(first(db.pragma('SYNCHRONOUS')))],\n ['cache size', pick(first(db.pragma('CACHE_SIZE')))],\n ['auto vacuum', pick(first(db.pragma('AUTO_VACUUM')))],\n ['freelist count', pick(first(db.pragma('FREELIST_COUNT')))],\n ['wal autocheckpoint', pick(first(db.pragma('WAL_AUTOCHECKPOINT')))],\n ['db file stats', fs.statSync(config.replica.file)],\n ] as const);\n } finally {\n db.close();\n }\n}\n\nfunction replicationStats(lc: LogContext, config: ZeroConfig) {\n const db = new Database(lc, config.replica.file);\n try {\n return getReplicationStats(db);\n } finally {\n db.close();\n }\n}\n\nfunction getReplicationStats(db: Database) {\n const {stateVersion} = getReplicationState(new StatementRunner(db));\n const lsn = fromStateVersionString(stateVersion);\n return {lsn};\n}\n\nfunction osStats() {\n return Object.fromEntries([\n ['load avg', os.loadavg()],\n ['uptime', os.uptime()],\n ['total mem', os.totalmem()],\n ['free mem', os.freemem()],\n ['cpus', os.cpus().length],\n ['available parallelism', os.availableParallelism()],\n ['platform', os.platform()],\n ['arch', os.arch()],\n ['release', os.release()],\n ] as const);\n}\n\nasync function getPgStats(\n pendingQueries: [\n name: string,\n query: ReturnType<ReturnType<typeof pgClient>>,\n ][],\n) {\n const results = await Promise.all(\n pendingQueries.map(async ([name, query]) => [name, await query] as const),\n );\n return Object.fromEntries(results);\n}\n\ntype StatsObject = Record<string, unknown>;\n\nfunction printStats(group: string, statsObject: StatsObject): string {\n const lines: string[] = ['\\n' + header(group)];\n for (const [name, result] of Object.entries(statsObject)) {\n lines.push('\\n' + name + ': ' + BigIntJSON.stringify(result, null, 2));\n }\n lines.push('\\n');\n return lines.join('');\n}\n\n/**\n * HTTP query parameters:\n * * `group`: restricts the groups for which stats are computed\n * * `format=json`: returns the stats as a JSON object\n * * `pretty`: formats the JSON object with indentation\n */\nexport async function handleStatzRequest(\n lc: LogContext,\n config: ZeroConfig,\n req: FastifyRequest,\n res: FastifyReply,\n) {\n const credentials = auth(req);\n if (!isAdminPasswordValid(lc, config, credentials?.pass)) {\n void res\n .code(401)\n .header('WWW-Authenticate', 'Basic realm=\"Statz Protected Area\"')\n .send('Unauthorized');\n return;\n }\n\n const statsFns: Record<string, () => Promise<StatsObject> | StatsObject> = {\n upstream: () => upstreamStats(lc, config),\n cvr: () => cvrStats(lc, config),\n changeLog: () => changeLogStats(lc, config),\n replica: () => replicaStats(lc, config),\n replication: () => replicationStats(lc, config),\n os: () => osStats(),\n };\n\n async function computeStats(group: string): Promise<[string, StatsObject]> {\n try {\n return [group, await statsFns[group]()];\n } catch (e) {\n lc.error?.(`error computing ${group} stats`, e);\n return [group, {error: String(e)}];\n }\n }\n\n const query = req.query as Record<string, unknown>;\n const groups =\n typeof query.group === 'string'\n ? query.group.split(',')\n : Array.isArray(query.group)\n ? query.group\n : undefined;\n\n const stats = await Promise.all(\n groups\n ? groups.filter(g => g in statsFns).map(computeStats)\n : Object.keys(statsFns).map(computeStats),\n );\n\n if (query.format === 'json') {\n const indent = query.pretty !== undefined ? 2 : undefined;\n await res\n .header('Content-Type', 'application/json')\n .send(BigIntJSON.stringify(Object.fromEntries(stats), null, indent));\n return;\n } else {\n const body = stats\n .map(([group, statsObject]) => printStats(group, statsObject))\n .join('');\n await res.header('Content-Type', 'text/plain; charset=utf-8').send(body);\n }\n}\n\nfunction first(x: object[]): object {\n return x[0];\n}\n\nfunction pick(x: object): unknown {\n return Object.values(x)[0];\n}\n\nfunction header(name: string): string {\n return `=== ${name} ===\\n`;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"active-users-gauge.js","names":["#lc","#db","#schema","#updateIntervalMs","#state","#setActiveUsersGetter","#lastActiveUsers"],"sources":["../../../../../../zero-cache/src/services/view-syncer/active-users-gauge.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {must} from '../../../../shared/src/must.ts';\nimport {mapValues} from '../../../../shared/src/objects.ts';\nimport {\n setActiveUsersGetter,\n type ActiveUsers,\n} from '../../server/anonymous-otel-start.ts';\nimport type {PostgresDB} from '../../types/pg.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {RunningState} from '../running-state.ts';\nimport type {Service} from '../service.ts';\n\nconst MINUTE = 60 * 1000;\nconst DAY = 24 * 60 * MINUTE;\n\ntype Options = {\n updateIntervalMs?: number;\n};\n\nexport class ActiveUsersGauge implements Service {\n readonly id = 'active-users-gauge';\n\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #updateIntervalMs: number;\n readonly #state = new RunningState('active-users-gauge');\n readonly #setActiveUsersGetter: typeof setActiveUsersGetter;\n\n // latest computed value exposed via the observable gauge callback\n #lastActiveUsers: ActiveUsers | undefined;\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n opts: Options = {},\n setActiveUsersGetterFn = setActiveUsersGetter,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#updateIntervalMs = opts.updateIntervalMs ?? 60 * 1000; // default 1 minute\n this.#setActiveUsersGetter = setActiveUsersGetterFn;\n }\n\n async run(): Promise<void> {\n while (this.#state.shouldRun()) {\n try {\n const now = Date.now();\n const since30day = now - DAY * 30;\n const since7day = now - DAY * 7;\n const since1day = now - DAY;\n\n // This query performs a single scan over the `profile_ids_last_active`\n // index to compute aggregated results for all of our active user\n // metric variants.\n //\n // The eventually-correct metrics are `users_#da` which count distinct\n // profileIDs produced by the zero-client (i.e. starting with 'p').\n // The `users_#da_legacy` metrics include back-filled profileIDs (which\n // start with `cg`) and will over-count users on apps using memstore.\n const [actives] = await this.#db<[ActiveUsers]> /*sql*/ `\n SELECT \n COUNT(*) FILTER (WHERE \"lastActive\" >= ${since1day}) AS active_users_last_day,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE \"lastActive\" >= ${since1day} AND starts_with(\"profileID\", 'p')) AS users_1da,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE \"lastActive\" >= ${since7day} AND starts_with(\"profileID\", 'p')) AS users_7da,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE starts_with(\"profileID\", 'p')) AS users_30da,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE \"lastActive\" >= ${since1day}) AS users_1da_legacy,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE \"lastActive\" >= ${since7day}) AS users_7da_legacy,\n COUNT(DISTINCT(\"profileID\")) AS users_30da_legacy\n FROM ${this.#db(this.#schema)}.instances\n WHERE \"lastActive\" >= ${since30day}\n `;\n // Determine if the getter needs to be set (i.e. the first time).\n const setGetter = this.#lastActiveUsers === undefined;\n this.#lastActiveUsers = mapValues(actives, bigVal => Number(bigVal));\n\n // Set the getter after the first value is computed\n if (setGetter) {\n this.#setActiveUsersGetter(() => must(this.#lastActiveUsers));\n }\n\n this.#lc.debug?.(`updated active-users gauge`, this.#lastActiveUsers);\n } catch (e) {\n this.#lc.warn?.('error updating active-users gauge', e);\n }\n\n await this.#state.sleep(this.#updateIntervalMs);\n }\n }\n\n stop(): Promise<void> {\n this.#state.stop(this.#lc);\n return this.#state.stopped();\n }\n}\n"],"mappings":";;;;;;AAaA,IAAM,MAAM,QADG,KAAK;AAOpB,IAAa,mBAAb,MAAiD;CAC/C,KAAc;CAEd;CACA;CACA;CACA;CACA,SAAkB,IAAI,aAAa,oBAAoB;CACvD;CAGA;CAEA,YACE,IACA,IACA,OACA,OAAgB,CAAC,GACjB,yBAAyB,sBACzB;EACA,KAAKA,MAAM;EACX,KAAKC,MAAM;EACX,KAAKC,UAAU,UAAU,KAAK;EAC9B,KAAKC,oBAAoB,KAAK,oBAAoB,KAAK;EACvD,KAAKE,wBAAwB;CAC/B;CAEA,MAAM,MAAqB;EACzB,OAAO,KAAKD,OAAO,UAAU,GAAG;GAC9B,IAAI;IACF,MAAM,MAAM,KAAK,IAAI;IACrB,MAAM,aAAa,MAAM,MAAM;IAC/B,MAAM,YAAY,MAAM,MAAM;IAC9B,MAAM,YAAY,MAAM;IAUxB,MAAM,CAAC,WAAW,MAAM,KAAKH,GAA2B;;qDAEX,UAAU;yEACU,UAAU;yEACV,UAAU;;yEAEV,UAAU;yEACV,UAAU;;mBAEhE,KAAKA,IAAI,KAAKC,OAAO,EAAE;oCACN,WAAW;;IAGvC,MAAM,YAAY,KAAKI,qBAAqB,KAAA;IAC5C,KAAKA,mBAAmB,UAAU,UAAS,WAAU,OAAO,MAAM,CAAC;IAGnE,IAAI,WACF,KAAKD,4BAA4B,KAAK,KAAKC,gBAAgB,CAAC;IAG9D,KAAKN,IAAI,QAAQ,8BAA8B,KAAKM,gBAAgB;GACtE,SAAS,GAAG;IACV,KAAKN,IAAI,OAAO,qCAAqC,CAAC;GACxD;GAEA,MAAM,KAAKI,OAAO,MAAM,KAAKD,iBAAiB;EAChD;CACF;CAEA,OAAsB;EACpB,KAAKC,OAAO,KAAK,KAAKJ,GAAG;EACzB,OAAO,KAAKI,OAAO,QAAQ;CAC7B;AACF"}
1
+ {"version":3,"file":"active-users-gauge.js","names":["#lc","#db","#schema","#updateIntervalMs","#state","#setActiveUsersGetter","#lastActiveUsers"],"sources":["../../../../../../zero-cache/src/services/view-syncer/active-users-gauge.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {must} from '../../../../shared/src/must.ts';\nimport {mapValues} from '../../../../shared/src/objects.ts';\nimport {\n setActiveUsersGetter,\n type ActiveUsers,\n} from '../../server/anonymous-otel-start.ts';\nimport type {PostgresDB} from '../../types/pg.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {RunningState} from '../running-state.ts';\nimport type {Service} from '../service.ts';\n\nconst MINUTE = 60 * 1000;\nconst DAY = 24 * 60 * MINUTE;\n\ntype Options = {\n updateIntervalMs?: number;\n};\n\nexport class ActiveUsersGauge implements Service {\n readonly id = 'active-users-gauge';\n\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #updateIntervalMs: number;\n readonly #state = new RunningState('active-users-gauge');\n readonly #setActiveUsersGetter: typeof setActiveUsersGetter;\n\n // latest computed value exposed via the observable gauge callback\n #lastActiveUsers: ActiveUsers | undefined;\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n opts: Options = {},\n setActiveUsersGetterFn = setActiveUsersGetter,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#updateIntervalMs = opts.updateIntervalMs ?? 60 * 1000; // default 1 minute\n this.#setActiveUsersGetter = setActiveUsersGetterFn;\n }\n\n async run(): Promise<void> {\n while (this.#state.shouldRun()) {\n try {\n const now = Date.now();\n const since30day = now - DAY * 30;\n const since7day = now - DAY * 7;\n const since1day = now - DAY;\n\n // This query performs a single scan over the `profile_ids_last_active`\n // index to compute aggregated results for all of our active user\n // metric variants.\n //\n // The eventually-correct metrics are `users_#da` which count distinct\n // profileIDs produced by the zero-client (i.e. starting with 'p').\n // The `users_#da_legacy` metrics include back-filled profileIDs (which\n // start with `cg`) and will over-count users on apps using memstore.\n const [actives] = await this.#db<[ActiveUsers]> /*sql*/ `\n SELECT \n COUNT(*) FILTER (WHERE \"lastActive\" >= ${since1day}) AS active_users_last_day,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE \"lastActive\" >= ${since1day} AND starts_with(\"profileID\", 'p')) AS users_1da,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE \"lastActive\" >= ${since7day} AND starts_with(\"profileID\", 'p')) AS users_7da,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE starts_with(\"profileID\", 'p')) AS users_30da,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE \"lastActive\" >= ${since1day}) AS users_1da_legacy,\n COUNT(DISTINCT(\"profileID\")) FILTER (WHERE \"lastActive\" >= ${since7day}) AS users_7da_legacy,\n COUNT(DISTINCT(\"profileID\")) AS users_30da_legacy\n FROM ${this.#db(this.#schema)}.instances\n WHERE \"lastActive\" >= ${since30day}\n `;\n // Determine if the getter needs to be set (i.e. the first time).\n const setGetter = this.#lastActiveUsers === undefined;\n this.#lastActiveUsers = mapValues(actives, bigVal => Number(bigVal));\n\n // Set the getter after the first value is computed\n if (setGetter) {\n this.#setActiveUsersGetter(() => must(this.#lastActiveUsers));\n }\n\n this.#lc.debug?.(`updated active-users gauge`, this.#lastActiveUsers);\n } catch (e) {\n this.#lc.warn?.('error updating active-users gauge', e);\n }\n\n await this.#state.sleep(this.#updateIntervalMs);\n }\n }\n\n stop(): Promise<void> {\n this.#state.stop(this.#lc);\n return this.#state.stopped();\n }\n}\n"],"mappings":";;;;;;AAaA,IAAM,MAAM,QADG,KAAK;AAOpB,IAAa,mBAAb,MAAiD;CAC/C,KAAc;CAEd;CACA;CACA;CACA;CACA,SAAkB,IAAI,aAAa,qBAAqB;CACxD;CAGA;CAEA,YACE,IACA,IACA,OACA,OAAgB,EAAE,EAClB,yBAAyB,sBACzB;AACA,QAAA,KAAW;AACX,QAAA,KAAW;AACX,QAAA,SAAe,UAAU,MAAM;AAC/B,QAAA,mBAAyB,KAAK,oBAAoB,KAAK;AACvD,QAAA,uBAA6B;;CAG/B,MAAM,MAAqB;AACzB,SAAO,MAAA,MAAY,WAAW,EAAE;AAC9B,OAAI;IACF,MAAM,MAAM,KAAK,KAAK;IACtB,MAAM,aAAa,MAAM,MAAM;IAC/B,MAAM,YAAY,MAAM,MAAM;IAC9B,MAAM,YAAY,MAAM;IAUxB,MAAM,CAAC,WAAW,MAAM,MAAA,EAAgC;;qDAEX,UAAU;yEACU,UAAU;yEACV,UAAU;;yEAEV,UAAU;yEACV,UAAU;;mBAEhE,MAAA,GAAS,MAAA,OAAa,CAAC;oCACN,WAAW;;IAGvC,MAAM,YAAY,MAAA,oBAA0B,KAAA;AAC5C,UAAA,kBAAwB,UAAU,UAAS,WAAU,OAAO,OAAO,CAAC;AAGpE,QAAI,UACF,OAAA,2BAAiC,KAAK,MAAA,gBAAsB,CAAC;AAG/D,UAAA,GAAS,QAAQ,8BAA8B,MAAA,gBAAsB;YAC9D,GAAG;AACV,UAAA,GAAS,OAAO,qCAAqC,EAAE;;AAGzD,SAAM,MAAA,MAAY,MAAM,MAAA,iBAAuB;;;CAInD,OAAsB;AACpB,QAAA,MAAY,KAAK,MAAA,GAAS;AAC1B,SAAO,MAAA,MAAY,SAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-handler.js","names":["#clientGroupID","#zeroClientsTable","#zeroMutationsTable","#lc","#downstream","#pokeTime","#pokeTransactions","#pokedRows","#baseVersion","#push","#updateLMIDs"],"sources":["../../../../../../zero-cache/src/services/view-syncer/client-handler.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport type {JSONObject} from '../../../../shared/src/bigint-json.ts';\nimport {\n assertJSONValue,\n type JSONObject as SafeJSONObject,\n} from '../../../../shared/src/json.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport type {Writable} from '../../../../shared/src/writable.ts';\nimport type {ErroredQuery} from '../../../../zero-protocol/src/custom-queries.ts';\nimport {rowSchema} from '../../../../zero-protocol/src/data.ts';\nimport type {DeleteClientsBody} from '../../../../zero-protocol/src/delete-clients.ts';\nimport type {Downstream} from '../../../../zero-protocol/src/down.ts';\nimport {\n ProtocolError,\n type TransformFailedBody,\n} from '../../../../zero-protocol/src/error.ts';\nimport type {InspectDownBody} from '../../../../zero-protocol/src/inspect-down.ts';\nimport {mutationResultSchema} from '../../../../zero-protocol/src/mutation.ts';\nimport type {\n PokePartBody,\n PokeStartBody,\n} from '../../../../zero-protocol/src/poke.ts';\nimport {primaryKeyValueRecordSchema} from '../../../../zero-protocol/src/primary-key.ts';\nimport type {RowPatchOp} from '../../../../zero-protocol/src/row-patch.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport {\n getLogLevel,\n wrapWithProtocolError,\n} from '../../types/error-with-level.ts';\nimport {upstreamSchema, type ShardID} from '../../types/shards.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {\n cmpVersions,\n cookieToVersion,\n versionToCookie,\n versionToNullableCookie,\n type CVRVersion,\n type DelQueryPatch,\n type NullableCVRVersion,\n type PutQueryPatch,\n type RowID,\n} from './schema/types.ts';\n\nexport type PutRowPatch = {\n type: 'row';\n op: 'put';\n id: RowID;\n contents: JSONObject;\n};\n\nexport type DeleteRowPatch = {\n type: 'row';\n op: 'del';\n id: RowID;\n};\n\nexport type RowPatch = PutRowPatch | DeleteRowPatch;\nexport type ConfigPatch = DelQueryPatch | PutQueryPatch;\n\nexport type Patch = ConfigPatch | RowPatch;\n\nexport type PatchToVersion = {\n patch: Patch;\n toVersion: CVRVersion;\n};\n\nexport interface PokeHandler {\n addPatch(patch: PatchToVersion): Promise<void>;\n cancel(): Promise<void>;\n end(finalVersion: CVRVersion): Promise<void>;\n}\n\nconst NOOP: PokeHandler = {\n addPatch: () => promiseVoid,\n cancel: () => promiseVoid,\n end: () => promiseVoid,\n};\n\n/** Wraps PokeHandlers for multiple clients in a single PokeHandler. */\nexport function startPoke(\n clients: ClientHandler[],\n tentativeVersion: CVRVersion,\n): PokeHandler {\n const pokers = clients.map(c => c.startPoke(tentativeVersion));\n\n // Promise.allSettled() ensures that a failed (e.g. disconnected) client\n // does not prevent other clients from receiving the pokes. However, the\n // rate (per client group) will be limited by the slowest connection.\n return {\n addPatch: async patch => {\n await Promise.allSettled(pokers.map(poker => poker.addPatch(patch)));\n },\n cancel: async () => {\n await Promise.allSettled(pokers.map(poker => poker.cancel()));\n },\n end: async finalVersion => {\n await Promise.allSettled(pokers.map(poker => poker.end(finalVersion)));\n },\n };\n}\n\n// Semi-arbitrary threshold at which poke body parts are flushed.\n// When row size is being computed, that should be used as a threshold instead.\nconst PART_COUNT_FLUSH_THRESHOLD = 100;\n\n/**\n * Handles a single `ViewSyncer` connection.\n */\nexport class ClientHandler {\n readonly #clientGroupID: string;\n readonly clientID: string;\n readonly wsID: string;\n readonly #zeroClientsTable: string;\n readonly #zeroMutationsTable: string;\n readonly #lc: LogContext;\n readonly #downstream: Subscription<Downstream>;\n #baseVersion: NullableCVRVersion;\n\n readonly #pokeTime = getOrCreateLatencyHistogram(\n 'sync',\n 'poke.time',\n 'Time elapsed for each poke transaction. Canceled / noop pokes are excluded.',\n );\n\n readonly #pokeTransactions = getOrCreateCounter(\n 'sync',\n 'poke.transactions',\n 'Count of poke transactions.',\n );\n\n readonly #pokedRows = getOrCreateCounter(\n 'sync',\n 'poke.rows',\n 'Count of poked rows.',\n );\n\n constructor(\n lc: LogContext,\n clientGroupID: string,\n clientID: string,\n wsID: string,\n shard: ShardID,\n baseCookie: string | null,\n downstream: Subscription<Downstream>,\n ) {\n lc.debug?.('new client handler');\n this.#clientGroupID = clientGroupID;\n this.clientID = clientID;\n this.wsID = wsID;\n this.#zeroClientsTable = `${upstreamSchema(shard)}.clients`;\n this.#zeroMutationsTable = `${upstreamSchema(shard)}.mutations`;\n this.#lc = lc;\n this.#downstream = downstream;\n this.#baseVersion = cookieToVersion(baseCookie);\n }\n\n version(): NullableCVRVersion {\n return this.#baseVersion;\n }\n\n async #push(msg: Downstream): Promise<void> {\n const {result} = this.#downstream.push(msg);\n await result;\n }\n\n fail(e: unknown) {\n this.#lc[getLogLevel(e)]?.(\n `view-syncer closing connection with error: ${String(e)}`,\n e,\n );\n this.#downstream.fail(wrapWithProtocolError(e));\n }\n\n close(reason: string) {\n this.#lc.debug?.(`view-syncer closing connection: ${reason}`);\n this.#downstream.cancel();\n }\n\n startPoke(tentativeVersion: CVRVersion): PokeHandler {\n const pokeID = versionToCookie(tentativeVersion);\n const lc = this.#lc.withContext('pokeID', pokeID);\n\n if (cmpVersions(this.#baseVersion, tentativeVersion) >= 0) {\n lc.info?.(`already caught up, not sending poke.`);\n return NOOP;\n }\n\n const baseCookie = versionToNullableCookie(this.#baseVersion);\n const cookie = versionToCookie(tentativeVersion);\n lc.debug?.(`starting poke from ${baseCookie} to ${cookie}`);\n\n const start = performance.now();\n\n const pokeStart: PokeStartBody = {pokeID, baseCookie};\n\n let pokeStarted = false;\n let body: PokePartBody | undefined;\n let partCount = 0;\n const ensureBody = async () => {\n if (!pokeStarted) {\n await this.#push(['pokeStart', pokeStart]);\n pokeStarted = true;\n }\n return (body ??= {pokeID});\n };\n const flushBody = async () => {\n if (body) {\n await this.#push(['pokePart', body]);\n body = undefined;\n partCount = 0;\n }\n };\n\n const addPatch = async (patchToVersion: PatchToVersion) => {\n const {patch, toVersion} = patchToVersion;\n if (cmpVersions(toVersion, this.#baseVersion) <= 0) {\n return;\n }\n const body = await ensureBody();\n\n const {type, op} = patch;\n switch (type) {\n case 'query': {\n const patches = patch.clientID\n ? ((body.desiredQueriesPatches ??= {})[patch.clientID] ??= [])\n : (body.gotQueriesPatch ??= []);\n if (op === 'put') {\n patches.push({op, hash: patch.id});\n } else {\n patches.push({op, hash: patch.id});\n }\n break;\n }\n case 'row':\n if (patch.id.table === this.#zeroClientsTable) {\n this.#updateLMIDs((body.lastMutationIDChanges ??= {}), patch);\n } else if (patch.id.table === this.#zeroMutationsTable) {\n const patches = (body.mutationsPatch ??= []);\n if (op === 'put') {\n const row = v.parse(\n ensureSafeJSON(patch.contents),\n mutationRowSchema,\n 'passthrough',\n );\n patches.push({\n op: 'put',\n mutation: {\n id: {\n clientID: row.clientID,\n id: row.mutationID,\n },\n result: row.result,\n },\n });\n } else {\n const {clientID, mutationID} = patch.id.rowKey;\n assert(\n typeof clientID === 'string',\n 'client id must be a string',\n );\n const id = Number(mutationID);\n assert(\n !Number.isNaN(id) && Number.isFinite(id) && id >= 0,\n 'mutation id must be a finite number',\n );\n patches.push({\n op: 'del',\n id: {\n clientID,\n id,\n },\n });\n }\n } else {\n (body.rowsPatch ??= []).push(makeRowPatch(patch));\n }\n break;\n default:\n unreachable(patch);\n }\n\n if (++partCount >= PART_COUNT_FLUSH_THRESHOLD) {\n await flushBody();\n }\n };\n\n return {\n addPatch: async (patchToVersion: PatchToVersion) => {\n try {\n await addPatch(patchToVersion);\n if (patchToVersion.patch.type === 'row') {\n this.#pokedRows.add(1);\n }\n } catch (e) {\n this.#downstream.fail(wrapWithProtocolError(e));\n }\n },\n\n cancel: async () => {\n if (pokeStarted) {\n await this.#push(['pokeEnd', {pokeID, cookie: '', cancel: true}]);\n }\n },\n\n end: async (finalVersion: CVRVersion) => {\n const cookie = versionToCookie(finalVersion);\n if (!pokeStarted) {\n if (cmpVersions(this.#baseVersion, finalVersion) === 0) {\n return; // Nothing changed and nothing was sent.\n }\n await this.#push(['pokeStart', pokeStart]);\n } else if (cmpVersions(this.#baseVersion, finalVersion) >= 0) {\n // Sanity check: If the poke was started, the finalVersion\n // must be > #baseVersion.\n throw new Error(\n `Patches were sent but finalVersion ${finalVersion} is ` +\n `not greater than baseVersion ${this.#baseVersion}`,\n );\n }\n await flushBody();\n await this.#push(['pokeEnd', {pokeID, cookie}]);\n this.#baseVersion = finalVersion;\n\n const elapsed = performance.now() - start;\n this.#pokeTransactions.add(1);\n this.#pokeTime.recordMs(elapsed);\n },\n };\n }\n\n async sendDeleteClients(\n lc: LogContext,\n deletedClientIDs: string[],\n deletedClientGroupIDs: string[],\n ) {\n const deleteClientsBody: Writable<DeleteClientsBody> = {};\n if (deletedClientIDs.length > 0) {\n deleteClientsBody.clientIDs = deletedClientIDs;\n }\n if (deletedClientGroupIDs.length > 0) {\n deleteClientsBody.clientGroupIDs = deletedClientGroupIDs;\n }\n lc.debug?.('sending deleteClients', deleteClientsBody);\n await this.#push(['deleteClients', deleteClientsBody]);\n }\n\n sendQueryTransformApplicationErrors(errors: ErroredQuery[]) {\n void this.#push(['transformError', errors]);\n }\n\n sendQueryTransformFailedError(error: TransformFailedBody) {\n this.fail(new ProtocolError(error));\n }\n\n sendInspectResponse(lc: LogContext, response: InspectDownBody): void {\n lc.debug?.('sending inspect response', response);\n this.#downstream.push(['inspect', response]);\n }\n\n #updateLMIDs(lmids: Record<string, number>, patch: RowPatch) {\n if (patch.op === 'put') {\n const row = ensureSafeJSON(patch.contents);\n const {clientGroupID, clientID, lastMutationID} = v.parse(\n row,\n lmidRowSchema,\n 'passthrough',\n );\n if (clientGroupID !== this.#clientGroupID) {\n this.#lc.error?.(\n `Received clients row for wrong clientGroupID. Ignoring.`,\n clientGroupID,\n );\n } else {\n lmids[clientID] = lastMutationID;\n }\n } else {\n // The 'constrain' and 'del' ops for clients can be ignored.\n patch.op satisfies 'constrain' | 'del';\n }\n }\n}\n\n// Note: The {APP_ID}_{SHARD_ID}.clients table is set up in replicator/initial-sync.ts.\nconst lmidRowSchema = v.object({\n clientGroupID: v.string(),\n clientID: v.string(),\n lastMutationID: v.number(), // Actually returned as a bigint, but converted by ensureSafeJSON().\n});\n\nconst mutationRowSchema = v.object({\n clientGroupID: v.string(),\n clientID: v.string(),\n mutationID: v.number(),\n result: mutationResultSchema,\n});\n\nfunction makeRowPatch(patch: RowPatch): RowPatchOp {\n const {\n op,\n id: {table: tableName, rowKey: id},\n } = patch;\n\n switch (op) {\n case 'put':\n return {\n op: 'put',\n tableName,\n value: v.parse(ensureSafeJSON(patch.contents), rowSchema),\n };\n\n case 'del':\n return {\n op,\n tableName,\n id: v.parse(id, primaryKeyValueRecordSchema),\n };\n\n default:\n unreachable(op);\n }\n}\n\n/**\n * Column values of type INT8 are returned as the `bigint` from the\n * Postgres library. These are converted to `number` if they are within\n * the safe Number range, allowing the protocol to support numbers larger\n * than 32-bits. Values outside of the safe number range (e.g. > 2^53) will\n * result in an Error.\n */\nexport function ensureSafeJSON(row: JSONObject): SafeJSONObject {\n const modified = Object.entries(row)\n .filter(([k, v]) => {\n if (typeof v === 'bigint') {\n if (v >= Number.MIN_SAFE_INTEGER && v <= Number.MAX_SAFE_INTEGER) {\n return true; // send this entry onto the next map() step.\n }\n throw new Error(`Value of \"${k}\" exceeds safe Number range (${v})`);\n } else if (typeof v === 'object') {\n assertJSONValue(v);\n }\n return false;\n })\n .map(([k, v]) => [k, Number(v)]);\n\n return modified.length\n ? {...row, ...Object.fromEntries(modified)}\n : (row as SafeJSONObject);\n}\n"],"mappings":";;;;;;;;;;;;;AA6EA,IAAM,OAAoB;CACxB,gBAAgB;CAChB,cAAc;CACd,WAAW;AACb;;AAGA,SAAgB,UACd,SACA,kBACa;CACb,MAAM,SAAS,QAAQ,KAAI,MAAK,EAAE,UAAU,gBAAgB,CAAC;CAK7D,OAAO;EACL,UAAU,OAAM,UAAS;GACvB,MAAM,QAAQ,WAAW,OAAO,KAAI,UAAS,MAAM,SAAS,KAAK,CAAC,CAAC;EACrE;EACA,QAAQ,YAAY;GAClB,MAAM,QAAQ,WAAW,OAAO,KAAI,UAAS,MAAM,OAAO,CAAC,CAAC;EAC9D;EACA,KAAK,OAAM,iBAAgB;GACzB,MAAM,QAAQ,WAAW,OAAO,KAAI,UAAS,MAAM,IAAI,YAAY,CAAC,CAAC;EACvE;CACF;AACF;AAIA,IAAM,6BAA6B;;;;AAKnC,IAAa,gBAAb,MAA2B;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAqB,4BACnB,QACA,aACA,6EACF;CAEA,oBAA6B,mBAC3B,QACA,qBACA,6BACF;CAEA,aAAsB,mBACpB,QACA,aACA,sBACF;CAEA,YACE,IACA,eACA,UACA,MACA,OACA,YACA,YACA;EACA,GAAG,QAAQ,oBAAoB;EAC/B,KAAKA,iBAAiB;EACtB,KAAK,WAAW;EAChB,KAAK,OAAO;EACZ,KAAKC,oBAAoB,GAAG,eAAe,KAAK,EAAE;EAClD,KAAKC,sBAAsB,GAAG,eAAe,KAAK,EAAE;EACpD,KAAKC,MAAM;EACX,KAAKC,cAAc;EACnB,KAAKI,eAAe,gBAAgB,UAAU;CAChD;CAEA,UAA8B;EAC5B,OAAO,KAAKA;CACd;CAEA,MAAMC,MAAM,KAAgC;EAC1C,MAAM,EAAC,WAAU,KAAKL,YAAY,KAAK,GAAG;EAC1C,MAAM;CACR;CAEA,KAAK,GAAY;EACf,KAAKD,IAAI,YAAY,CAAC,KACpB,8CAA8C,OAAO,CAAC,KACtD,CACF;EACA,KAAKC,YAAY,KAAK,sBAAsB,CAAC,CAAC;CAChD;CAEA,MAAM,QAAgB;EACpB,KAAKD,IAAI,QAAQ,mCAAmC,QAAQ;EAC5D,KAAKC,YAAY,OAAO;CAC1B;CAEA,UAAU,kBAA2C;EACnD,MAAM,SAAS,gBAAgB,gBAAgB;EAC/C,MAAM,KAAK,KAAKD,IAAI,YAAY,UAAU,MAAM;EAEhD,IAAI,YAAY,KAAKK,cAAc,gBAAgB,KAAK,GAAG;GACzD,GAAG,OAAO,sCAAsC;GAChD,OAAO;EACT;EAEA,MAAM,aAAa,wBAAwB,KAAKA,YAAY;EAC5D,MAAM,SAAS,gBAAgB,gBAAgB;EAC/C,GAAG,QAAQ,sBAAsB,WAAW,MAAM,QAAQ;EAE1D,MAAM,QAAQ,YAAY,IAAI;EAE9B,MAAM,YAA2B;GAAC;GAAQ;EAAU;EAEpD,IAAI,cAAc;EAClB,IAAI;EACJ,IAAI,YAAY;EAChB,MAAM,aAAa,YAAY;GAC7B,IAAI,CAAC,aAAa;IAChB,MAAM,KAAKC,MAAM,CAAC,aAAa,SAAS,CAAC;IACzC,cAAc;GAChB;GACA,OAAQ,SAAS,EAAC,OAAM;EAC1B;EACA,MAAM,YAAY,YAAY;GAC5B,IAAI,MAAM;IACR,MAAM,KAAKA,MAAM,CAAC,YAAY,IAAI,CAAC;IACnC,OAAO,KAAA;IACP,YAAY;GACd;EACF;EAEA,MAAM,WAAW,OAAO,mBAAmC;GACzD,MAAM,EAAC,OAAO,cAAa;GAC3B,IAAI,YAAY,WAAW,KAAKD,YAAY,KAAK,GAC/C;GAEF,MAAM,OAAO,MAAM,WAAW;GAE9B,MAAM,EAAC,MAAM,OAAM;GACnB,QAAQ,MAAR;IACE,KAAK,SAAS;KACZ,MAAM,UAAU,MAAM,WACjB,CAAC,KAAK,0BAA0B,CAAC,GAAG,MAAM,cAAc,CAAC,IACzD,KAAK,oBAAoB,CAAC;KAC/B,IAAI,OAAO,OACT,QAAQ,KAAK;MAAC;MAAI,MAAM,MAAM;KAAE,CAAC;UAEjC,QAAQ,KAAK;MAAC;MAAI,MAAM,MAAM;KAAE,CAAC;KAEnC;IACF;IACA,KAAK;KACH,IAAI,MAAM,GAAG,UAAU,KAAKP,mBAC1B,KAAKS,aAAc,KAAK,0BAA0B,CAAC,GAAI,KAAK;UACvD,IAAI,MAAM,GAAG,UAAU,KAAKR,qBAAqB;MACtD,MAAM,UAAW,KAAK,mBAAmB,CAAC;MAC1C,IAAI,OAAO,OAAO;OAChB,MAAM,MAAM,MACV,eAAe,MAAM,QAAQ,GAC7B,mBACA,aACF;OACA,QAAQ,KAAK;QACX,IAAI;QACJ,UAAU;SACR,IAAI;UACF,UAAU,IAAI;UACd,IAAI,IAAI;SACV;SACA,QAAQ,IAAI;QACd;OACF,CAAC;MACH,OAAO;OACL,MAAM,EAAC,UAAU,eAAc,MAAM,GAAG;OACxC,OACE,OAAO,aAAa,UACpB,4BACF;OACA,MAAM,KAAK,OAAO,UAAU;OAC5B,OACE,CAAC,OAAO,MAAM,EAAE,KAAK,OAAO,SAAS,EAAE,KAAK,MAAM,GAClD,qCACF;OACA,QAAQ,KAAK;QACX,IAAI;QACJ,IAAI;SACF;SACA;QACF;OACF,CAAC;MACH;KACF,OACE,CAAC,KAAK,cAAc,CAAC,GAAG,KAAK,aAAa,KAAK,CAAC;KAElD;IACF,SACE,YAAY,KAAK;GACrB;GAEA,IAAI,EAAE,aAAa,4BACjB,MAAM,UAAU;EAEpB;EAEA,OAAO;GACL,UAAU,OAAO,mBAAmC;IAClD,IAAI;KACF,MAAM,SAAS,cAAc;KAC7B,IAAI,eAAe,MAAM,SAAS,OAChC,KAAKK,WAAW,IAAI,CAAC;IAEzB,SAAS,GAAG;KACV,KAAKH,YAAY,KAAK,sBAAsB,CAAC,CAAC;IAChD;GACF;GAEA,QAAQ,YAAY;IAClB,IAAI,aACF,MAAM,KAAKK,MAAM,CAAC,WAAW;KAAC;KAAQ,QAAQ;KAAI,QAAQ;IAAI,CAAC,CAAC;GAEpE;GAEA,KAAK,OAAO,iBAA6B;IACvC,MAAM,SAAS,gBAAgB,YAAY;IAC3C,IAAI,CAAC,aAAa;KAChB,IAAI,YAAY,KAAKD,cAAc,YAAY,MAAM,GACnD;KAEF,MAAM,KAAKC,MAAM,CAAC,aAAa,SAAS,CAAC;IAC3C,OAAO,IAAI,YAAY,KAAKD,cAAc,YAAY,KAAK,GAGzD,MAAM,IAAI,MACR,sCAAsC,aAAa,mCACjB,KAAKA,cACzC;IAEF,MAAM,UAAU;IAChB,MAAM,KAAKC,MAAM,CAAC,WAAW;KAAC;KAAQ;IAAM,CAAC,CAAC;IAC9C,KAAKD,eAAe;IAEpB,MAAM,UAAU,YAAY,IAAI,IAAI;IACpC,KAAKF,kBAAkB,IAAI,CAAC;IAC5B,KAAKD,UAAU,SAAS,OAAO;GACjC;EACF;CACF;CAEA,MAAM,kBACJ,IACA,kBACA,uBACA;EACA,MAAM,oBAAiD,CAAC;EACxD,IAAI,iBAAiB,SAAS,GAC5B,kBAAkB,YAAY;EAEhC,IAAI,sBAAsB,SAAS,GACjC,kBAAkB,iBAAiB;EAErC,GAAG,QAAQ,yBAAyB,iBAAiB;EACrD,MAAM,KAAKI,MAAM,CAAC,iBAAiB,iBAAiB,CAAC;CACvD;CAEA,oCAAoC,QAAwB;EAC1D,KAAUA,MAAM,CAAC,kBAAkB,MAAM,CAAC;CAC5C;CAEA,8BAA8B,OAA4B;EACxD,KAAK,KAAK,IAAI,cAAc,KAAK,CAAC;CACpC;CAEA,oBAAoB,IAAgB,UAAiC;EACnE,GAAG,QAAQ,4BAA4B,QAAQ;EAC/C,KAAKL,YAAY,KAAK,CAAC,WAAW,QAAQ,CAAC;CAC7C;CAEA,aAAa,OAA+B,OAAiB;EAC3D,IAAI,MAAM,OAAO,OAAO;GAEtB,MAAM,EAAC,eAAe,UAAU,mBAAkB,MADtC,eAAe,MAAM,QAE/B,GACA,eACA,aACF;GACA,IAAI,kBAAkB,KAAKJ,gBACzB,KAAKG,IAAI,QACP,2DACA,aACF;QAEA,MAAM,YAAY;EAEtB,OAEE,MAAM;CAEV;AACF;AAGA,IAAM,gBAAgB,eAAE,OAAO;CAC7B,eAAe,eAAE,OAAO;CACxB,UAAU,eAAE,OAAO;CACnB,gBAAgB,eAAE,OAAO;AAC3B,CAAC;AAED,IAAM,oBAAoB,eAAE,OAAO;CACjC,eAAe,eAAE,OAAO;CACxB,UAAU,eAAE,OAAO;CACnB,YAAY,eAAE,OAAO;CACrB,QAAQ;AACV,CAAC;AAED,SAAS,aAAa,OAA6B;CACjD,MAAM,EACJ,IACA,IAAI,EAAC,OAAO,WAAW,QAAQ,SAC7B;CAEJ,QAAQ,IAAR;EACE,KAAK,OACH,OAAO;GACL,IAAI;GACJ;GACA,OAAO,MAAQ,eAAe,MAAM,QAAQ,GAAG,SAAS;EAC1D;EAEF,KAAK,OACH,OAAO;GACL;GACA;GACA,IAAI,MAAQ,IAAI,2BAA2B;EAC7C;EAEF,SACE,YAAY,EAAE;CAClB;AACF;;;;;;;;AASA,SAAgB,eAAe,KAAiC;CAC9D,MAAM,WAAW,OAAO,QAAQ,GAAG,EAChC,QAAQ,CAAC,GAAG,OAAO;EAClB,IAAI,OAAO,MAAM,UAAU;GACzB,IAAI,KAAK,OAAO,oBAAoB,KAAK,OAAO,kBAC9C,OAAO;GAET,MAAM,IAAI,MAAM,aAAa,EAAE,+BAA+B,EAAE,EAAE;EACpE,OAAO,IAAI,OAAO,MAAM,UACtB,gBAAgB,CAAC;EAEnB,OAAO;CACT,CAAC,EACA,KAAK,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAEjC,OAAO,SAAS,SACZ;EAAC,GAAG;EAAK,GAAG,OAAO,YAAY,QAAQ;CAAC,IACvC;AACP"}
1
+ {"version":3,"file":"client-handler.js","names":["#clientGroupID","#zeroClientsTable","#zeroMutationsTable","#lc","#downstream","#pokeTime","#pokeTransactions","#pokedRows","#baseVersion","#push","#updateLMIDs"],"sources":["../../../../../../zero-cache/src/services/view-syncer/client-handler.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport type {JSONObject} from '../../../../shared/src/bigint-json.ts';\nimport {\n assertJSONValue,\n type JSONObject as SafeJSONObject,\n} from '../../../../shared/src/json.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport type {Writable} from '../../../../shared/src/writable.ts';\nimport type {ErroredQuery} from '../../../../zero-protocol/src/custom-queries.ts';\nimport {rowSchema} from '../../../../zero-protocol/src/data.ts';\nimport type {DeleteClientsBody} from '../../../../zero-protocol/src/delete-clients.ts';\nimport type {Downstream} from '../../../../zero-protocol/src/down.ts';\nimport {\n ProtocolError,\n type TransformFailedBody,\n} from '../../../../zero-protocol/src/error.ts';\nimport type {InspectDownBody} from '../../../../zero-protocol/src/inspect-down.ts';\nimport {mutationResultSchema} from '../../../../zero-protocol/src/mutation.ts';\nimport type {\n PokePartBody,\n PokeStartBody,\n} from '../../../../zero-protocol/src/poke.ts';\nimport {primaryKeyValueRecordSchema} from '../../../../zero-protocol/src/primary-key.ts';\nimport type {RowPatchOp} from '../../../../zero-protocol/src/row-patch.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport {\n getLogLevel,\n wrapWithProtocolError,\n} from '../../types/error-with-level.ts';\nimport {upstreamSchema, type ShardID} from '../../types/shards.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {\n cmpVersions,\n cookieToVersion,\n versionToCookie,\n versionToNullableCookie,\n type CVRVersion,\n type DelQueryPatch,\n type NullableCVRVersion,\n type PutQueryPatch,\n type RowID,\n} from './schema/types.ts';\n\nexport type PutRowPatch = {\n type: 'row';\n op: 'put';\n id: RowID;\n contents: JSONObject;\n};\n\nexport type DeleteRowPatch = {\n type: 'row';\n op: 'del';\n id: RowID;\n};\n\nexport type RowPatch = PutRowPatch | DeleteRowPatch;\nexport type ConfigPatch = DelQueryPatch | PutQueryPatch;\n\nexport type Patch = ConfigPatch | RowPatch;\n\nexport type PatchToVersion = {\n patch: Patch;\n toVersion: CVRVersion;\n};\n\nexport interface PokeHandler {\n addPatch(patch: PatchToVersion): Promise<void>;\n cancel(): Promise<void>;\n end(finalVersion: CVRVersion): Promise<void>;\n}\n\nconst NOOP: PokeHandler = {\n addPatch: () => promiseVoid,\n cancel: () => promiseVoid,\n end: () => promiseVoid,\n};\n\n/** Wraps PokeHandlers for multiple clients in a single PokeHandler. */\nexport function startPoke(\n clients: ClientHandler[],\n tentativeVersion: CVRVersion,\n): PokeHandler {\n const pokers = clients.map(c => c.startPoke(tentativeVersion));\n\n // Promise.allSettled() ensures that a failed (e.g. disconnected) client\n // does not prevent other clients from receiving the pokes. However, the\n // rate (per client group) will be limited by the slowest connection.\n return {\n addPatch: async patch => {\n await Promise.allSettled(pokers.map(poker => poker.addPatch(patch)));\n },\n cancel: async () => {\n await Promise.allSettled(pokers.map(poker => poker.cancel()));\n },\n end: async finalVersion => {\n await Promise.allSettled(pokers.map(poker => poker.end(finalVersion)));\n },\n };\n}\n\n// Semi-arbitrary threshold at which poke body parts are flushed.\n// When row size is being computed, that should be used as a threshold instead.\nconst PART_COUNT_FLUSH_THRESHOLD = 100;\n\n/**\n * Handles a single `ViewSyncer` connection.\n */\nexport class ClientHandler {\n readonly #clientGroupID: string;\n readonly clientID: string;\n readonly wsID: string;\n readonly #zeroClientsTable: string;\n readonly #zeroMutationsTable: string;\n readonly #lc: LogContext;\n readonly #downstream: Subscription<Downstream>;\n #baseVersion: NullableCVRVersion;\n\n readonly #pokeTime = getOrCreateLatencyHistogram(\n 'sync',\n 'poke.time',\n 'Time elapsed for each poke transaction. Canceled / noop pokes are excluded.',\n );\n\n readonly #pokeTransactions = getOrCreateCounter(\n 'sync',\n 'poke.transactions',\n 'Count of poke transactions.',\n );\n\n readonly #pokedRows = getOrCreateCounter(\n 'sync',\n 'poke.rows',\n 'Count of poked rows.',\n );\n\n constructor(\n lc: LogContext,\n clientGroupID: string,\n clientID: string,\n wsID: string,\n shard: ShardID,\n baseCookie: string | null,\n downstream: Subscription<Downstream>,\n ) {\n lc.debug?.('new client handler');\n this.#clientGroupID = clientGroupID;\n this.clientID = clientID;\n this.wsID = wsID;\n this.#zeroClientsTable = `${upstreamSchema(shard)}.clients`;\n this.#zeroMutationsTable = `${upstreamSchema(shard)}.mutations`;\n this.#lc = lc;\n this.#downstream = downstream;\n this.#baseVersion = cookieToVersion(baseCookie);\n }\n\n version(): NullableCVRVersion {\n return this.#baseVersion;\n }\n\n async #push(msg: Downstream): Promise<void> {\n const {result} = this.#downstream.push(msg);\n await result;\n }\n\n fail(e: unknown) {\n this.#lc[getLogLevel(e)]?.(\n `view-syncer closing connection with error: ${String(e)}`,\n e,\n );\n this.#downstream.fail(wrapWithProtocolError(e));\n }\n\n close(reason: string) {\n this.#lc.debug?.(`view-syncer closing connection: ${reason}`);\n this.#downstream.cancel();\n }\n\n startPoke(tentativeVersion: CVRVersion): PokeHandler {\n const pokeID = versionToCookie(tentativeVersion);\n const lc = this.#lc.withContext('pokeID', pokeID);\n\n if (cmpVersions(this.#baseVersion, tentativeVersion) >= 0) {\n lc.info?.(`already caught up, not sending poke.`);\n return NOOP;\n }\n\n const baseCookie = versionToNullableCookie(this.#baseVersion);\n const cookie = versionToCookie(tentativeVersion);\n lc.debug?.(`starting poke from ${baseCookie} to ${cookie}`);\n\n const start = performance.now();\n\n const pokeStart: PokeStartBody = {pokeID, baseCookie};\n\n let pokeStarted = false;\n let body: PokePartBody | undefined;\n let partCount = 0;\n const ensureBody = async () => {\n if (!pokeStarted) {\n await this.#push(['pokeStart', pokeStart]);\n pokeStarted = true;\n }\n return (body ??= {pokeID});\n };\n const flushBody = async () => {\n if (body) {\n await this.#push(['pokePart', body]);\n body = undefined;\n partCount = 0;\n }\n };\n\n const addPatch = async (patchToVersion: PatchToVersion) => {\n const {patch, toVersion} = patchToVersion;\n if (cmpVersions(toVersion, this.#baseVersion) <= 0) {\n return;\n }\n const body = await ensureBody();\n\n const {type, op} = patch;\n switch (type) {\n case 'query': {\n const patches = patch.clientID\n ? ((body.desiredQueriesPatches ??= {})[patch.clientID] ??= [])\n : (body.gotQueriesPatch ??= []);\n if (op === 'put') {\n patches.push({op, hash: patch.id});\n } else {\n patches.push({op, hash: patch.id});\n }\n break;\n }\n case 'row':\n if (patch.id.table === this.#zeroClientsTable) {\n this.#updateLMIDs((body.lastMutationIDChanges ??= {}), patch);\n } else if (patch.id.table === this.#zeroMutationsTable) {\n const patches = (body.mutationsPatch ??= []);\n if (op === 'put') {\n const row = v.parse(\n ensureSafeJSON(patch.contents),\n mutationRowSchema,\n 'passthrough',\n );\n patches.push({\n op: 'put',\n mutation: {\n id: {\n clientID: row.clientID,\n id: row.mutationID,\n },\n result: row.result,\n },\n });\n } else {\n const {clientID, mutationID} = patch.id.rowKey;\n assert(\n typeof clientID === 'string',\n 'client id must be a string',\n );\n const id = Number(mutationID);\n assert(\n !Number.isNaN(id) && Number.isFinite(id) && id >= 0,\n 'mutation id must be a finite number',\n );\n patches.push({\n op: 'del',\n id: {\n clientID,\n id,\n },\n });\n }\n } else {\n (body.rowsPatch ??= []).push(makeRowPatch(patch));\n }\n break;\n default:\n unreachable(patch);\n }\n\n if (++partCount >= PART_COUNT_FLUSH_THRESHOLD) {\n await flushBody();\n }\n };\n\n return {\n addPatch: async (patchToVersion: PatchToVersion) => {\n try {\n await addPatch(patchToVersion);\n if (patchToVersion.patch.type === 'row') {\n this.#pokedRows.add(1);\n }\n } catch (e) {\n this.#downstream.fail(wrapWithProtocolError(e));\n }\n },\n\n cancel: async () => {\n if (pokeStarted) {\n await this.#push(['pokeEnd', {pokeID, cookie: '', cancel: true}]);\n }\n },\n\n end: async (finalVersion: CVRVersion) => {\n const cookie = versionToCookie(finalVersion);\n if (!pokeStarted) {\n if (cmpVersions(this.#baseVersion, finalVersion) === 0) {\n return; // Nothing changed and nothing was sent.\n }\n await this.#push(['pokeStart', pokeStart]);\n } else if (cmpVersions(this.#baseVersion, finalVersion) >= 0) {\n // Sanity check: If the poke was started, the finalVersion\n // must be > #baseVersion.\n throw new Error(\n `Patches were sent but finalVersion ${finalVersion} is ` +\n `not greater than baseVersion ${this.#baseVersion}`,\n );\n }\n await flushBody();\n await this.#push(['pokeEnd', {pokeID, cookie}]);\n this.#baseVersion = finalVersion;\n\n const elapsed = performance.now() - start;\n this.#pokeTransactions.add(1);\n this.#pokeTime.recordMs(elapsed);\n },\n };\n }\n\n async sendDeleteClients(\n lc: LogContext,\n deletedClientIDs: string[],\n deletedClientGroupIDs: string[],\n ) {\n const deleteClientsBody: Writable<DeleteClientsBody> = {};\n if (deletedClientIDs.length > 0) {\n deleteClientsBody.clientIDs = deletedClientIDs;\n }\n if (deletedClientGroupIDs.length > 0) {\n deleteClientsBody.clientGroupIDs = deletedClientGroupIDs;\n }\n lc.debug?.('sending deleteClients', deleteClientsBody);\n await this.#push(['deleteClients', deleteClientsBody]);\n }\n\n sendQueryTransformApplicationErrors(errors: ErroredQuery[]) {\n void this.#push(['transformError', errors]);\n }\n\n sendQueryTransformFailedError(error: TransformFailedBody) {\n this.fail(new ProtocolError(error));\n }\n\n sendInspectResponse(lc: LogContext, response: InspectDownBody): void {\n lc.debug?.('sending inspect response', response);\n this.#downstream.push(['inspect', response]);\n }\n\n #updateLMIDs(lmids: Record<string, number>, patch: RowPatch) {\n if (patch.op === 'put') {\n const row = ensureSafeJSON(patch.contents);\n const {clientGroupID, clientID, lastMutationID} = v.parse(\n row,\n lmidRowSchema,\n 'passthrough',\n );\n if (clientGroupID !== this.#clientGroupID) {\n this.#lc.error?.(\n `Received clients row for wrong clientGroupID. Ignoring.`,\n clientGroupID,\n );\n } else {\n lmids[clientID] = lastMutationID;\n }\n } else {\n // The 'constrain' and 'del' ops for clients can be ignored.\n patch.op satisfies 'constrain' | 'del';\n }\n }\n}\n\n// Note: The {APP_ID}_{SHARD_ID}.clients table is set up in replicator/initial-sync.ts.\nconst lmidRowSchema = v.object({\n clientGroupID: v.string(),\n clientID: v.string(),\n lastMutationID: v.number(), // Actually returned as a bigint, but converted by ensureSafeJSON().\n});\n\nconst mutationRowSchema = v.object({\n clientGroupID: v.string(),\n clientID: v.string(),\n mutationID: v.number(),\n result: mutationResultSchema,\n});\n\nfunction makeRowPatch(patch: RowPatch): RowPatchOp {\n const {\n op,\n id: {table: tableName, rowKey: id},\n } = patch;\n\n switch (op) {\n case 'put':\n return {\n op: 'put',\n tableName,\n value: v.parse(ensureSafeJSON(patch.contents), rowSchema),\n };\n\n case 'del':\n return {\n op,\n tableName,\n id: v.parse(id, primaryKeyValueRecordSchema),\n };\n\n default:\n unreachable(op);\n }\n}\n\n/**\n * Column values of type INT8 are returned as the `bigint` from the\n * Postgres library. These are converted to `number` if they are within\n * the safe Number range, allowing the protocol to support numbers larger\n * than 32-bits. Values outside of the safe number range (e.g. > 2^53) will\n * result in an Error.\n */\nexport function ensureSafeJSON(row: JSONObject): SafeJSONObject {\n const modified = Object.entries(row)\n .filter(([k, v]) => {\n if (typeof v === 'bigint') {\n if (v >= Number.MIN_SAFE_INTEGER && v <= Number.MAX_SAFE_INTEGER) {\n return true; // send this entry onto the next map() step.\n }\n throw new Error(`Value of \"${k}\" exceeds safe Number range (${v})`);\n } else if (typeof v === 'object') {\n assertJSONValue(v);\n }\n return false;\n })\n .map(([k, v]) => [k, Number(v)]);\n\n return modified.length\n ? {...row, ...Object.fromEntries(modified)}\n : (row as SafeJSONObject);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-schema.js","names":[],"sources":["../../../../../../zero-cache/src/services/view-syncer/client-schema.ts"],"sourcesContent":["import {toSorted} from '../../../../shared/src/iterables.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {\n difference,\n equals,\n intersection,\n} from '../../../../shared/src/set-utils.ts';\nimport type {ClientSchema} from '../../../../zero-protocol/src/client-schema.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport {ProtocolError} from '../../../../zero-protocol/src/error.ts';\nimport type {LiteAndZqlSpec, LiteTableSpec} from '../../db/specs.ts';\nimport {appSchema, upstreamSchema, type ShardID} from '../../types/shards.ts';\nimport {ZERO_VERSION_COLUMN_NAME} from '../replicator/schema/constants.ts';\n\nexport function checkClientSchema(\n shardID: ShardID,\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n fullTables: Map<string, LiteTableSpec>,\n) {\n if (fullTables.size === 0) {\n throw new ProtocolError({\n kind: 'Internal',\n message:\n `No tables have been synced from upstream. ` +\n `Please check that the ZERO_UPSTREAM_DB has been properly set.`,\n origin: ErrorOrigin.ZeroCache,\n });\n }\n const errors: string[] = [];\n const clientTables = new Set(Object.keys(clientSchema.tables));\n const missingTables = difference(clientTables, tableSpecs);\n for (const missing of toSorted(missingTables)) {\n if (fullTables.has(missing)) {\n errors.push(\n `The \"${missing}\" table is missing a primary key or non-null ` +\n `unique index and thus cannot be synced to the client`,\n );\n } else {\n const app = appSchema(shardID) + '.';\n const shard = upstreamSchema(shardID) + '.';\n const syncedTables = [...tableSpecs.keys()]\n .filter(t => !t.startsWith(app) && !t.startsWith(shard))\n .sort()\n .map(t => `\"${t}\"`)\n .join(',');\n const schemaTip =\n missing.includes('.') && !syncedTables.includes('.')\n ? ` Note that zero does not sync tables from non-public schemas ` +\n `by default. Make sure you have defined a custom ` +\n `ZERO_APP_PUBLICATION to sync tables from non-public schemas.`\n : '';\n errors.push(\n `The \"${missing}\" table does not exist or is not ` +\n `one of the replicated tables: ${syncedTables}.${schemaTip}`,\n );\n }\n }\n const tables = intersection(tableSpecs, clientTables);\n for (const table of toSorted(tables)) {\n const clientSpec = clientSchema.tables[table];\n const serverSpec = must(tableSpecs.get(table)); // guaranteed by intersection\n const fullSpec = must(fullTables.get(table));\n\n const clientColumns = new Set(Object.keys(clientSpec.columns));\n const syncedColumns = new Set(Object.keys(serverSpec.zqlSpec));\n const missingColumns = difference(clientColumns, syncedColumns);\n for (const missing of toSorted(missingColumns)) {\n if (fullSpec.columns[missing]) {\n errors.push(\n `The \"${table}\".\"${missing}\" column cannot be synced because it ` +\n `is of an unsupported data type \"${fullSpec.columns[missing].dataType}\"`,\n );\n } else {\n const columns = [...syncedColumns]\n .filter(c => c !== ZERO_VERSION_COLUMN_NAME)\n .sort()\n .map(c => `\"${c}\"`)\n .join(',');\n\n errors.push(\n `The \"${table}\".\"${missing}\" column does not exist ` +\n `or is not one of the replicated columns: ${columns}.`,\n );\n }\n }\n const columns = intersection(clientColumns, syncedColumns);\n for (const column of columns) {\n const clientType = clientSpec.columns[column].type;\n const serverType = serverSpec.zqlSpec[column].type;\n if (clientSpec.columns[column].type !== serverSpec.zqlSpec[column].type) {\n errors.push(\n `The \"${table}\".\"${column}\" column's upstream type \"${serverType}\" ` +\n `does not match the client type \"${clientType}\"`,\n );\n }\n }\n if (!clientSpec.primaryKey) {\n errors.push(\n `The \"${table}\" table's client schema does not specify a primary key.`,\n );\n } else {\n const clientPrimaryKey = new Set(clientSpec.primaryKey);\n if (\n !serverSpec.tableSpec.allPotentialPrimaryKeys.some(key =>\n equals(clientPrimaryKey, new Set(key)),\n )\n ) {\n errors.push(\n `The \"${table}\" table's primaryKey <${clientSpec.primaryKey.join(',')}> ` +\n `is not associated with a non-null unique index.`,\n );\n }\n }\n }\n if (errors.length) {\n throw new ProtocolError({\n kind: 'SchemaVersionNotSupported',\n message: errors.join('\\n'),\n origin: ErrorOrigin.ZeroCache,\n });\n }\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,kBACd,SACA,cACA,YACA,YACA;CACA,IAAI,WAAW,SAAS,GACtB,MAAM,IAAI,cAAc;EACtB,MAAM;EACN,SACE;EAEF,QAAQ;CACV,CAAC;CAEH,MAAM,SAAmB,CAAC;CAC1B,MAAM,eAAe,IAAI,IAAI,OAAO,KAAK,aAAa,MAAM,CAAC;CAC7D,MAAM,gBAAgB,WAAW,cAAc,UAAU;CACzD,KAAK,MAAM,WAAW,SAAS,aAAa,GAC1C,IAAI,WAAW,IAAI,OAAO,GACxB,OAAO,KACL,QAAQ,QAAQ,kGAElB;MACK;EACL,MAAM,MAAM,UAAU,OAAO,IAAI;EACjC,MAAM,QAAQ,eAAe,OAAO,IAAI;EACxC,MAAM,eAAe,CAAC,GAAG,WAAW,KAAK,CAAC,EACvC,QAAO,MAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC,EAAE,WAAW,KAAK,CAAC,EACtD,KAAK,EACL,KAAI,MAAK,IAAI,EAAE,EAAE,EACjB,KAAK,GAAG;EACX,MAAM,YACJ,QAAQ,SAAS,GAAG,KAAK,CAAC,aAAa,SAAS,GAAG,IAC/C,8KAGA;EACN,OAAO,KACL,QAAQ,QAAQ,iEACmB,aAAa,GAAG,WACrD;CACF;CAEF,MAAM,SAAS,aAAa,YAAY,YAAY;CACpD,KAAK,MAAM,SAAS,SAAS,MAAM,GAAG;EACpC,MAAM,aAAa,aAAa,OAAO;EACvC,MAAM,aAAa,KAAK,WAAW,IAAI,KAAK,CAAC;EAC7C,MAAM,WAAW,KAAK,WAAW,IAAI,KAAK,CAAC;EAE3C,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,WAAW,OAAO,CAAC;EAC7D,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,WAAW,OAAO,CAAC;EAC7D,MAAM,iBAAiB,WAAW,eAAe,aAAa;EAC9D,KAAK,MAAM,WAAW,SAAS,cAAc,GAC3C,IAAI,SAAS,QAAQ,UACnB,OAAO,KACL,QAAQ,MAAM,KAAK,QAAQ,uEACU,SAAS,QAAQ,SAAS,SAAS,EAC1E;OACK;GACL,MAAM,UAAU,CAAC,GAAG,aAAa,EAC9B,QAAO,MAAK,MAAM,wBAAwB,EAC1C,KAAK,EACL,KAAI,MAAK,IAAI,EAAE,EAAE,EACjB,KAAK,GAAG;GAEX,OAAO,KACL,QAAQ,MAAM,KAAK,QAAQ,mEACmB,QAAQ,EACxD;EACF;EAEF,MAAM,UAAU,aAAa,eAAe,aAAa;EACzD,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,aAAa,WAAW,QAAQ,QAAQ;GAC9C,MAAM,aAAa,WAAW,QAAQ,QAAQ;GAC9C,IAAI,WAAW,QAAQ,QAAQ,SAAS,WAAW,QAAQ,QAAQ,MACjE,OAAO,KACL,QAAQ,MAAM,KAAK,OAAO,4BAA4B,WAAW,oCAC5B,WAAW,EAClD;EAEJ;EACA,IAAI,CAAC,WAAW,YACd,OAAO,KACL,QAAQ,MAAM,wDAChB;OACK;GACL,MAAM,mBAAmB,IAAI,IAAI,WAAW,UAAU;GACtD,IACE,CAAC,WAAW,UAAU,wBAAwB,MAAK,QACjD,OAAO,kBAAkB,IAAI,IAAI,GAAG,CAAC,CACvC,GAEA,OAAO,KACL,QAAQ,MAAM,wBAAwB,WAAW,WAAW,KAAK,GAAG,EAAE,kDAExE;EAEJ;CACF;CACA,IAAI,OAAO,QACT,MAAM,IAAI,cAAc;EACtB,MAAM;EACN,SAAS,OAAO,KAAK,IAAI;EACzB,QAAQ;CACV,CAAC;AAEL"}
1
+ {"version":3,"file":"client-schema.js","names":[],"sources":["../../../../../../zero-cache/src/services/view-syncer/client-schema.ts"],"sourcesContent":["import {toSorted} from '../../../../shared/src/iterables.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {\n difference,\n equals,\n intersection,\n} from '../../../../shared/src/set-utils.ts';\nimport type {ClientSchema} from '../../../../zero-protocol/src/client-schema.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport {ProtocolError} from '../../../../zero-protocol/src/error.ts';\nimport type {LiteAndZqlSpec, LiteTableSpec} from '../../db/specs.ts';\nimport {appSchema, upstreamSchema, type ShardID} from '../../types/shards.ts';\nimport {ZERO_VERSION_COLUMN_NAME} from '../replicator/schema/constants.ts';\n\nexport function checkClientSchema(\n shardID: ShardID,\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n fullTables: Map<string, LiteTableSpec>,\n) {\n if (fullTables.size === 0) {\n throw new ProtocolError({\n kind: 'Internal',\n message:\n `No tables have been synced from upstream. ` +\n `Please check that the ZERO_UPSTREAM_DB has been properly set.`,\n origin: ErrorOrigin.ZeroCache,\n });\n }\n const errors: string[] = [];\n const clientTables = new Set(Object.keys(clientSchema.tables));\n const missingTables = difference(clientTables, tableSpecs);\n for (const missing of toSorted(missingTables)) {\n if (fullTables.has(missing)) {\n errors.push(\n `The \"${missing}\" table is missing a primary key or non-null ` +\n `unique index and thus cannot be synced to the client`,\n );\n } else {\n const app = appSchema(shardID) + '.';\n const shard = upstreamSchema(shardID) + '.';\n const syncedTables = [...tableSpecs.keys()]\n .filter(t => !t.startsWith(app) && !t.startsWith(shard))\n .sort()\n .map(t => `\"${t}\"`)\n .join(',');\n const schemaTip =\n missing.includes('.') && !syncedTables.includes('.')\n ? ` Note that zero does not sync tables from non-public schemas ` +\n `by default. Make sure you have defined a custom ` +\n `ZERO_APP_PUBLICATION to sync tables from non-public schemas.`\n : '';\n errors.push(\n `The \"${missing}\" table does not exist or is not ` +\n `one of the replicated tables: ${syncedTables}.${schemaTip}`,\n );\n }\n }\n const tables = intersection(tableSpecs, clientTables);\n for (const table of toSorted(tables)) {\n const clientSpec = clientSchema.tables[table];\n const serverSpec = must(tableSpecs.get(table)); // guaranteed by intersection\n const fullSpec = must(fullTables.get(table));\n\n const clientColumns = new Set(Object.keys(clientSpec.columns));\n const syncedColumns = new Set(Object.keys(serverSpec.zqlSpec));\n const missingColumns = difference(clientColumns, syncedColumns);\n for (const missing of toSorted(missingColumns)) {\n if (fullSpec.columns[missing]) {\n errors.push(\n `The \"${table}\".\"${missing}\" column cannot be synced because it ` +\n `is of an unsupported data type \"${fullSpec.columns[missing].dataType}\"`,\n );\n } else {\n const columns = [...syncedColumns]\n .filter(c => c !== ZERO_VERSION_COLUMN_NAME)\n .sort()\n .map(c => `\"${c}\"`)\n .join(',');\n\n errors.push(\n `The \"${table}\".\"${missing}\" column does not exist ` +\n `or is not one of the replicated columns: ${columns}.`,\n );\n }\n }\n const columns = intersection(clientColumns, syncedColumns);\n for (const column of columns) {\n const clientType = clientSpec.columns[column].type;\n const serverType = serverSpec.zqlSpec[column].type;\n if (clientSpec.columns[column].type !== serverSpec.zqlSpec[column].type) {\n errors.push(\n `The \"${table}\".\"${column}\" column's upstream type \"${serverType}\" ` +\n `does not match the client type \"${clientType}\"`,\n );\n }\n }\n if (!clientSpec.primaryKey) {\n errors.push(\n `The \"${table}\" table's client schema does not specify a primary key.`,\n );\n } else {\n const clientPrimaryKey = new Set(clientSpec.primaryKey);\n if (\n !serverSpec.tableSpec.allPotentialPrimaryKeys.some(key =>\n equals(clientPrimaryKey, new Set(key)),\n )\n ) {\n errors.push(\n `The \"${table}\" table's primaryKey <${clientSpec.primaryKey.join(',')}> ` +\n `is not associated with a non-null unique index.`,\n );\n }\n }\n }\n if (errors.length) {\n throw new ProtocolError({\n kind: 'SchemaVersionNotSupported',\n message: errors.join('\\n'),\n origin: ErrorOrigin.ZeroCache,\n });\n }\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,kBACd,SACA,cACA,YACA,YACA;AACA,KAAI,WAAW,SAAS,EACtB,OAAM,IAAI,cAAc;EACtB,MAAM;EACN,SACE;EAEF,QAAQ;EACT,CAAC;CAEJ,MAAM,SAAmB,EAAE;CAC3B,MAAM,eAAe,IAAI,IAAI,OAAO,KAAK,aAAa,OAAO,CAAC;CAC9D,MAAM,gBAAgB,WAAW,cAAc,WAAW;AAC1D,MAAK,MAAM,WAAW,SAAS,cAAc,CAC3C,KAAI,WAAW,IAAI,QAAQ,CACzB,QAAO,KACL,QAAQ,QAAQ,mGAEjB;MACI;EACL,MAAM,MAAM,UAAU,QAAQ,GAAG;EACjC,MAAM,QAAQ,eAAe,QAAQ,GAAG;EACxC,MAAM,eAAe,CAAC,GAAG,WAAW,MAAM,CAAC,CACxC,QAAO,MAAK,CAAC,EAAE,WAAW,IAAI,IAAI,CAAC,EAAE,WAAW,MAAM,CAAC,CACvD,MAAM,CACN,KAAI,MAAK,IAAI,EAAE,GAAG,CAClB,KAAK,IAAI;EACZ,MAAM,YACJ,QAAQ,SAAS,IAAI,IAAI,CAAC,aAAa,SAAS,IAAI,GAChD,8KAGA;AACN,SAAO,KACL,QAAQ,QAAQ,iEACmB,aAAa,GAAG,YACpD;;CAGL,MAAM,SAAS,aAAa,YAAY,aAAa;AACrD,MAAK,MAAM,SAAS,SAAS,OAAO,EAAE;EACpC,MAAM,aAAa,aAAa,OAAO;EACvC,MAAM,aAAa,KAAK,WAAW,IAAI,MAAM,CAAC;EAC9C,MAAM,WAAW,KAAK,WAAW,IAAI,MAAM,CAAC;EAE5C,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,WAAW,QAAQ,CAAC;EAC9D,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,WAAW,QAAQ,CAAC;EAC9D,MAAM,iBAAiB,WAAW,eAAe,cAAc;AAC/D,OAAK,MAAM,WAAW,SAAS,eAAe,CAC5C,KAAI,SAAS,QAAQ,SACnB,QAAO,KACL,QAAQ,MAAM,KAAK,QAAQ,uEACU,SAAS,QAAQ,SAAS,SAAS,GACzE;OACI;GACL,MAAM,UAAU,CAAC,GAAG,cAAc,CAC/B,QAAO,MAAK,MAAM,yBAAyB,CAC3C,MAAM,CACN,KAAI,MAAK,IAAI,EAAE,GAAG,CAClB,KAAK,IAAI;AAEZ,UAAO,KACL,QAAQ,MAAM,KAAK,QAAQ,mEACmB,QAAQ,GACvD;;EAGL,MAAM,UAAU,aAAa,eAAe,cAAc;AAC1D,OAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,aAAa,WAAW,QAAQ,QAAQ;GAC9C,MAAM,aAAa,WAAW,QAAQ,QAAQ;AAC9C,OAAI,WAAW,QAAQ,QAAQ,SAAS,WAAW,QAAQ,QAAQ,KACjE,QAAO,KACL,QAAQ,MAAM,KAAK,OAAO,4BAA4B,WAAW,oCAC5B,WAAW,GACjD;;AAGL,MAAI,CAAC,WAAW,WACd,QAAO,KACL,QAAQ,MAAM,yDACf;OACI;GACL,MAAM,mBAAmB,IAAI,IAAI,WAAW,WAAW;AACvD,OACE,CAAC,WAAW,UAAU,wBAAwB,MAAK,QACjD,OAAO,kBAAkB,IAAI,IAAI,IAAI,CAAC,CACvC,CAED,QAAO,KACL,QAAQ,MAAM,wBAAwB,WAAW,WAAW,KAAK,IAAI,CAAC,mDAEvE;;;AAIP,KAAI,OAAO,OACT,OAAM,IAAI,cAAc;EACtB,MAAM;EACN,SAAS,OAAO,KAAK,KAAK;EAC1B,QAAQ;EACT,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"connection-context-manager.js","names":["#lc","#connections","#validateLegacyJWT","#now","#revalidateIntervalMs","#retransformIntervalMs","#queryConfig","#pushConfig","#removeConnection","#nextInsertionOrder","#storeConnection","#refreshBackgroundConnectionContext","#updateBackgroundRetransformDeadline","#mustGetConnectionContext","#demoteConnection","#getConnectionContext","#group","#setGroup","#nextRevalidateAt","#getBackgroundConnectionContext","#sharedRetransformReady","#setBackgroundConnection"],"sources":["../../../../../../zero-cache/src/services/view-syncer/connection-context-manager.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {InitConnectionBody} from '../../../../zero-protocol/src/connect.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport type {UpdateAuthBody} from '../../../../zero-protocol/src/update-auth.ts';\nimport {\n authEquals,\n resolveAuth,\n type Auth,\n type ValidateLegacyJWT,\n} from '../../auth/auth.ts';\nimport type {ZeroConfig} from '../../config/zero-config.ts';\nimport {compileUrlPattern} from '../../custom/fetch.ts';\nimport {ProtocolErrorWithLevel} from '../../types/error-with-level.ts';\nimport type {ConnectParams} from '../../workers/connect-params.ts';\n\nexport type ConnectionState = 'provisional' | 'validated';\n\n/**\n * Normalized user identity shared by live connection state and group auth state.\n * `id: null` means logged out.\n */\nexport type UserState = {readonly id: string | null};\n\n/**\n * Delineates the two paths for validating a connection: either server can validate\n * the user's identity and return a definitive userID to trust, or we fall back to\n * trusting the one provided by the client in the incoming query params.\n */\nexport type ConnectionValidation =\n | {kind: 'client-fallback'}\n | {kind: 'server-validated'; validatedUserID: string | null};\n\n/**\n * Identifies one live websocket for a client slot.\n */\nexport type ConnectionSelector = {\n readonly clientID: string;\n readonly wsID: string;\n};\n\ntype FetchConfig = ZeroConfig['query'];\n\nexport type HeaderOptions = {\n readonly apiKey?: string | undefined;\n readonly customHeaders?: Readonly<Record<string, string>> | undefined;\n readonly allowedClientHeaders?: readonly string[] | undefined;\n readonly cookie?: string | undefined;\n readonly origin?: string | undefined;\n};\n\nexport type ConnectionFetchContext = {\n readonly url: string | undefined;\n readonly allowedUrlPatterns: readonly URLPattern[] | undefined;\n readonly headerOptions: HeaderOptions;\n};\n\n/**\n * A snapshot of one live connection tracked by the manager.\n *\n * `revalidateAt` is only populated while the connection is `validated`.\n */\nexport type ConnectionContext = {\n readonly state: ConnectionState;\n\n readonly clientID: string;\n readonly wsID: string;\n readonly user: UserState;\n\n readonly auth: Auth | undefined;\n\n readonly profileID: string | null;\n readonly baseCookie: string | null;\n readonly protocolVersion: number;\n\n readonly revision: number;\n\n readonly revalidateAt: number | undefined;\n\n readonly insertionOrder: number;\n\n readonly queryContext: ConnectionFetchContext;\n readonly mutateContext: ConnectionFetchContext;\n};\n\n/**\n * Group-scoped auth state shared across the live connections.\n *\n * The background connection is the validated connection currently used for\n * shared background work. Retransform happens on a group level, and uses\n * the background connection's credential to refetch the latest queries.\n */\nexport type GroupAuthState = {\n readonly pinnedUser: UserState | undefined;\n\n readonly backgroundConnection: ConnectionSelector | undefined;\n readonly retransformAt: number | undefined;\n // Defer all maintenance in case a transient failure occurs.\n readonly maintenanceNotBeforeAt: number | undefined;\n};\n\nexport type ConnectionContextManager = {\n registerConnection(\n selector: ConnectionSelector,\n connectParams: ConnectParams,\n auth?: Auth,\n ): Readonly<ConnectionContext>;\n\n initConnection(\n selector: ConnectionSelector,\n body: InitConnectionBody,\n ): Readonly<ConnectionContext>;\n\n updateAuth(\n selector: ConnectionSelector,\n body: UpdateAuthBody,\n ): Promise<Readonly<ConnectionContext>>;\n\n validateConnection(\n selector: ConnectionSelector,\n revision: number,\n validation: ConnectionValidation,\n ):\n | Readonly<{\n connection: ConnectionContext;\n group: GroupAuthState;\n }>\n | undefined;\n\n failConnection(\n selector: ConnectionSelector,\n revision: number,\n ): Readonly<ConnectionContext> | undefined;\n closeConnection(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined;\n\n markBackgroundRetransformSuccess(\n selector: ConnectionSelector,\n revision: number,\n ): void;\n\n setSharedRetransformReady(ready: boolean): void;\n\n deferMaintenance(kind: 'revalidate' | 'retransform'): void;\n\n getConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined;\n mustGetConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext>;\n\n getBackgroundConnectionContext(): Readonly<ConnectionContext> | undefined;\n mustGetBackgroundConnectionContext(): Readonly<ConnectionContext>;\n\n getGroupState(): Readonly<GroupAuthState>;\n\n planMaintenance(): {\n dueRevalidations: Readonly<ConnectionContext>[];\n dueRetransform: boolean;\n earliestDeadlineAt: number | undefined;\n };\n};\n\n/**\n * State machine for the auth state of a single `ViewSyncerService`.\n *\n * Connections are registered as `provisional`, optionally backfilled with\n * `initConnection` metadata, and then promoted to `validated` once their\n * effective `userID` is confirmed as valid. The manager also tracks which\n * validated connection currently serves as the group's background connection.\n */\nexport class ConnectionContextManagerImpl implements ConnectionContextManager {\n readonly #lc: LogContext;\n\n // The live connection records, keyed by clientID\n readonly #connections = new Map<string, ConnectionContext>();\n #group: GroupAuthState = {\n pinnedUser: undefined,\n backgroundConnection: undefined,\n retransformAt: undefined,\n maintenanceNotBeforeAt: undefined,\n };\n\n readonly #validateLegacyJWT: ValidateLegacyJWT | undefined;\n\n readonly #now: () => number;\n readonly #revalidateIntervalMs: number | undefined;\n readonly #retransformIntervalMs: number | undefined;\n readonly #queryConfig: FetchConfig | undefined;\n readonly #pushConfig: FetchConfig | undefined;\n #sharedRetransformReady = false;\n #nextInsertionOrder = 0;\n\n constructor(\n lc: LogContext,\n revalidateIntervalSeconds?: number,\n retransformIntervalSeconds?: number,\n queryConfig?: FetchConfig,\n pushConfig?: FetchConfig,\n validateLegacyJWT?: ValidateLegacyJWT,\n now?: () => number,\n ) {\n this.#lc = lc;\n this.#now = now ?? Date.now;\n this.#revalidateIntervalMs =\n revalidateIntervalSeconds === undefined\n ? undefined\n : revalidateIntervalSeconds * 1000;\n this.#retransformIntervalMs =\n retransformIntervalSeconds === undefined\n ? undefined\n : retransformIntervalSeconds * 1000;\n this.#queryConfig = queryConfig;\n this.#pushConfig = pushConfig;\n this.#validateLegacyJWT = validateLegacyJWT;\n }\n\n /**\n * Creates or replaces the live record for a websocket connection.\n *\n * Re-registering the same `clientID` drops the old socket record and starts\n * the replacement back in `provisional` state.\n */\n registerConnection(\n selector: ConnectionSelector,\n connectParams: ConnectParams,\n auth?: Auth,\n ): Readonly<ConnectionContext> {\n this.#removeConnection(selector);\n\n const getContext = (type: 'query' | 'mutate'): ConnectionFetchContext => {\n const config = type === 'query' ? this.#queryConfig : this.#pushConfig;\n\n return {\n url: config?.url?.[0],\n allowedUrlPatterns: config?.url?.map(compileUrlPattern),\n headerOptions: {\n customHeaders: undefined,\n origin: connectParams.origin,\n apiKey: config?.apiKey,\n allowedClientHeaders: cloneAllowedClientHeaders(\n config?.allowedClientHeaders,\n ),\n cookie: config?.forwardCookies ? connectParams.httpCookie : undefined,\n },\n };\n };\n\n const connection: ConnectionContext = {\n state: 'provisional',\n\n clientID: connectParams.clientID,\n wsID: connectParams.wsID,\n revision: 0,\n user: {id: connectParams.userID ?? null},\n auth,\n\n profileID: connectParams.profileID,\n baseCookie: connectParams.baseCookie,\n protocolVersion: connectParams.protocolVersion,\n\n revalidateAt: undefined,\n\n queryContext: getContext('query'),\n mutateContext: getContext('mutate'),\n\n insertionOrder: ++this.#nextInsertionOrder,\n };\n this.#storeConnection(connection);\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n return connection;\n }\n\n /**\n * Backfills `initConnection` data for sockets that were registered before the\n * client could send its full init payload.\n *\n * This updates metadata only; it does not validate the connection.\n */\n initConnection(\n selector: ConnectionSelector,\n body: InitConnectionBody,\n ): Readonly<ConnectionContext> {\n const connection = this.#mustGetConnectionContext(selector);\n\n let queryContext = connection.queryContext;\n let mutateContext = connection.mutateContext;\n\n if (body.userQueryURL) {\n queryContext = {\n ...queryContext,\n url: body.userQueryURL,\n };\n }\n if (body.userQueryHeaders) {\n queryContext = {\n ...queryContext,\n headerOptions: {\n ...queryContext.headerOptions,\n customHeaders: cloneCustomHeaders(body.userQueryHeaders),\n },\n };\n }\n if (body.userPushURL) {\n mutateContext = {\n ...mutateContext,\n url: body.userPushURL,\n };\n }\n if (body.userPushHeaders) {\n mutateContext = {\n ...mutateContext,\n headerOptions: {\n ...mutateContext.headerOptions,\n customHeaders: cloneCustomHeaders(body.userPushHeaders),\n },\n };\n }\n\n return this.#demoteConnection({\n ...connection,\n revision: connection.revision + 1,\n queryContext,\n mutateContext,\n });\n }\n\n /**\n * A material auth change demotes the connection back to provisional until it\n * is validated again.\n */\n async updateAuth(\n selector: ConnectionSelector,\n body: UpdateAuthBody,\n ): Promise<Readonly<ConnectionContext>> {\n const connection = this.#mustGetConnectionContext(selector);\n\n const nextAuth = await resolveAuth(\n this.#lc,\n connection.auth,\n connection.user.id,\n body.auth,\n this.#validateLegacyJWT,\n );\n\n const authChanged = !authEquals(connection.auth, nextAuth);\n if (authChanged) {\n return this.#demoteConnection({\n ...connection,\n auth: nextAuth,\n revision: connection.revision + 1,\n });\n }\n\n if (nextAuth === connection.auth) {\n return connection;\n }\n\n return this.#storeConnection({\n ...connection,\n auth: nextAuth,\n });\n }\n\n /**\n * Validates one connection against the group's pinned `userID`.\n *\n * The first successful validation binds the group `userID`. Later\n * validations must match it. Validation also refreshes the connection's\n * revalidation deadline and may pick the connection as the group\n * background connection if none is currently available. If the websocket is\n * gone by the time async validation finishes, this becomes a no-op.\n */\n validateConnection(\n selector: ConnectionSelector,\n revision: number,\n validation: ConnectionValidation,\n ):\n | Readonly<{\n connection: ConnectionContext;\n group: GroupAuthState;\n }>\n | undefined {\n const connection = this.#getConnectionContext(selector);\n if (!connection) {\n return undefined;\n }\n\n if (connection.revision !== revision) {\n this.#lc.debug?.('Skipping validateConnection for stale revision', {\n clientID: selector.clientID,\n attemptedRevision: revision,\n currentRevision: connection.revision,\n });\n return undefined;\n }\n\n let validatedUserState: UserState | undefined;\n\n // If the API server has validated the user's identity, we ensure that\n // the connection's claimed userID matches it.\n if (validation.kind === 'server-validated') {\n validatedUserState = {id: validation.validatedUserID};\n\n // Check that the ws connection userID provided by the client\n // matches the validated userID from the API server.\n if (connection.user.id !== validatedUserState.id) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Unauthorized,\n message:\n 'Connection userID does not match validated server userID.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n }\n\n // The incoming user state is either the validated user state from the server\n // or the WS client's claimed user state if no server validation occurred.\n const incomingUserState = validatedUserState ?? connection.user;\n\n // Once a client group is validated, every later validated connection must\n // agree with that pinned identity.\n if (\n this.#group.pinnedUser !== undefined &&\n this.#group.pinnedUser.id !== incomingUserState.id\n ) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Unauthorized,\n message:\n 'Client groups are pinned to a single userID. Connection userID does not match existing client group userID.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n\n if (this.#group.pinnedUser === undefined) {\n this.#setGroup({\n ...this.#group,\n pinnedUser: incomingUserState,\n });\n }\n\n const validatedConnection = this.#storeConnection({\n ...connection,\n state: 'validated',\n revalidateAt: this.#nextRevalidateAt(),\n });\n this.#refreshBackgroundConnectionContext(validatedConnection);\n this.#updateBackgroundRetransformDeadline(false);\n\n return {\n connection: validatedConnection,\n group: this.getGroupState(),\n };\n }\n\n /** Removes one connection due to failed auth and updates all derived background/deadline state. */\n failConnection(\n selector: ConnectionSelector,\n revision: number,\n ): ConnectionContext | undefined {\n return this.#removeConnection(selector, revision);\n }\n\n /** Removes one disconnected connection and updates all derived background/deadline state. */\n closeConnection(selector: ConnectionSelector): ConnectionContext | undefined {\n return this.#removeConnection(selector);\n }\n\n /**\n * Records a successful background retransform. This starts a fresh interval\n * from the manager clock when shared retransform is schedulable, or\n * clears the deadline if it is not.\n */\n markBackgroundRetransformSuccess(\n selector: ConnectionSelector,\n revision: number,\n ): void {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (!backgroundConnection) {\n return;\n }\n if (\n backgroundConnection.clientID !== selector.clientID ||\n backgroundConnection.wsID !== selector.wsID ||\n backgroundConnection.revision !== revision\n ) {\n return;\n }\n this.#updateBackgroundRetransformDeadline(true);\n }\n\n setSharedRetransformReady(ready: boolean): void {\n if (this.#sharedRetransformReady === ready) {\n return;\n }\n this.#sharedRetransformReady = ready;\n this.#updateBackgroundRetransformDeadline(true);\n }\n\n deferMaintenance(kind: 'revalidate' | 'retransform'): void {\n const intervalMs =\n kind === 'revalidate'\n ? this.#revalidateIntervalMs\n : this.#retransformIntervalMs;\n if (intervalMs === undefined) {\n return;\n }\n this.#setGroup({\n ...this.#group,\n maintenanceNotBeforeAt: Math.max(\n this.#group.maintenanceNotBeforeAt ?? 0,\n this.#now() + intervalMs,\n ),\n });\n }\n\n /** Returns the current live record for a client slot, if any. */\n getConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined {\n return this.#getConnectionContext(selector);\n }\n\n /** Returns the live record for one websocket or throws if it is unavailable. */\n mustGetConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> {\n return this.#mustGetConnectionContext(selector);\n }\n\n /** Returns the current background connection, if one exists. */\n getBackgroundConnectionContext(): Readonly<ConnectionContext> | undefined {\n return this.#getBackgroundConnectionContext();\n }\n\n mustGetBackgroundConnectionContext(): Readonly<ConnectionContext> {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (!backgroundConnection) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.InvalidConnectionRequest,\n message:\n 'No validated connection is available for shared query work.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n return backgroundConnection;\n }\n\n /** Returns the shared group auth state. */\n getGroupState(): Readonly<GroupAuthState> {\n return this.#group;\n }\n\n /**\n * Reports which maintenance work is currently due.\n *\n * The result is a pure snapshot: callers decide which actions to run and\n * when to wake up next. `earliestDeadlineAt` is the earliest outstanding\n * maintenance deadline, including overdue work, unless a transient failure\n * has deferred all scheduled maintenance until `maintenanceNotBeforeAt`.\n */\n planMaintenance(): {\n dueRevalidations: Readonly<ConnectionContext>[];\n dueRetransform: boolean;\n earliestDeadlineAt: number | undefined;\n } {\n const dueRevalidations: Readonly<ConnectionContext>[] = [];\n const now = this.#now();\n let earliestDeadlineAt = this.#group.retransformAt;\n\n for (const connection of this.#connections.values()) {\n if (\n connection.state !== 'validated' ||\n connection.revalidateAt === undefined\n ) {\n continue;\n }\n if (connection.revalidateAt <= now) {\n dueRevalidations.push(connection);\n }\n earliestDeadlineAt = minDefined(\n earliestDeadlineAt,\n connection.revalidateAt,\n );\n }\n\n const dueRetransform =\n this.#group.retransformAt !== undefined &&\n this.#group.retransformAt <= now;\n const maintenanceNotBeforeAt = this.#group.maintenanceNotBeforeAt;\n\n if (\n maintenanceNotBeforeAt !== undefined &&\n maintenanceNotBeforeAt > now &&\n earliestDeadlineAt !== undefined\n ) {\n return {\n dueRevalidations: [],\n dueRetransform: false,\n earliestDeadlineAt: Math.max(\n earliestDeadlineAt,\n maintenanceNotBeforeAt,\n ),\n };\n }\n\n return {\n dueRevalidations: dueRevalidations.sort(compareByInsertionOrder),\n dueRetransform,\n earliestDeadlineAt,\n };\n }\n\n #removeConnection(\n selector: ConnectionSelector,\n revision?: number,\n ): Readonly<ConnectionContext> | undefined {\n const connection = this.#getConnectionContext(selector);\n\n if (!connection) {\n return undefined;\n }\n\n // If the revision has changed, we should not remove the connection\n if (revision !== undefined && connection.revision !== revision) {\n this.#lc.debug?.('Ignoring failConnection for stale revision', {\n clientID: selector.clientID,\n wsID: selector.wsID,\n attemptedRevision: revision,\n currentRevision: connection.revision,\n });\n return undefined;\n }\n\n this.#connections.delete(connection.clientID);\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n\n return connection;\n }\n\n #demoteConnection(connection: ConnectionContext): ConnectionContext {\n const demotedConnection = this.#storeConnection({\n ...connection,\n state: 'provisional',\n revalidateAt: undefined,\n });\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n return demotedConnection;\n }\n\n /**\n * Keeps the background connection sticky while it remains validated.\n *\n * When a newly validated `preferred` connection is provided, it is promoted\n * only if there is no current validated background connection. Otherwise the\n * existing background connection stays in place until it disappears or is\n * demoted, at which point the newest validated connection is selected.\n */\n #refreshBackgroundConnectionContext(preferred?: ConnectionContext): void {\n if (preferred?.state === 'validated') {\n const currentBackgroundConnection =\n this.#getBackgroundConnectionContext();\n if (\n currentBackgroundConnection?.clientID === preferred.clientID &&\n currentBackgroundConnection.wsID === preferred.wsID\n ) {\n return;\n }\n if (currentBackgroundConnection !== undefined) {\n return;\n }\n this.#setBackgroundConnection({\n clientID: preferred.clientID,\n wsID: preferred.wsID,\n });\n this.#lc.debug?.('Selected background connection for shared auth work', {\n clientID: preferred.clientID,\n wsID: preferred.wsID,\n revision: preferred.revision,\n reason: 'preferred-validated',\n });\n return;\n }\n\n const currentBackgroundConnection = this.#getBackgroundConnectionContext();\n if (currentBackgroundConnection?.state === 'validated') {\n return;\n }\n\n const nextBackgroundConnection = [...this.#connections.values()]\n .filter(connection => connection.state === 'validated')\n .sort(comparePreferredValidatedConnection)\n .at(0);\n this.#setBackgroundConnection(\n nextBackgroundConnection\n ? {\n clientID: nextBackgroundConnection.clientID,\n wsID: nextBackgroundConnection.wsID,\n }\n : undefined,\n );\n if (nextBackgroundConnection) {\n this.#lc.debug?.('Selected background connection for shared auth work', {\n clientID: nextBackgroundConnection.clientID,\n wsID: nextBackgroundConnection.wsID,\n revision: nextBackgroundConnection.revision,\n reason: 'fallback-validated',\n });\n }\n }\n\n #getBackgroundConnectionContext(): ConnectionContext | undefined {\n const backgroundConnection = this.#group.backgroundConnection;\n if (!backgroundConnection) {\n return undefined;\n }\n return this.#getConnectionContext(backgroundConnection);\n }\n\n #getConnectionContext(\n selector: ConnectionSelector,\n ): ConnectionContext | undefined {\n const connection = this.#connections.get(selector.clientID);\n if (!connection) {\n return undefined;\n }\n if (connection.wsID !== selector.wsID) {\n return undefined;\n }\n return connection;\n }\n\n #mustGetConnectionContext(selector: ConnectionSelector): ConnectionContext {\n const connection = this.#getConnectionContext(selector);\n\n if (!connection) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.InvalidConnectionRequest,\n message:\n 'Connection auth state was not available for this websocket.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n\n return connection;\n }\n\n #storeConnection(connection: ConnectionContext): ConnectionContext {\n this.#connections.set(connection.clientID, connection);\n return connection;\n }\n\n #setGroup(group: GroupAuthState): GroupAuthState {\n this.#group = group;\n return group;\n }\n\n #setBackgroundConnection(\n backgroundConnection: ConnectionSelector | undefined,\n ) {\n if (\n sameConnectionSelector(\n this.#group.backgroundConnection,\n backgroundConnection,\n )\n ) {\n return;\n }\n this.#setGroup({\n ...this.#group,\n backgroundConnection: backgroundConnection\n ? {...backgroundConnection}\n : undefined,\n });\n }\n\n /**\n * Keeps the group background retransform deadline coherent with current\n * schedulability.\n *\n * When `reset` is false, this seeds a deadline only when shared retransform\n * is now possible and no deadline exists yet, preserving any existing\n * cadence. When `reset` is true, it starts a fresh interval from `#now()` if\n * retransform is schedulable for the current ready ViewSyncer instance, or\n * clears the deadline if it is not.\n */\n #updateBackgroundRetransformDeadline(reset: boolean) {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (\n !backgroundConnection ||\n this.#retransformIntervalMs === undefined ||\n !this.#sharedRetransformReady\n ) {\n if (this.#group.retransformAt !== undefined) {\n this.#setGroup({\n ...this.#group,\n retransformAt: undefined,\n });\n }\n return;\n }\n\n if (reset || this.#group.retransformAt === undefined) {\n this.#setGroup({\n ...this.#group,\n retransformAt: this.#now() + this.#retransformIntervalMs,\n });\n }\n }\n\n #nextRevalidateAt() {\n return this.#revalidateIntervalMs === undefined\n ? undefined\n : this.#now() + this.#revalidateIntervalMs;\n }\n}\n\nfunction compareByInsertionOrder(\n a: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n b: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n) {\n return a.insertionOrder - b.insertionOrder || a.wsID.localeCompare(b.wsID);\n}\n\nfunction comparePreferredValidatedConnection(\n a: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n b: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n) {\n return b.insertionOrder - a.insertionOrder || b.wsID.localeCompare(a.wsID);\n}\n\nfunction minDefined(a: number | undefined, b: number | undefined) {\n if (a === undefined) {\n return b;\n }\n if (b === undefined) {\n return a;\n }\n return Math.min(a, b);\n}\n\nfunction sameConnectionSelector(\n a: ConnectionSelector | undefined,\n b: ConnectionSelector | undefined,\n) {\n return a?.clientID === b?.clientID && a?.wsID === b?.wsID;\n}\n\nfunction cloneCustomHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n) {\n return headers ? {...headers} : undefined;\n}\n\nfunction cloneAllowedClientHeaders(headers: readonly string[] | undefined) {\n return headers ? [...headers] : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"connection-context-manager.js","names":["#lc","#connections","#validateLegacyJWT","#now","#revalidateIntervalMs","#retransformIntervalMs","#queryConfig","#pushConfig","#removeConnection","#nextInsertionOrder","#storeConnection","#refreshBackgroundConnectionContext","#updateBackgroundRetransformDeadline","#mustGetConnectionContext","#demoteConnection","#getConnectionContext","#group","#setGroup","#nextRevalidateAt","#getBackgroundConnectionContext","#sharedRetransformReady","#setBackgroundConnection"],"sources":["../../../../../../zero-cache/src/services/view-syncer/connection-context-manager.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {InitConnectionBody} from '../../../../zero-protocol/src/connect.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport type {UpdateAuthBody} from '../../../../zero-protocol/src/update-auth.ts';\nimport {\n authEquals,\n resolveAuth,\n type Auth,\n type ValidateLegacyJWT,\n} from '../../auth/auth.ts';\nimport type {ZeroConfig} from '../../config/zero-config.ts';\nimport {compileUrlPattern} from '../../custom/fetch.ts';\nimport {ProtocolErrorWithLevel} from '../../types/error-with-level.ts';\nimport type {ConnectParams} from '../../workers/connect-params.ts';\n\nexport type ConnectionState = 'provisional' | 'validated';\n\n/**\n * Normalized user identity shared by live connection state and group auth state.\n * `id: null` means logged out.\n */\nexport type UserState = {readonly id: string | null};\n\n/**\n * Delineates the two paths for validating a connection: either server can validate\n * the user's identity and return a definitive userID to trust, or we fall back to\n * trusting the one provided by the client in the incoming query params.\n */\nexport type ConnectionValidation =\n | {kind: 'client-fallback'}\n | {kind: 'server-validated'; validatedUserID: string | null};\n\n/**\n * Identifies one live websocket for a client slot.\n */\nexport type ConnectionSelector = {\n readonly clientID: string;\n readonly wsID: string;\n};\n\ntype FetchConfig = ZeroConfig['query'];\n\nexport type HeaderOptions = {\n readonly apiKey?: string | undefined;\n readonly customHeaders?: Readonly<Record<string, string>> | undefined;\n readonly allowedClientHeaders?: readonly string[] | undefined;\n readonly cookie?: string | undefined;\n readonly origin?: string | undefined;\n};\n\nexport type ConnectionFetchContext = {\n readonly url: string | undefined;\n readonly allowedUrlPatterns: readonly URLPattern[] | undefined;\n readonly headerOptions: HeaderOptions;\n};\n\n/**\n * A snapshot of one live connection tracked by the manager.\n *\n * `revalidateAt` is only populated while the connection is `validated`.\n */\nexport type ConnectionContext = {\n readonly state: ConnectionState;\n\n readonly clientID: string;\n readonly wsID: string;\n readonly user: UserState;\n\n readonly auth: Auth | undefined;\n\n readonly profileID: string | null;\n readonly baseCookie: string | null;\n readonly protocolVersion: number;\n\n readonly revision: number;\n\n readonly revalidateAt: number | undefined;\n\n readonly insertionOrder: number;\n\n readonly queryContext: ConnectionFetchContext;\n readonly mutateContext: ConnectionFetchContext;\n};\n\n/**\n * Group-scoped auth state shared across the live connections.\n *\n * The background connection is the validated connection currently used for\n * shared background work. Retransform happens on a group level, and uses\n * the background connection's credential to refetch the latest queries.\n */\nexport type GroupAuthState = {\n readonly pinnedUser: UserState | undefined;\n\n readonly backgroundConnection: ConnectionSelector | undefined;\n readonly retransformAt: number | undefined;\n // Defer all maintenance in case a transient failure occurs.\n readonly maintenanceNotBeforeAt: number | undefined;\n};\n\nexport type ConnectionContextManager = {\n registerConnection(\n selector: ConnectionSelector,\n connectParams: ConnectParams,\n auth?: Auth,\n ): Readonly<ConnectionContext>;\n\n initConnection(\n selector: ConnectionSelector,\n body: InitConnectionBody,\n ): Readonly<ConnectionContext>;\n\n updateAuth(\n selector: ConnectionSelector,\n body: UpdateAuthBody,\n ): Promise<Readonly<ConnectionContext>>;\n\n validateConnection(\n selector: ConnectionSelector,\n revision: number,\n validation: ConnectionValidation,\n ):\n | Readonly<{\n connection: ConnectionContext;\n group: GroupAuthState;\n }>\n | undefined;\n\n failConnection(\n selector: ConnectionSelector,\n revision: number,\n ): Readonly<ConnectionContext> | undefined;\n closeConnection(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined;\n\n markBackgroundRetransformSuccess(\n selector: ConnectionSelector,\n revision: number,\n ): void;\n\n setSharedRetransformReady(ready: boolean): void;\n\n deferMaintenance(kind: 'revalidate' | 'retransform'): void;\n\n getConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined;\n mustGetConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext>;\n\n getBackgroundConnectionContext(): Readonly<ConnectionContext> | undefined;\n mustGetBackgroundConnectionContext(): Readonly<ConnectionContext>;\n\n getGroupState(): Readonly<GroupAuthState>;\n\n planMaintenance(): {\n dueRevalidations: Readonly<ConnectionContext>[];\n dueRetransform: boolean;\n earliestDeadlineAt: number | undefined;\n };\n};\n\n/**\n * State machine for the auth state of a single `ViewSyncerService`.\n *\n * Connections are registered as `provisional`, optionally backfilled with\n * `initConnection` metadata, and then promoted to `validated` once their\n * effective `userID` is confirmed as valid. The manager also tracks which\n * validated connection currently serves as the group's background connection.\n */\nexport class ConnectionContextManagerImpl implements ConnectionContextManager {\n readonly #lc: LogContext;\n\n // The live connection records, keyed by clientID\n readonly #connections = new Map<string, ConnectionContext>();\n #group: GroupAuthState = {\n pinnedUser: undefined,\n backgroundConnection: undefined,\n retransformAt: undefined,\n maintenanceNotBeforeAt: undefined,\n };\n\n readonly #validateLegacyJWT: ValidateLegacyJWT | undefined;\n\n readonly #now: () => number;\n readonly #revalidateIntervalMs: number | undefined;\n readonly #retransformIntervalMs: number | undefined;\n readonly #queryConfig: FetchConfig | undefined;\n readonly #pushConfig: FetchConfig | undefined;\n #sharedRetransformReady = false;\n #nextInsertionOrder = 0;\n\n constructor(\n lc: LogContext,\n revalidateIntervalSeconds?: number,\n retransformIntervalSeconds?: number,\n queryConfig?: FetchConfig,\n pushConfig?: FetchConfig,\n validateLegacyJWT?: ValidateLegacyJWT,\n now?: () => number,\n ) {\n this.#lc = lc;\n this.#now = now ?? Date.now;\n this.#revalidateIntervalMs =\n revalidateIntervalSeconds === undefined\n ? undefined\n : revalidateIntervalSeconds * 1000;\n this.#retransformIntervalMs =\n retransformIntervalSeconds === undefined\n ? undefined\n : retransformIntervalSeconds * 1000;\n this.#queryConfig = queryConfig;\n this.#pushConfig = pushConfig;\n this.#validateLegacyJWT = validateLegacyJWT;\n }\n\n /**\n * Creates or replaces the live record for a websocket connection.\n *\n * Re-registering the same `clientID` drops the old socket record and starts\n * the replacement back in `provisional` state.\n */\n registerConnection(\n selector: ConnectionSelector,\n connectParams: ConnectParams,\n auth?: Auth,\n ): Readonly<ConnectionContext> {\n this.#removeConnection(selector);\n\n const getContext = (type: 'query' | 'mutate'): ConnectionFetchContext => {\n const config = type === 'query' ? this.#queryConfig : this.#pushConfig;\n\n return {\n url: config?.url?.[0],\n allowedUrlPatterns: config?.url?.map(compileUrlPattern),\n headerOptions: {\n customHeaders: undefined,\n origin: connectParams.origin,\n apiKey: config?.apiKey,\n allowedClientHeaders: cloneAllowedClientHeaders(\n config?.allowedClientHeaders,\n ),\n cookie: config?.forwardCookies ? connectParams.httpCookie : undefined,\n },\n };\n };\n\n const connection: ConnectionContext = {\n state: 'provisional',\n\n clientID: connectParams.clientID,\n wsID: connectParams.wsID,\n revision: 0,\n user: {id: connectParams.userID ?? null},\n auth,\n\n profileID: connectParams.profileID,\n baseCookie: connectParams.baseCookie,\n protocolVersion: connectParams.protocolVersion,\n\n revalidateAt: undefined,\n\n queryContext: getContext('query'),\n mutateContext: getContext('mutate'),\n\n insertionOrder: ++this.#nextInsertionOrder,\n };\n this.#storeConnection(connection);\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n return connection;\n }\n\n /**\n * Backfills `initConnection` data for sockets that were registered before the\n * client could send its full init payload.\n *\n * This updates metadata only; it does not validate the connection.\n */\n initConnection(\n selector: ConnectionSelector,\n body: InitConnectionBody,\n ): Readonly<ConnectionContext> {\n const connection = this.#mustGetConnectionContext(selector);\n\n let queryContext = connection.queryContext;\n let mutateContext = connection.mutateContext;\n\n if (body.userQueryURL) {\n queryContext = {\n ...queryContext,\n url: body.userQueryURL,\n };\n }\n if (body.userQueryHeaders) {\n queryContext = {\n ...queryContext,\n headerOptions: {\n ...queryContext.headerOptions,\n customHeaders: cloneCustomHeaders(body.userQueryHeaders),\n },\n };\n }\n if (body.userPushURL) {\n mutateContext = {\n ...mutateContext,\n url: body.userPushURL,\n };\n }\n if (body.userPushHeaders) {\n mutateContext = {\n ...mutateContext,\n headerOptions: {\n ...mutateContext.headerOptions,\n customHeaders: cloneCustomHeaders(body.userPushHeaders),\n },\n };\n }\n\n return this.#demoteConnection({\n ...connection,\n revision: connection.revision + 1,\n queryContext,\n mutateContext,\n });\n }\n\n /**\n * A material auth change demotes the connection back to provisional until it\n * is validated again.\n */\n async updateAuth(\n selector: ConnectionSelector,\n body: UpdateAuthBody,\n ): Promise<Readonly<ConnectionContext>> {\n const connection = this.#mustGetConnectionContext(selector);\n\n const nextAuth = await resolveAuth(\n this.#lc,\n connection.auth,\n connection.user.id,\n body.auth,\n this.#validateLegacyJWT,\n );\n\n const authChanged = !authEquals(connection.auth, nextAuth);\n if (authChanged) {\n return this.#demoteConnection({\n ...connection,\n auth: nextAuth,\n revision: connection.revision + 1,\n });\n }\n\n if (nextAuth === connection.auth) {\n return connection;\n }\n\n return this.#storeConnection({\n ...connection,\n auth: nextAuth,\n });\n }\n\n /**\n * Validates one connection against the group's pinned `userID`.\n *\n * The first successful validation binds the group `userID`. Later\n * validations must match it. Validation also refreshes the connection's\n * revalidation deadline and may pick the connection as the group\n * background connection if none is currently available. If the websocket is\n * gone by the time async validation finishes, this becomes a no-op.\n */\n validateConnection(\n selector: ConnectionSelector,\n revision: number,\n validation: ConnectionValidation,\n ):\n | Readonly<{\n connection: ConnectionContext;\n group: GroupAuthState;\n }>\n | undefined {\n const connection = this.#getConnectionContext(selector);\n if (!connection) {\n return undefined;\n }\n\n if (connection.revision !== revision) {\n this.#lc.debug?.('Skipping validateConnection for stale revision', {\n clientID: selector.clientID,\n attemptedRevision: revision,\n currentRevision: connection.revision,\n });\n return undefined;\n }\n\n let validatedUserState: UserState | undefined;\n\n // If the API server has validated the user's identity, we ensure that\n // the connection's claimed userID matches it.\n if (validation.kind === 'server-validated') {\n validatedUserState = {id: validation.validatedUserID};\n\n // Check that the ws connection userID provided by the client\n // matches the validated userID from the API server.\n if (connection.user.id !== validatedUserState.id) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Unauthorized,\n message:\n 'Connection userID does not match validated server userID.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n }\n\n // The incoming user state is either the validated user state from the server\n // or the WS client's claimed user state if no server validation occurred.\n const incomingUserState = validatedUserState ?? connection.user;\n\n // Once a client group is validated, every later validated connection must\n // agree with that pinned identity.\n if (\n this.#group.pinnedUser !== undefined &&\n this.#group.pinnedUser.id !== incomingUserState.id\n ) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Unauthorized,\n message:\n 'Client groups are pinned to a single userID. Connection userID does not match existing client group userID.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n\n if (this.#group.pinnedUser === undefined) {\n this.#setGroup({\n ...this.#group,\n pinnedUser: incomingUserState,\n });\n }\n\n const validatedConnection = this.#storeConnection({\n ...connection,\n state: 'validated',\n revalidateAt: this.#nextRevalidateAt(),\n });\n this.#refreshBackgroundConnectionContext(validatedConnection);\n this.#updateBackgroundRetransformDeadline(false);\n\n return {\n connection: validatedConnection,\n group: this.getGroupState(),\n };\n }\n\n /** Removes one connection due to failed auth and updates all derived background/deadline state. */\n failConnection(\n selector: ConnectionSelector,\n revision: number,\n ): ConnectionContext | undefined {\n return this.#removeConnection(selector, revision);\n }\n\n /** Removes one disconnected connection and updates all derived background/deadline state. */\n closeConnection(selector: ConnectionSelector): ConnectionContext | undefined {\n return this.#removeConnection(selector);\n }\n\n /**\n * Records a successful background retransform. This starts a fresh interval\n * from the manager clock when shared retransform is schedulable, or\n * clears the deadline if it is not.\n */\n markBackgroundRetransformSuccess(\n selector: ConnectionSelector,\n revision: number,\n ): void {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (!backgroundConnection) {\n return;\n }\n if (\n backgroundConnection.clientID !== selector.clientID ||\n backgroundConnection.wsID !== selector.wsID ||\n backgroundConnection.revision !== revision\n ) {\n return;\n }\n this.#updateBackgroundRetransformDeadline(true);\n }\n\n setSharedRetransformReady(ready: boolean): void {\n if (this.#sharedRetransformReady === ready) {\n return;\n }\n this.#sharedRetransformReady = ready;\n this.#updateBackgroundRetransformDeadline(true);\n }\n\n deferMaintenance(kind: 'revalidate' | 'retransform'): void {\n const intervalMs =\n kind === 'revalidate'\n ? this.#revalidateIntervalMs\n : this.#retransformIntervalMs;\n if (intervalMs === undefined) {\n return;\n }\n this.#setGroup({\n ...this.#group,\n maintenanceNotBeforeAt: Math.max(\n this.#group.maintenanceNotBeforeAt ?? 0,\n this.#now() + intervalMs,\n ),\n });\n }\n\n /** Returns the current live record for a client slot, if any. */\n getConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> | undefined {\n return this.#getConnectionContext(selector);\n }\n\n /** Returns the live record for one websocket or throws if it is unavailable. */\n mustGetConnectionContext(\n selector: ConnectionSelector,\n ): Readonly<ConnectionContext> {\n return this.#mustGetConnectionContext(selector);\n }\n\n /** Returns the current background connection, if one exists. */\n getBackgroundConnectionContext(): Readonly<ConnectionContext> | undefined {\n return this.#getBackgroundConnectionContext();\n }\n\n mustGetBackgroundConnectionContext(): Readonly<ConnectionContext> {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (!backgroundConnection) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.InvalidConnectionRequest,\n message:\n 'No validated connection is available for shared query work.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n return backgroundConnection;\n }\n\n /** Returns the shared group auth state. */\n getGroupState(): Readonly<GroupAuthState> {\n return this.#group;\n }\n\n /**\n * Reports which maintenance work is currently due.\n *\n * The result is a pure snapshot: callers decide which actions to run and\n * when to wake up next. `earliestDeadlineAt` is the earliest outstanding\n * maintenance deadline, including overdue work, unless a transient failure\n * has deferred all scheduled maintenance until `maintenanceNotBeforeAt`.\n */\n planMaintenance(): {\n dueRevalidations: Readonly<ConnectionContext>[];\n dueRetransform: boolean;\n earliestDeadlineAt: number | undefined;\n } {\n const dueRevalidations: Readonly<ConnectionContext>[] = [];\n const now = this.#now();\n let earliestDeadlineAt = this.#group.retransformAt;\n\n for (const connection of this.#connections.values()) {\n if (\n connection.state !== 'validated' ||\n connection.revalidateAt === undefined\n ) {\n continue;\n }\n if (connection.revalidateAt <= now) {\n dueRevalidations.push(connection);\n }\n earliestDeadlineAt = minDefined(\n earliestDeadlineAt,\n connection.revalidateAt,\n );\n }\n\n const dueRetransform =\n this.#group.retransformAt !== undefined &&\n this.#group.retransformAt <= now;\n const maintenanceNotBeforeAt = this.#group.maintenanceNotBeforeAt;\n\n if (\n maintenanceNotBeforeAt !== undefined &&\n maintenanceNotBeforeAt > now &&\n earliestDeadlineAt !== undefined\n ) {\n return {\n dueRevalidations: [],\n dueRetransform: false,\n earliestDeadlineAt: Math.max(\n earliestDeadlineAt,\n maintenanceNotBeforeAt,\n ),\n };\n }\n\n return {\n dueRevalidations: dueRevalidations.sort(compareByInsertionOrder),\n dueRetransform,\n earliestDeadlineAt,\n };\n }\n\n #removeConnection(\n selector: ConnectionSelector,\n revision?: number,\n ): Readonly<ConnectionContext> | undefined {\n const connection = this.#getConnectionContext(selector);\n\n if (!connection) {\n return undefined;\n }\n\n // If the revision has changed, we should not remove the connection\n if (revision !== undefined && connection.revision !== revision) {\n this.#lc.debug?.('Ignoring failConnection for stale revision', {\n clientID: selector.clientID,\n wsID: selector.wsID,\n attemptedRevision: revision,\n currentRevision: connection.revision,\n });\n return undefined;\n }\n\n this.#connections.delete(connection.clientID);\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n\n return connection;\n }\n\n #demoteConnection(connection: ConnectionContext): ConnectionContext {\n const demotedConnection = this.#storeConnection({\n ...connection,\n state: 'provisional',\n revalidateAt: undefined,\n });\n this.#refreshBackgroundConnectionContext();\n this.#updateBackgroundRetransformDeadline(false);\n return demotedConnection;\n }\n\n /**\n * Keeps the background connection sticky while it remains validated.\n *\n * When a newly validated `preferred` connection is provided, it is promoted\n * only if there is no current validated background connection. Otherwise the\n * existing background connection stays in place until it disappears or is\n * demoted, at which point the newest validated connection is selected.\n */\n #refreshBackgroundConnectionContext(preferred?: ConnectionContext): void {\n if (preferred?.state === 'validated') {\n const currentBackgroundConnection =\n this.#getBackgroundConnectionContext();\n if (\n currentBackgroundConnection?.clientID === preferred.clientID &&\n currentBackgroundConnection.wsID === preferred.wsID\n ) {\n return;\n }\n if (currentBackgroundConnection !== undefined) {\n return;\n }\n this.#setBackgroundConnection({\n clientID: preferred.clientID,\n wsID: preferred.wsID,\n });\n this.#lc.debug?.('Selected background connection for shared auth work', {\n clientID: preferred.clientID,\n wsID: preferred.wsID,\n revision: preferred.revision,\n reason: 'preferred-validated',\n });\n return;\n }\n\n const currentBackgroundConnection = this.#getBackgroundConnectionContext();\n if (currentBackgroundConnection?.state === 'validated') {\n return;\n }\n\n const nextBackgroundConnection = [...this.#connections.values()]\n .filter(connection => connection.state === 'validated')\n .sort(comparePreferredValidatedConnection)\n .at(0);\n this.#setBackgroundConnection(\n nextBackgroundConnection\n ? {\n clientID: nextBackgroundConnection.clientID,\n wsID: nextBackgroundConnection.wsID,\n }\n : undefined,\n );\n if (nextBackgroundConnection) {\n this.#lc.debug?.('Selected background connection for shared auth work', {\n clientID: nextBackgroundConnection.clientID,\n wsID: nextBackgroundConnection.wsID,\n revision: nextBackgroundConnection.revision,\n reason: 'fallback-validated',\n });\n }\n }\n\n #getBackgroundConnectionContext(): ConnectionContext | undefined {\n const backgroundConnection = this.#group.backgroundConnection;\n if (!backgroundConnection) {\n return undefined;\n }\n return this.#getConnectionContext(backgroundConnection);\n }\n\n #getConnectionContext(\n selector: ConnectionSelector,\n ): ConnectionContext | undefined {\n const connection = this.#connections.get(selector.clientID);\n if (!connection) {\n return undefined;\n }\n if (connection.wsID !== selector.wsID) {\n return undefined;\n }\n return connection;\n }\n\n #mustGetConnectionContext(selector: ConnectionSelector): ConnectionContext {\n const connection = this.#getConnectionContext(selector);\n\n if (!connection) {\n throw new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.InvalidConnectionRequest,\n message:\n 'Connection auth state was not available for this websocket.',\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n\n return connection;\n }\n\n #storeConnection(connection: ConnectionContext): ConnectionContext {\n this.#connections.set(connection.clientID, connection);\n return connection;\n }\n\n #setGroup(group: GroupAuthState): GroupAuthState {\n this.#group = group;\n return group;\n }\n\n #setBackgroundConnection(\n backgroundConnection: ConnectionSelector | undefined,\n ) {\n if (\n sameConnectionSelector(\n this.#group.backgroundConnection,\n backgroundConnection,\n )\n ) {\n return;\n }\n this.#setGroup({\n ...this.#group,\n backgroundConnection: backgroundConnection\n ? {...backgroundConnection}\n : undefined,\n });\n }\n\n /**\n * Keeps the group background retransform deadline coherent with current\n * schedulability.\n *\n * When `reset` is false, this seeds a deadline only when shared retransform\n * is now possible and no deadline exists yet, preserving any existing\n * cadence. When `reset` is true, it starts a fresh interval from `#now()` if\n * retransform is schedulable for the current ready ViewSyncer instance, or\n * clears the deadline if it is not.\n */\n #updateBackgroundRetransformDeadline(reset: boolean) {\n const backgroundConnection = this.#getBackgroundConnectionContext();\n if (\n !backgroundConnection ||\n this.#retransformIntervalMs === undefined ||\n !this.#sharedRetransformReady\n ) {\n if (this.#group.retransformAt !== undefined) {\n this.#setGroup({\n ...this.#group,\n retransformAt: undefined,\n });\n }\n return;\n }\n\n if (reset || this.#group.retransformAt === undefined) {\n this.#setGroup({\n ...this.#group,\n retransformAt: this.#now() + this.#retransformIntervalMs,\n });\n }\n }\n\n #nextRevalidateAt() {\n return this.#revalidateIntervalMs === undefined\n ? undefined\n : this.#now() + this.#revalidateIntervalMs;\n }\n}\n\nfunction compareByInsertionOrder(\n a: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n b: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n) {\n return a.insertionOrder - b.insertionOrder || a.wsID.localeCompare(b.wsID);\n}\n\nfunction comparePreferredValidatedConnection(\n a: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n b: Pick<ConnectionContext, 'insertionOrder' | 'wsID'>,\n) {\n return b.insertionOrder - a.insertionOrder || b.wsID.localeCompare(a.wsID);\n}\n\nfunction minDefined(a: number | undefined, b: number | undefined) {\n if (a === undefined) {\n return b;\n }\n if (b === undefined) {\n return a;\n }\n return Math.min(a, b);\n}\n\nfunction sameConnectionSelector(\n a: ConnectionSelector | undefined,\n b: ConnectionSelector | undefined,\n) {\n return a?.clientID === b?.clientID && a?.wsID === b?.wsID;\n}\n\nfunction cloneCustomHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n) {\n return headers ? {...headers} : undefined;\n}\n\nfunction cloneAllowedClientHeaders(headers: readonly string[] | undefined) {\n return headers ? [...headers] : undefined;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cvr-purger.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-purger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAQ3C,KAAK,OAAO,GAAG;IACb,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,qBAAa,SAAU,YAAW,OAAO;;IACvC,QAAQ,CAAC,EAAE,YAAY;gBAYrB,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,EAAC,qBAAqB,EAAE,gBAAgB,EAAE,iBAAiB,EAAC,EAAE,OAAO,EACrE,uBAAuB,SAA4B;IAc/C,GAAG;IAiDT,iBAAiB,CACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,CAAC;IAkF/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAItB"}
1
+ {"version":3,"file":"cvr-purger.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-purger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAQ3C,KAAK,OAAO,GAAG;IACb,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,qBAAa,SAAU,YAAW,OAAO;;IACvC,QAAQ,CAAC,EAAE,YAAY;gBAYrB,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,EAAC,qBAAqB,EAAE,gBAAgB,EAAE,iBAAiB,EAAC,EAAE,OAAO,EACrE,uBAAuB,SAA4B;IAc/C,GAAG;IAgDT,iBAAiB,CACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,CAAC;IAkF/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAItB"}
@@ -37,7 +37,6 @@ var CVRPurger = class {
37
37
  while (this.#state.shouldRun()) try {
38
38
  const start = performance.now();
39
39
  const { purged, remaining } = await this.purgeInactiveCVRs(maxCVRsPerPurge);
40
- this.#state.resetBackoff();
41
40
  if (purgeable !== void 0 && remaining > purgeable) {
42
41
  maxCVRsPerPurge += this.#initialBatchSize;
43
42
  this.#lc.info?.(`increased CVRs per purge to ${maxCVRsPerPurge}`);
@@ -48,7 +47,7 @@ var CVRPurger = class {
48
47
  this.#lc.info?.(`purged ${purged} inactive CVRs (${elapsed.toFixed(2)} ms). Next purge in ${purgeInterval} ms`);
49
48
  await this.#state.sleep(purgeInterval);
50
49
  } catch (e) {
51
- await this.#state.backoff(this.#lc, e);
50
+ this.#lc.warn?.(`error encountered while garbage collecting CVRs`, e);
52
51
  }
53
52
  }
54
53
  purgeInactiveCVRs(maxCVRs) {