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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (518) hide show
  1. package/out/replicache/src/btree/node.d.ts +0 -2
  2. package/out/replicache/src/btree/node.d.ts.map +1 -1
  3. package/out/replicache/src/btree/node.js.map +1 -1
  4. package/out/replicache/src/connection-loop.d.ts.map +1 -1
  5. package/out/replicache/src/connection-loop.js +1 -1
  6. package/out/replicache/src/connection-loop.js.map +1 -1
  7. package/out/replicache/src/db/commit.d.ts +0 -1
  8. package/out/replicache/src/db/commit.d.ts.map +1 -1
  9. package/out/replicache/src/db/commit.js +1 -1
  10. package/out/replicache/src/db/commit.js.map +1 -1
  11. package/out/replicache/src/db/write.d.ts +0 -2
  12. package/out/replicache/src/db/write.d.ts.map +1 -1
  13. package/out/replicache/src/db/write.js +1 -1
  14. package/out/replicache/src/db/write.js.map +1 -1
  15. package/out/replicache/src/deleted-clients.d.ts +0 -2
  16. package/out/replicache/src/deleted-clients.d.ts.map +1 -1
  17. package/out/replicache/src/deleted-clients.js +1 -1
  18. package/out/replicache/src/deleted-clients.js.map +1 -1
  19. package/out/replicache/src/frozen-json.d.ts +0 -1
  20. package/out/replicache/src/frozen-json.d.ts.map +1 -1
  21. package/out/replicache/src/frozen-json.js.map +1 -1
  22. package/out/replicache/src/index-defs.d.ts +0 -2
  23. package/out/replicache/src/index-defs.d.ts.map +1 -1
  24. package/out/replicache/src/index-defs.js +1 -1
  25. package/out/replicache/src/index-defs.js.map +1 -1
  26. package/out/replicache/src/kv/expo-sqlite/store.d.ts +0 -1
  27. package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
  28. package/out/replicache/src/kv/expo-sqlite/store.js +1 -1
  29. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  30. package/out/replicache/src/kv/idb-store.js +1 -1
  31. package/out/replicache/src/kv/idb-store.js.map +1 -1
  32. package/out/replicache/src/kv/throw-if-closed.d.ts +0 -3
  33. package/out/replicache/src/kv/throw-if-closed.d.ts.map +1 -1
  34. package/out/replicache/src/kv/throw-if-closed.js.map +1 -1
  35. package/out/replicache/src/persist/client-group-gc.d.ts +0 -7
  36. package/out/replicache/src/persist/client-group-gc.d.ts.map +1 -1
  37. package/out/replicache/src/persist/client-group-gc.js +1 -1
  38. package/out/replicache/src/persist/client-group-gc.js.map +1 -1
  39. package/out/replicache/src/process-scheduler.js +1 -1
  40. package/out/replicache/src/process-scheduler.js.map +1 -1
  41. package/out/replicache/src/replicache-impl.d.ts +2 -7
  42. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  43. package/out/replicache/src/replicache-impl.js +1 -1
  44. package/out/replicache/src/replicache-impl.js.map +1 -1
  45. package/out/replicache/src/sync/patch.d.ts +0 -5
  46. package/out/replicache/src/sync/patch.d.ts.map +1 -1
  47. package/out/replicache/src/sync/patch.js +1 -1
  48. package/out/replicache/src/sync/patch.js.map +1 -1
  49. package/out/replicache/src/sync/pull.d.ts +0 -1
  50. package/out/replicache/src/sync/pull.d.ts.map +1 -1
  51. package/out/replicache/src/sync/pull.js.map +1 -1
  52. package/out/replicache/src/sync/push.d.ts +2 -1
  53. package/out/replicache/src/sync/push.d.ts.map +1 -1
  54. package/out/replicache/src/sync/push.js.map +1 -1
  55. package/out/replicache/src/types.d.ts +2 -1
  56. package/out/replicache/src/types.d.ts.map +1 -1
  57. package/out/shared/src/asserts.d.ts +0 -5
  58. package/out/shared/src/asserts.d.ts.map +1 -1
  59. package/out/shared/src/asserts.js +1 -1
  60. package/out/shared/src/asserts.js.map +1 -1
  61. package/out/shared/src/browser-env.d.ts +0 -17
  62. package/out/shared/src/browser-env.d.ts.map +1 -1
  63. package/out/shared/src/browser-env.js.map +1 -1
  64. package/out/shared/src/deep-clone.d.ts +0 -1
  65. package/out/shared/src/deep-clone.d.ts.map +1 -1
  66. package/out/shared/src/deep-clone.js +1 -1
  67. package/out/shared/src/deep-clone.js.map +1 -1
  68. package/out/shared/src/document-visible.js +1 -1
  69. package/out/shared/src/document-visible.js.map +1 -1
  70. package/out/shared/src/expand.d.ts +0 -3
  71. package/out/shared/src/expand.d.ts.map +1 -1
  72. package/out/shared/src/immutable.d.ts +1 -1
  73. package/out/shared/src/immutable.d.ts.map +1 -1
  74. package/out/shared/src/iterables.d.ts +0 -1
  75. package/out/shared/src/iterables.d.ts.map +1 -1
  76. package/out/shared/src/iterables.js.map +1 -1
  77. package/out/shared/src/logging-test-utils.js +1 -1
  78. package/out/shared/src/logging-test-utils.js.map +1 -1
  79. package/out/shared/src/options-types.d.ts +1 -1
  80. package/out/shared/src/options-types.d.ts.map +1 -1
  81. package/out/shared/src/options.d.ts +2 -3
  82. package/out/shared/src/options.d.ts.map +1 -1
  83. package/out/shared/src/options.js +1 -1
  84. package/out/shared/src/options.js.map +1 -1
  85. package/out/shared/src/promise-race.js +1 -1
  86. package/out/shared/src/promise-race.js.map +1 -1
  87. package/out/shared/src/queue.js +1 -1
  88. package/out/shared/src/queue.js.map +1 -1
  89. package/out/shared/src/resolved-promises.d.ts +0 -4
  90. package/out/shared/src/resolved-promises.d.ts.map +1 -1
  91. package/out/shared/src/resolved-promises.js +0 -1
  92. package/out/shared/src/resolved-promises.js.map +1 -1
  93. package/out/shared/src/resolver.d.ts +9 -0
  94. package/out/shared/src/resolver.d.ts.map +1 -0
  95. package/out/shared/src/resolver.js +6 -0
  96. package/out/shared/src/resolver.js.map +1 -0
  97. package/out/shared/src/sleep.js +1 -1
  98. package/out/shared/src/sleep.js.map +1 -1
  99. package/out/shared/src/tdigest.d.ts +0 -1
  100. package/out/shared/src/tdigest.d.ts.map +1 -1
  101. package/out/shared/src/tdigest.js.map +1 -1
  102. package/out/z2s/src/sql.d.ts +1 -5
  103. package/out/z2s/src/sql.d.ts.map +1 -1
  104. package/out/z2s/src/sql.js.map +1 -1
  105. package/out/zero/package.js +2 -3
  106. package/out/zero/package.js.map +1 -1
  107. package/out/zero/src/zero-cache-dev.js +1 -1
  108. package/out/zero/src/zero-cache-dev.js.map +1 -1
  109. package/out/zero-cache/src/config/zero-config.d.ts +14 -11
  110. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  111. package/out/zero-cache/src/config/zero-config.js +23 -0
  112. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  113. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  114. package/out/zero-cache/src/db/migration.js +1 -1
  115. package/out/zero-cache/src/db/migration.js.map +1 -1
  116. package/out/zero-cache/src/db/pg-copy-binary.js +1 -1
  117. package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -1
  118. package/out/zero-cache/src/db/pg-type-parser.js.map +1 -1
  119. package/out/zero-cache/src/db/specs.d.ts +3 -4
  120. package/out/zero-cache/src/db/specs.d.ts.map +1 -1
  121. package/out/zero-cache/src/db/specs.js +5 -7
  122. package/out/zero-cache/src/db/specs.js.map +1 -1
  123. package/out/zero-cache/src/db/transaction-pool.js +2 -2
  124. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  125. package/out/zero-cache/src/observability/events.js +1 -1
  126. package/out/zero-cache/src/observability/events.js.map +1 -1
  127. package/out/zero-cache/src/server/change-streamer.js +1 -1
  128. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  129. package/out/zero-cache/src/server/main.js +1 -1
  130. package/out/zero-cache/src/server/main.js.map +1 -1
  131. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  132. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  133. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +1 -1
  134. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  135. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +1 -1
  136. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  137. package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -1
  138. package/out/zero-cache/src/services/change-source/custom/change-source.js +1 -1
  139. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  140. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
  141. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  142. package/out/zero-cache/src/services/change-source/pg/change-source.js +1 -1
  143. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  144. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
  145. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  146. package/out/zero-cache/src/services/change-source/pg/lsn.d.ts +0 -2
  147. package/out/zero-cache/src/services/change-source/pg/lsn.d.ts.map +1 -1
  148. package/out/zero-cache/src/services/change-source/pg/lsn.js.map +1 -1
  149. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +1 -1
  150. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  151. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +0 -12
  152. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  153. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +5 -3
  154. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  155. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts +1 -1
  156. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts.map +1 -1
  157. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
  158. package/out/zero-cache/src/services/change-source/pg/schema/validation.d.ts +0 -1
  159. package/out/zero-cache/src/services/change-source/pg/schema/validation.d.ts.map +1 -1
  160. package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
  161. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +1 -1
  162. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  163. package/out/zero-cache/src/services/change-streamer/broadcast.js +1 -1
  164. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  165. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +1 -1
  166. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  167. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +1 -1
  168. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  169. package/out/zero-cache/src/services/change-streamer/change-streamer.js +1 -1
  170. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  171. package/out/zero-cache/src/services/change-streamer/drain-tracker.js +1 -1
  172. package/out/zero-cache/src/services/change-streamer/drain-tracker.js.map +1 -1
  173. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js +2 -2
  174. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -1
  175. package/out/zero-cache/src/services/change-streamer/restore-admission.js +2 -2
  176. package/out/zero-cache/src/services/change-streamer/restore-admission.js.map +1 -1
  177. package/out/zero-cache/src/services/change-streamer/schema/tables.js +1 -1
  178. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  179. package/out/zero-cache/src/services/change-streamer/snapshot-message.js +2 -3
  180. package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -1
  181. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +2 -2
  182. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
  183. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -1
  184. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  185. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  186. package/out/zero-cache/src/services/change-streamer/subscribe.js +1 -1
  187. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
  188. package/out/zero-cache/src/services/change-streamer/subscriber.js +1 -1
  189. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  190. package/out/zero-cache/src/services/life-cycle.js +1 -1
  191. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  192. package/out/zero-cache/src/services/litestream/commands.js +1 -1
  193. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  194. package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
  195. package/out/zero-cache/src/services/mutagen/mutagen.js +2 -2
  196. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  197. package/out/zero-cache/src/services/mutagen/pusher.js +57 -14
  198. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  199. package/out/zero-cache/src/services/plannerz.d.ts +4 -12
  200. package/out/zero-cache/src/services/plannerz.d.ts.map +1 -1
  201. package/out/zero-cache/src/services/plannerz.js +1 -3
  202. package/out/zero-cache/src/services/plannerz.js.map +1 -1
  203. package/out/zero-cache/src/services/replicator/change-log-db.js +1 -1
  204. package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
  205. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +1 -1
  206. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
  207. package/out/zero-cache/src/services/replicator/drain-handler.js +1 -1
  208. package/out/zero-cache/src/services/replicator/drain-handler.js.map +1 -1
  209. package/out/zero-cache/src/services/replicator/reporter/recorder.js +1 -1
  210. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  211. package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts +2 -11
  212. package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts.map +1 -1
  213. package/out/zero-cache/src/services/replicator/reporter/report-schema.js +1 -1
  214. package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -1
  215. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +0 -10
  216. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts.map +1 -1
  217. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  218. package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts +0 -7
  219. package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts.map +1 -1
  220. package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
  221. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
  222. package/out/zero-cache/src/services/replicator/write-worker-client.js +1 -1
  223. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  224. package/out/zero-cache/src/services/running-state.js +1 -1
  225. package/out/zero-cache/src/services/running-state.js.map +1 -1
  226. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  227. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  228. package/out/zero-cache/src/services/view-syncer/cvr-store.js +1 -1
  229. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  230. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  231. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js +2 -2
  232. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  233. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  234. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -1
  235. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +1 -1
  236. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  237. package/out/zero-cache/src/services/view-syncer/schema/cvr.js +1 -1
  238. package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
  239. package/out/zero-cache/src/services/view-syncer/schema/types.js +3 -17
  240. package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
  241. package/out/zero-cache/src/services/view-syncer/snapshotter.js +1 -1
  242. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  243. package/out/zero-cache/src/services/view-syncer/view-syncer.js +13 -10
  244. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  245. package/out/zero-cache/src/types/lite.d.ts +0 -1
  246. package/out/zero-cache/src/types/lite.d.ts.map +1 -1
  247. package/out/zero-cache/src/types/lite.js +2 -2
  248. package/out/zero-cache/src/types/lite.js.map +1 -1
  249. package/out/zero-cache/src/types/pg-types.d.ts +0 -40
  250. package/out/zero-cache/src/types/pg-types.d.ts.map +1 -1
  251. package/out/zero-cache/src/types/pg-types.js.map +1 -1
  252. package/out/zero-cache/src/types/poke-chunk.js +1 -1
  253. package/out/zero-cache/src/types/poke-chunk.js.map +1 -1
  254. package/out/zero-cache/src/types/row-key.d.ts +0 -5
  255. package/out/zero-cache/src/types/row-key.d.ts.map +1 -1
  256. package/out/zero-cache/src/types/row-key.js.map +1 -1
  257. package/out/zero-cache/src/types/streams.js +1 -1
  258. package/out/zero-cache/src/types/streams.js.map +1 -1
  259. package/out/zero-cache/src/types/subscription.js +1 -1
  260. package/out/zero-cache/src/types/subscription.js.map +1 -1
  261. package/out/zero-cache/src/types/websocket-handoff.d.ts +0 -6
  262. package/out/zero-cache/src/types/websocket-handoff.d.ts.map +1 -1
  263. package/out/zero-cache/src/types/websocket-handoff.js +8 -2
  264. package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
  265. package/out/zero-cache/src/types/ws.d.ts +2 -1
  266. package/out/zero-cache/src/types/ws.d.ts.map +1 -1
  267. package/out/zero-cache/src/types/ws.js +1 -1
  268. package/out/zero-cache/src/types/ws.js.map +1 -1
  269. package/out/zero-cache/src/workers/connection.js +4 -1
  270. package/out/zero-cache/src/workers/connection.js.map +1 -1
  271. package/out/zero-cache/src/workers/mutator.js +1 -1
  272. package/out/zero-cache/src/workers/mutator.js.map +1 -1
  273. package/out/zero-cache/src/workers/syncer.js +45 -3
  274. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  275. package/out/zero-client/src/client/active-clients-manager.d.ts.map +1 -1
  276. package/out/zero-client/src/client/active-clients-manager.js +1 -1
  277. package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
  278. package/out/zero-client/src/client/connection-manager.js +1 -1
  279. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  280. package/out/zero-client/src/client/crud.d.ts +2 -8
  281. package/out/zero-client/src/client/crud.d.ts.map +1 -1
  282. package/out/zero-client/src/client/crud.js +1 -1
  283. package/out/zero-client/src/client/crud.js.map +1 -1
  284. package/out/zero-client/src/client/custom.d.ts +0 -2
  285. package/out/zero-client/src/client/custom.d.ts.map +1 -1
  286. package/out/zero-client/src/client/custom.js +1 -1
  287. package/out/zero-client/src/client/custom.js.map +1 -1
  288. package/out/zero-client/src/client/enable-analytics.d.ts +0 -1
  289. package/out/zero-client/src/client/enable-analytics.d.ts.map +1 -1
  290. package/out/zero-client/src/client/enable-analytics.js +2 -1
  291. package/out/zero-client/src/client/enable-analytics.js.map +1 -1
  292. package/out/zero-client/src/client/error.d.ts +4 -5
  293. package/out/zero-client/src/client/error.d.ts.map +1 -1
  294. package/out/zero-client/src/client/error.js.map +1 -1
  295. package/out/zero-client/src/client/keys.d.ts +0 -1
  296. package/out/zero-client/src/client/keys.d.ts.map +1 -1
  297. package/out/zero-client/src/client/keys.js +4 -2
  298. package/out/zero-client/src/client/keys.js.map +1 -1
  299. package/out/zero-client/src/client/mutation-tracker.d.ts.map +1 -1
  300. package/out/zero-client/src/client/mutation-tracker.js +1 -1
  301. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  302. package/out/zero-client/src/client/options.d.ts +0 -4
  303. package/out/zero-client/src/client/options.d.ts.map +1 -1
  304. package/out/zero-client/src/client/options.js.map +1 -1
  305. package/out/zero-client/src/client/replicache-types.d.ts +3 -2
  306. package/out/zero-client/src/client/replicache-types.d.ts.map +1 -1
  307. package/out/zero-client/src/client/version.js +1 -1
  308. package/out/zero-client/src/client/zero-poke-handler.d.ts +1 -2
  309. package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
  310. package/out/zero-client/src/client/zero-poke-handler.js +1 -1
  311. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  312. package/out/zero-client/src/client/zero.d.ts +1 -5
  313. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  314. package/out/zero-client/src/client/zero.js +1 -1
  315. package/out/zero-client/src/client/zero.js.map +1 -1
  316. package/out/zero-client/src/types/client-state.d.ts +0 -6
  317. package/out/zero-client/src/types/client-state.d.ts.map +1 -1
  318. package/out/zero-permissions/src/permissions.d.ts +1 -12
  319. package/out/zero-permissions/src/permissions.d.ts.map +1 -1
  320. package/out/zero-permissions/src/permissions.js +1 -2
  321. package/out/zero-permissions/src/permissions.js.map +1 -1
  322. package/out/zero-protocol/src/ast.d.ts +0 -4
  323. package/out/zero-protocol/src/ast.d.ts.map +1 -1
  324. package/out/zero-protocol/src/ast.js +1 -2
  325. package/out/zero-protocol/src/ast.js.map +1 -1
  326. package/out/zero-protocol/src/change-desired-queries.d.ts +0 -20
  327. package/out/zero-protocol/src/change-desired-queries.d.ts.map +1 -1
  328. package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
  329. package/out/zero-protocol/src/client-schema.d.ts +2 -5
  330. package/out/zero-protocol/src/client-schema.d.ts.map +1 -1
  331. package/out/zero-protocol/src/client-schema.js +1 -1
  332. package/out/zero-protocol/src/client-schema.js.map +1 -1
  333. package/out/zero-protocol/src/close-connection.d.ts +0 -8
  334. package/out/zero-protocol/src/close-connection.d.ts.map +1 -1
  335. package/out/zero-protocol/src/close-connection.js +1 -1
  336. package/out/zero-protocol/src/close-connection.js.map +1 -1
  337. package/out/zero-protocol/src/connect.d.ts +0 -12
  338. package/out/zero-protocol/src/connect.d.ts.map +1 -1
  339. package/out/zero-protocol/src/connect.js +1 -1
  340. package/out/zero-protocol/src/connect.js.map +1 -1
  341. package/out/zero-protocol/src/custom-queries.d.ts +3 -17
  342. package/out/zero-protocol/src/custom-queries.d.ts.map +1 -1
  343. package/out/zero-protocol/src/custom-queries.js +1 -1
  344. package/out/zero-protocol/src/custom-queries.js.map +1 -1
  345. package/out/zero-protocol/src/data.d.ts +0 -1
  346. package/out/zero-protocol/src/data.d.ts.map +1 -1
  347. package/out/zero-protocol/src/data.js +1 -1
  348. package/out/zero-protocol/src/data.js.map +1 -1
  349. package/out/zero-protocol/src/down.d.ts +27 -27
  350. package/out/zero-protocol/src/inspect-down.d.ts +82 -84
  351. package/out/zero-protocol/src/inspect-down.d.ts.map +1 -1
  352. package/out/zero-protocol/src/inspect-down.js +1 -1
  353. package/out/zero-protocol/src/inspect-down.js.map +1 -1
  354. package/out/zero-protocol/src/inspect-up.d.ts +0 -28
  355. package/out/zero-protocol/src/inspect-up.d.ts.map +1 -1
  356. package/out/zero-protocol/src/inspect-up.js +1 -1
  357. package/out/zero-protocol/src/inspect-up.js.map +1 -1
  358. package/out/zero-protocol/src/mutate-server.d.ts +0 -21
  359. package/out/zero-protocol/src/mutate-server.d.ts.map +1 -1
  360. package/out/zero-protocol/src/mutate-server.js +1 -1
  361. package/out/zero-protocol/src/mutate-server.js.map +1 -1
  362. package/out/zero-protocol/src/mutation-type-enum.d.ts +0 -1
  363. package/out/zero-protocol/src/mutation-type-enum.d.ts.map +1 -1
  364. package/out/zero-protocol/src/mutation-type-enum.js.map +1 -1
  365. package/out/zero-protocol/src/mutation.d.ts +2 -4
  366. package/out/zero-protocol/src/mutation.d.ts.map +1 -1
  367. package/out/zero-protocol/src/mutation.js +1 -1
  368. package/out/zero-protocol/src/mutation.js.map +1 -1
  369. package/out/zero-protocol/src/mutations-patch.d.ts +0 -33
  370. package/out/zero-protocol/src/mutations-patch.d.ts.map +1 -1
  371. package/out/zero-protocol/src/mutations-patch.js +1 -1
  372. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  373. package/out/zero-protocol/src/ping.d.ts +0 -2
  374. package/out/zero-protocol/src/ping.d.ts.map +1 -1
  375. package/out/zero-protocol/src/ping.js +1 -1
  376. package/out/zero-protocol/src/ping.js.map +1 -1
  377. package/out/zero-protocol/src/poke.d.ts +3 -2
  378. package/out/zero-protocol/src/poke.d.ts.map +1 -1
  379. package/out/zero-protocol/src/poke.js +1 -1
  380. package/out/zero-protocol/src/poke.js.map +1 -1
  381. package/out/zero-protocol/src/pong.d.ts +0 -2
  382. package/out/zero-protocol/src/pong.d.ts.map +1 -1
  383. package/out/zero-protocol/src/pong.js +1 -1
  384. package/out/zero-protocol/src/pong.js.map +1 -1
  385. package/out/zero-protocol/src/pull.d.ts +2 -7
  386. package/out/zero-protocol/src/pull.d.ts.map +1 -1
  387. package/out/zero-protocol/src/pull.js +1 -1
  388. package/out/zero-protocol/src/pull.js.map +1 -1
  389. package/out/zero-protocol/src/push.d.ts +1 -3
  390. package/out/zero-protocol/src/push.d.ts.map +1 -1
  391. package/out/zero-protocol/src/push.js +1 -1
  392. package/out/zero-protocol/src/push.js.map +1 -1
  393. package/out/zero-protocol/src/queries-patch.d.ts +0 -16
  394. package/out/zero-protocol/src/queries-patch.d.ts.map +1 -1
  395. package/out/zero-protocol/src/queries-patch.js +1 -1
  396. package/out/zero-protocol/src/queries-patch.js.map +1 -1
  397. package/out/zero-protocol/src/query-server.d.ts +2 -41
  398. package/out/zero-protocol/src/query-server.d.ts.map +1 -1
  399. package/out/zero-protocol/src/query-server.js +1 -1
  400. package/out/zero-protocol/src/query-server.js.map +1 -1
  401. package/out/zero-protocol/src/up.d.ts +42 -42
  402. package/out/zero-protocol/src/version.d.ts +0 -1
  403. package/out/zero-protocol/src/version.d.ts.map +1 -1
  404. package/out/zero-protocol/src/version.js.map +1 -1
  405. package/out/zero-react/src/use-query.js +1 -1
  406. package/out/zero-react/src/use-query.js.map +1 -1
  407. package/out/zero-schema/src/builder/relationship-builder.d.ts +0 -2
  408. package/out/zero-schema/src/builder/relationship-builder.d.ts.map +1 -1
  409. package/out/zero-schema/src/builder/relationship-builder.js.map +1 -1
  410. package/out/zero-schema/src/builder/table-builder.d.ts +1 -1
  411. package/out/zero-schema/src/builder/table-builder.d.ts.map +1 -1
  412. package/out/zero-schema/src/builder/table-builder.js +1 -1
  413. package/out/zero-schema/src/builder/table-builder.js.map +1 -1
  414. package/out/zero-schema/src/schema-config.d.ts +0 -1
  415. package/out/zero-schema/src/schema-config.d.ts.map +1 -1
  416. package/out/zero-schema/src/schema-config.js +1 -1
  417. package/out/zero-schema/src/schema-config.js.map +1 -1
  418. package/out/zero-schema/src/table-schema.d.ts +3 -11
  419. package/out/zero-schema/src/table-schema.d.ts.map +1 -1
  420. package/out/zero-server/src/queries/process-queries.d.ts +2 -4
  421. package/out/zero-server/src/queries/process-queries.d.ts.map +1 -1
  422. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  423. package/out/zero-solid/src/solid-view.d.ts +0 -2
  424. package/out/zero-solid/src/solid-view.d.ts.map +1 -1
  425. package/out/zero-solid/src/solid-view.js +1 -1
  426. package/out/zero-solid/src/solid-view.js.map +1 -1
  427. package/out/zero-types/src/name-mapper.d.ts +2 -1
  428. package/out/zero-types/src/name-mapper.d.ts.map +1 -1
  429. package/out/zero-types/src/name-mapper.js.map +1 -1
  430. package/out/zero-types/src/schema-value.d.ts +3 -2
  431. package/out/zero-types/src/schema-value.d.ts.map +1 -1
  432. package/out/zero-types/src/schema.d.ts +1 -1
  433. package/out/zero-types/src/schema.d.ts.map +1 -1
  434. package/out/zql/src/builder/builder.d.ts +1 -11
  435. package/out/zql/src/builder/builder.d.ts.map +1 -1
  436. package/out/zql/src/builder/builder.js +1 -1
  437. package/out/zql/src/builder/builder.js.map +1 -1
  438. package/out/zql/src/builder/filter.d.ts +0 -1
  439. package/out/zql/src/builder/filter.d.ts.map +1 -1
  440. package/out/zql/src/builder/filter.js.map +1 -1
  441. package/out/zql/src/error.d.ts +0 -5
  442. package/out/zql/src/error.d.ts.map +1 -1
  443. package/out/zql/src/error.js.map +1 -1
  444. package/out/zql/src/ivm/constraint.d.ts +0 -7
  445. package/out/zql/src/ivm/constraint.d.ts.map +1 -1
  446. package/out/zql/src/ivm/constraint.js +1 -1
  447. package/out/zql/src/ivm/constraint.js.map +1 -1
  448. package/out/zql/src/ivm/data.d.ts +0 -1
  449. package/out/zql/src/ivm/data.d.ts.map +1 -1
  450. package/out/zql/src/ivm/data.js.map +1 -1
  451. package/out/zql/src/ivm/memory-source.d.ts +1 -2
  452. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  453. package/out/zql/src/ivm/memory-source.js +1 -1
  454. package/out/zql/src/ivm/memory-source.js.map +1 -1
  455. package/out/zql/src/ivm/stream.d.ts +0 -1
  456. package/out/zql/src/ivm/stream.d.ts.map +1 -1
  457. package/out/zql/src/ivm/stream.js.map +1 -1
  458. package/out/zql/src/ivm/union-fan-in.d.ts +0 -1
  459. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  460. package/out/zql/src/ivm/union-fan-in.js +1 -1
  461. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  462. package/out/zql/src/ivm/view-apply-change.d.ts +5 -35
  463. package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
  464. package/out/zql/src/ivm/view-apply-change.js +1 -1
  465. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  466. package/out/zql/src/mutate/crud.d.ts +1 -3
  467. package/out/zql/src/mutate/crud.d.ts.map +1 -1
  468. package/out/zql/src/mutate/crud.js +1 -1
  469. package/out/zql/src/mutate/crud.js.map +1 -1
  470. package/out/zql/src/mutate/mutator-registry.d.ts +1 -1
  471. package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -1
  472. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  473. package/out/zql/src/mutate/mutator.d.ts +1 -1
  474. package/out/zql/src/mutate/mutator.d.ts.map +1 -1
  475. package/out/zql/src/mutate/mutator.js.map +1 -1
  476. package/out/zql/src/planner/planner-debug.d.ts +10 -9
  477. package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
  478. package/out/zql/src/planner/planner-debug.js.map +1 -1
  479. package/out/zql/src/planner/planner-node.d.ts +0 -1
  480. package/out/zql/src/planner/planner-node.d.ts.map +1 -1
  481. package/out/zql/src/planner/planner-node.js.map +1 -1
  482. package/out/zql/src/planner/planner-warnings.d.ts +3 -3
  483. package/out/zql/src/planner/planner-warnings.d.ts.map +1 -1
  484. package/out/zql/src/planner/planner-warnings.js.map +1 -1
  485. package/out/zql/src/query/expression.d.ts +0 -22
  486. package/out/zql/src/query/expression.d.ts.map +1 -1
  487. package/out/zql/src/query/expression.js +1 -1
  488. package/out/zql/src/query/expression.js.map +1 -1
  489. package/out/zql/src/query/query-delegate-base.d.ts +0 -7
  490. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  491. package/out/zql/src/query/query-delegate-base.js +2 -2
  492. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  493. package/out/zql/src/query/query-delegate.d.ts +1 -4
  494. package/out/zql/src/query/query-delegate.d.ts.map +1 -1
  495. package/out/zql/src/query/query-internals.d.ts +0 -3
  496. package/out/zql/src/query/query-internals.d.ts.map +1 -1
  497. package/out/zql/src/query/query-internals.js.map +1 -1
  498. package/out/zql/src/query/query-registry.d.ts +5 -5
  499. package/out/zql/src/query/query-registry.d.ts.map +1 -1
  500. package/out/zql/src/query/query-registry.js.map +1 -1
  501. package/out/zql/src/query/query.d.ts +2 -13
  502. package/out/zql/src/query/query.d.ts.map +1 -1
  503. package/out/zql/src/query/runnable-query-impl.d.ts +1 -19
  504. package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
  505. package/out/zql/src/query/runnable-query-impl.js +1 -1
  506. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  507. package/out/zql/src/query/ttl.d.ts +2 -3
  508. package/out/zql/src/query/ttl.d.ts.map +1 -1
  509. package/out/zql/src/query/ttl.js.map +1 -1
  510. package/out/zqlite/src/query-builder.d.ts +0 -2
  511. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  512. package/out/zqlite/src/query-builder.js +1 -1
  513. package/out/zqlite/src/query-builder.js.map +1 -1
  514. package/out/zqlite/src/table-source.d.ts +0 -1
  515. package/out/zqlite/src/table-source.d.ts.map +1 -1
  516. package/out/zqlite/src/table-source.js +1 -1
  517. package/out/zqlite/src/table-source.js.map +1 -1
  518. package/package.json +2 -3
@@ -1 +1 @@
1
- {"version":3,"file":"query-stats.js","names":[],"sources":["../../../../../../zero-cache/src/services/view-syncer/query-stats.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport type {PlanWarning} from '../../../../zql/src/planner/planner-warnings.ts';\nimport {takeIndexUsage} from '../../../../zqlite/src/index-usage.ts';\nimport type {QueryShape} from './query-shape.ts';\n\n/** The query that work is accounted to. */\nexport type QueryIdentity = {\n readonly queryName?: string | undefined;\n readonly shape: QueryShape;\n};\n\nexport type HydrationOutcome = 'finished' | 'aborted' | 'failed';\n\nexport type HydrationRecord = {\n readonly outcome: HydrationOutcome;\n readonly timeMs: number;\n /** Only for a finished hydration. */\n readonly rowCount?: number | undefined;\n /** Only for a finished hydration. */\n readonly rowsRead?: number | undefined;\n readonly planWarnings?: readonly PlanWarning[] | undefined;\n};\n\nexport type AdvanceRecord = {\n /** The time spent processing the pushes to the query. */\n readonly timeMs: number;\n /** The number of changes pushed to the query. */\n readonly changes: number;\n /** Whether the advancement timed out and reset the pipelines. */\n readonly timedOut: boolean;\n};\n\n/** Why an advancement stopped pushing to a query and hydrated it again. */\nexport type AbandonReason = 'estimate' | 'read' | 'cutoff';\n\nexport type QueryStatsOptions = {\n /**\n * The most query shapes tracked per interval. Work on shapes beyond this is\n * accounted to the interval's totals only, which bounds memory when the\n * shape space is unbounded (e.g. dynamically built queries).\n */\n readonly maxShapes?: number | undefined;\n\n /**\n * The most query shapes logged per interval, those that took the most\n * time. The rest are included in the interval's totals.\n */\n readonly maxReported?: number | undefined;\n\n readonly now?: (() => number) | undefined;\n\n /** Returns the index uses since the last call. See `recordIndexUsage()`. */\n readonly takeIndexUsage?: (() => Map<string, number>) | undefined;\n};\n\nclass Distribution {\n count = 0;\n sumMs = 0;\n minMs = Infinity;\n maxMs = 0;\n\n add(ms: number) {\n this.count++;\n this.sumMs += ms;\n this.minMs = Math.min(this.minMs, ms);\n this.maxMs = Math.max(this.maxMs, ms);\n }\n\n toJSON() {\n return {\n count: this.count,\n sumMs: round(this.sumMs),\n minMs: round(this.count === 0 ? 0 : this.minMs),\n maxMs: round(this.maxMs),\n };\n }\n}\n\nclass Totals {\n readonly hydrations = new Distribution();\n hydrationsAborted = 0;\n hydrationsFailed = 0;\n hydrationRowCount = 0;\n hydrationRowsRead = 0;\n readonly advances = new Distribution();\n advanceChanges = 0;\n advanceTimeouts = 0;\n /**\n * The advancements that hydrated the query again instead of pushing (all\n * of) their changes to it, by why. The hydrations are in `hydrations`.\n */\n readonly advancesAbandoned: Record<AbandonReason, number> = {\n estimate: 0,\n read: 0,\n cutoff: 0,\n };\n\n get timeMs() {\n return this.hydrations.sumMs + this.advances.sumMs;\n }\n\n addHydration({outcome, timeMs, rowCount, rowsRead}: HydrationRecord) {\n this.hydrations.add(timeMs);\n if (outcome === 'aborted') {\n this.hydrationsAborted++;\n } else if (outcome === 'failed') {\n this.hydrationsFailed++;\n }\n this.hydrationRowCount += rowCount ?? 0;\n this.hydrationRowsRead += rowsRead ?? 0;\n }\n\n addAdvance({timeMs, changes, timedOut}: AdvanceRecord) {\n this.advances.add(timeMs);\n this.advanceChanges += changes;\n if (timedOut) {\n this.advanceTimeouts++;\n }\n }\n\n toJSON() {\n return {\n timeMs: round(this.timeMs),\n hydrations: this.hydrations.toJSON(),\n hydrationsAborted: this.hydrationsAborted,\n hydrationsFailed: this.hydrationsFailed,\n hydrationRowCount: this.hydrationRowCount,\n hydrationRowsRead: this.hydrationRowsRead,\n advances: this.advances.toJSON(),\n advanceChanges: this.advanceChanges,\n advanceTimeouts: this.advanceTimeouts,\n // Only with cost-based sync, without which there are none.\n ...(Object.values(this.advancesAbandoned).some(n => n > 0) && {\n advancesAbandoned: {...this.advancesAbandoned},\n }),\n };\n }\n}\n\nclass ShapeStats extends Totals {\n readonly query: QueryIdentity;\n planWarnings: readonly PlanWarning[] | undefined;\n\n constructor(query: QueryIdentity) {\n super();\n this.query = query;\n }\n}\n\n/** A fixed-size linear-counting sketch for shapes beyond the tracking limit. */\nclass OverflowShapeCounter {\n readonly #buckets = new Uint8Array(4096);\n #occupied = 0;\n\n add(key: string): void {\n // FNV-1a gives a stable bucket without retaining the key.\n let hash = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n hash = Math.imul(hash ^ key.charCodeAt(i), 0x01000193);\n }\n const bucket = (hash >>> 0) % this.#buckets.length;\n if (this.#buckets[bucket] === 0) {\n this.#buckets[bucket] = 1;\n this.#occupied++;\n }\n }\n\n get estimatedCount(): number {\n const empty = this.#buckets.length - this.#occupied;\n return empty === 0\n ? this.#buckets.length\n : Math.round(\n -this.#buckets.length * Math.log(empty / this.#buckets.length),\n );\n }\n}\n\n/**\n * Aggregates the work done for each query shape (the query with its values\n * redacted, see {@link QueryShape}) on a worker, and periodically logs it,\n * like `pg_stat_statements` does for Postgres.\n *\n * Unlike the slow query logs, which are throttled and only written above a\n * threshold, every hydration and advancement is counted, so the logs can be\n * summed over any time range and across workers to rank queries by the time\n * they take.\n *\n * Each {@link flush} logs the interval since the previous one:\n *\n * - a `query-stats` event per query shape, for the {@link\n * QueryStatsOptions.maxReported} shapes that took the most time, and\n * - a `query-stats-summary` event with the totals of all shapes, including\n * those not reported individually, and the `indexUses`: how often the\n * process read each index of the replica, like `pg_stat_user_indexes`. An\n * index that is never read is absent. The workers of a single-process\n * zero-cache share these counts, which the first of them to flush reports.\n *\n * The events carry counts, sums, minimums and maximums, which compose across\n * intervals and workers.\n */\nexport class QueryStats {\n readonly #maxShapes: number;\n readonly #maxReported: number;\n readonly #now: () => number;\n readonly #takeIndexUsage: () => Map<string, number>;\n\n #shapes = new Map<string, ShapeStats>();\n #totals = new Totals();\n #overflowShapes = new OverflowShapeCounter();\n #intervalStart: number;\n\n constructor({\n maxShapes = 1000,\n maxReported = 100,\n now = Date.now,\n takeIndexUsage: takeIndexUsageFn = takeIndexUsage,\n }: QueryStatsOptions = {}) {\n assert(maxShapes > 0, 'maxShapes must be positive');\n assert(maxReported > 0, 'maxReported must be positive');\n this.#maxShapes = maxShapes;\n this.#maxReported = maxReported;\n this.#now = now;\n this.#takeIndexUsage = takeIndexUsageFn;\n this.#intervalStart = now();\n }\n\n recordHydration(query: QueryIdentity, record: HydrationRecord): void {\n this.#totals.addHydration(record);\n const stats = this.#stats(query);\n if (stats) {\n stats.addHydration(record);\n if (record.planWarnings !== undefined) {\n stats.planWarnings = record.planWarnings;\n }\n }\n }\n\n recordAdvance(query: QueryIdentity, record: AdvanceRecord): void {\n this.#totals.addAdvance(record);\n this.#stats(query)?.addAdvance(record);\n }\n\n /**\n * Records that an advancement abandoned the query's pipeline, to hydrate\n * the query again.\n */\n recordAbandoned(query: QueryIdentity, reason: AbandonReason): void {\n this.#totals.advancesAbandoned[reason]++;\n const stats = this.#stats(query);\n if (stats) {\n stats.advancesAbandoned[reason]++;\n }\n }\n\n #stats(query: QueryIdentity): ShapeStats | undefined {\n const key = `${query.queryName ?? ''}:${query.shape.hash}`;\n let stats = this.#shapes.get(key);\n if (stats === undefined) {\n if (this.#shapes.size >= this.#maxShapes) {\n this.#overflowShapes.add(key);\n return undefined;\n }\n stats = new ShapeStats({queryName: query.queryName, shape: query.shape});\n this.#shapes.set(key, stats);\n }\n return stats;\n }\n\n /**\n * Logs the stats of the interval since the previous flush, and starts a\n * new interval. Nothing is logged for an interval without any work.\n */\n flush(lc: LogContext): void {\n const now = this.#now();\n const intervalMs = now - this.#intervalStart;\n const shapes = this.#shapes;\n const totals = this.#totals;\n const overflowShapes = this.#overflowShapes.estimatedCount;\n const indexUses = this.#takeIndexUsage();\n this.#shapes = new Map();\n this.#totals = new Totals();\n this.#overflowShapes = new OverflowShapeCounter();\n this.#intervalStart = now;\n\n if (shapes.size === 0 && overflowShapes === 0 && indexUses.size === 0) {\n return;\n }\n const reported = [...shapes.values()]\n .toSorted((a, b) => b.timeMs - a.timeMs)\n .slice(0, this.#maxReported);\n for (const stats of reported) {\n const {queryName, shape} = stats.query;\n lc.info?.('query stats', {\n zeroEvent: 'query-stats',\n intervalMs,\n ...(queryName !== undefined && {queryName}),\n queryShape: shape.hash,\n ...stats.toJSON(),\n ...(stats.planWarnings !== undefined &&\n stats.planWarnings.length > 0 && {planWarnings: stats.planWarnings}),\n zql: shape.zql,\n });\n }\n lc.info?.('query stats summary', {\n zeroEvent: 'query-stats-summary',\n intervalMs,\n shapes: shapes.size + overflowShapes,\n shapesReported: reported.length,\n ...totals.toJSON(),\n ...(indexUses.size > 0 && {indexUses: Object.fromEntries(indexUses)}),\n });\n }\n\n /**\n * Flushes every `intervalMs` until the returned function is called, which\n * flushes one last time.\n */\n start(lc: LogContext, intervalMs: number): () => void {\n const timer = setInterval(() => this.flush(lc), intervalMs);\n timer.unref?.();\n return () => {\n clearInterval(timer);\n this.flush(lc);\n };\n }\n}\n\nfunction round(ms: number) {\n return Math.round(ms * 100) / 100;\n}\n"],"mappings":";;;AAwDA,IAAM,eAAN,MAAmB;CACjB,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CAER,IAAI,IAAY;EACd,KAAK;EACL,KAAK,SAAS;EACd,KAAK,QAAQ,KAAK,IAAI,KAAK,OAAO,EAAE;EACpC,KAAK,QAAQ,KAAK,IAAI,KAAK,OAAO,EAAE;CACtC;CAEA,SAAS;EACP,OAAO;GACL,OAAO,KAAK;GACZ,OAAO,MAAM,KAAK,KAAK;GACvB,OAAO,MAAM,KAAK,UAAU,IAAI,IAAI,KAAK,KAAK;GAC9C,OAAO,MAAM,KAAK,KAAK;EACzB;CACF;AACF;AAEA,IAAM,SAAN,MAAa;CACX,aAAsB,IAAI,aAAa;CACvC,oBAAoB;CACpB,mBAAmB;CACnB,oBAAoB;CACpB,oBAAoB;CACpB,WAAoB,IAAI,aAAa;CACrC,iBAAiB;CACjB,kBAAkB;;;;;CAKlB,oBAA4D;EAC1D,UAAU;EACV,MAAM;EACN,QAAQ;CACV;CAEA,IAAI,SAAS;EACX,OAAO,KAAK,WAAW,QAAQ,KAAK,SAAS;CAC/C;CAEA,aAAa,EAAC,SAAS,QAAQ,UAAU,YAA4B;EACnE,KAAK,WAAW,IAAI,MAAM;EAC1B,IAAI,YAAY,WACd,KAAK;OACA,IAAI,YAAY,UACrB,KAAK;EAEP,KAAK,qBAAqB,YAAY;EACtC,KAAK,qBAAqB,YAAY;CACxC;CAEA,WAAW,EAAC,QAAQ,SAAS,YAA0B;EACrD,KAAK,SAAS,IAAI,MAAM;EACxB,KAAK,kBAAkB;EACvB,IAAI,UACF,KAAK;CAET;CAEA,SAAS;EACP,OAAO;GACL,QAAQ,MAAM,KAAK,MAAM;GACzB,YAAY,KAAK,WAAW,OAAO;GACnC,mBAAmB,KAAK;GACxB,kBAAkB,KAAK;GACvB,mBAAmB,KAAK;GACxB,mBAAmB,KAAK;GACxB,UAAU,KAAK,SAAS,OAAO;GAC/B,gBAAgB,KAAK;GACrB,iBAAiB,KAAK;GAEtB,GAAI,OAAO,OAAO,KAAK,iBAAiB,CAAC,CAAC,MAAK,MAAK,IAAI,CAAC,KAAK,EAC5D,mBAAmB,EAAC,GAAG,KAAK,kBAAiB,EAC/C;EACF;CACF;AACF;AAEA,IAAM,aAAN,cAAyB,OAAO;CAC9B;CACA;CAEA,YAAY,OAAsB;EAChC,MAAM;EACN,KAAK,QAAQ;CACf;AACF;;AAGA,IAAM,uBAAN,MAA2B;CACzB,2BAAoB,IAAI,WAAW,IAAI;CACvC,YAAY;CAEZ,IAAI,KAAmB;EAErB,IAAI,OAAO;EACX,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAC9B,OAAO,KAAK,KAAK,OAAO,IAAI,WAAW,CAAC,GAAG,QAAU;EAEvD,MAAM,UAAU,SAAS,KAAK,KAAK,SAAS;EAC5C,IAAI,KAAK,SAAS,YAAY,GAAG;GAC/B,KAAK,SAAS,UAAU;GACxB,KAAK;EACP;CACF;CAEA,IAAI,iBAAyB;EAC3B,MAAM,QAAQ,KAAK,SAAS,SAAS,KAAK;EAC1C,OAAO,UAAU,IACb,KAAK,SAAS,SACd,KAAK,MACH,CAAC,KAAK,SAAS,SAAS,KAAK,IAAI,QAAQ,KAAK,SAAS,MAAM,CAC/D;CACN;AACF;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,aAAb,MAAwB;CACtB;CACA;CACA;CACA;CAEA,0BAAU,IAAI,IAAwB;CACtC,UAAU,IAAI,OAAO;CACrB,kBAAkB,IAAI,qBAAqB;CAC3C;CAEA,YAAY,EACV,YAAY,KACZ,cAAc,KACd,MAAM,KAAK,KACX,gBAAgB,mBAAmB,mBACd,CAAC,GAAG;EACzB,OAAO,YAAY,GAAG,4BAA4B;EAClD,OAAO,cAAc,GAAG,8BAA8B;EACtD,KAAK,aAAa;EAClB,KAAK,eAAe;EACpB,KAAK,OAAO;EACZ,KAAK,kBAAkB;EACvB,KAAK,iBAAiB,IAAI;CAC5B;CAEA,gBAAgB,OAAsB,QAA+B;EACnE,KAAK,QAAQ,aAAa,MAAM;EAChC,MAAM,QAAQ,KAAK,OAAO,KAAK;EAC/B,IAAI,OAAO;GACT,MAAM,aAAa,MAAM;GACzB,IAAI,OAAO,iBAAiB,KAAA,GAC1B,MAAM,eAAe,OAAO;EAEhC;CACF;CAEA,cAAc,OAAsB,QAA6B;EAC/D,KAAK,QAAQ,WAAW,MAAM;EAC9B,KAAK,OAAO,KAAK,CAAC,EAAE,WAAW,MAAM;CACvC;;;;;CAMA,gBAAgB,OAAsB,QAA6B;EACjE,KAAK,QAAQ,kBAAkB,OAAO;EACtC,MAAM,QAAQ,KAAK,OAAO,KAAK;EAC/B,IAAI,OACF,MAAM,kBAAkB,OAAO;CAEnC;CAEA,OAAO,OAA8C;EACnD,MAAM,MAAM,GAAG,MAAM,aAAa,GAAG,GAAG,MAAM,MAAM;EACpD,IAAI,QAAQ,KAAK,QAAQ,IAAI,GAAG;EAChC,IAAI,UAAU,KAAA,GAAW;GACvB,IAAI,KAAK,QAAQ,QAAQ,KAAK,YAAY;IACxC,KAAK,gBAAgB,IAAI,GAAG;IAC5B;GACF;GACA,QAAQ,IAAI,WAAW;IAAC,WAAW,MAAM;IAAW,OAAO,MAAM;GAAK,CAAC;GACvE,KAAK,QAAQ,IAAI,KAAK,KAAK;EAC7B;EACA,OAAO;CACT;;;;;CAMA,MAAM,IAAsB;EAC1B,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,aAAa,MAAM,KAAK;EAC9B,MAAM,SAAS,KAAK;EACpB,MAAM,SAAS,KAAK;EACpB,MAAM,iBAAiB,KAAK,gBAAgB;EAC5C,MAAM,YAAY,KAAK,gBAAgB;EACvC,KAAK,0BAAU,IAAI,IAAI;EACvB,KAAK,UAAU,IAAI,OAAO;EAC1B,KAAK,kBAAkB,IAAI,qBAAqB;EAChD,KAAK,iBAAiB;EAEtB,IAAI,OAAO,SAAS,KAAK,mBAAmB,KAAK,UAAU,SAAS,GAClE;EAEF,MAAM,WAAW,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAClC,UAAU,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,CACvC,MAAM,GAAG,KAAK,YAAY;EAC7B,KAAK,MAAM,SAAS,UAAU;GAC5B,MAAM,EAAC,WAAW,UAAS,MAAM;GACjC,GAAG,OAAO,eAAe;IACvB,WAAW;IACX;IACA,GAAI,cAAc,KAAA,KAAa,EAAC,UAAS;IACzC,YAAY,MAAM;IAClB,GAAG,MAAM,OAAO;IAChB,GAAI,MAAM,iBAAiB,KAAA,KACzB,MAAM,aAAa,SAAS,KAAK,EAAC,cAAc,MAAM,aAAY;IACpE,KAAK,MAAM;GACb,CAAC;EACH;EACA,GAAG,OAAO,uBAAuB;GAC/B,WAAW;GACX;GACA,QAAQ,OAAO,OAAO;GACtB,gBAAgB,SAAS;GACzB,GAAG,OAAO,OAAO;GACjB,GAAI,UAAU,OAAO,KAAK,EAAC,WAAW,OAAO,YAAY,SAAS,EAAC;EACrE,CAAC;CACH;;;;;CAMA,MAAM,IAAgB,YAAgC;EACpD,MAAM,QAAQ,kBAAkB,KAAK,MAAM,EAAE,GAAG,UAAU;EAC1D,MAAM,QAAQ;EACd,aAAa;GACX,cAAc,KAAK;GACnB,KAAK,MAAM,EAAE;EACf;CACF;AACF;AAEA,SAAS,MAAM,IAAY;CACzB,OAAO,KAAK,MAAM,KAAK,GAAG,IAAI;AAChC"}
1
+ {"version":3,"file":"query-stats.js","names":[],"sources":["../../../../../../zero-cache/src/services/view-syncer/query-stats.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport type {PlanWarning} from '../../../../zql/src/planner/planner-warnings.ts';\nimport {takeIndexUsage} from '../../../../zqlite/src/index-usage.ts';\nimport type {QueryShape} from './query-shape.ts';\n\n/** The query that work is accounted to. */\nexport type QueryIdentity = {\n readonly queryName?: string | undefined;\n readonly shape: QueryShape;\n};\n\ntype HydrationOutcome = 'finished' | 'aborted' | 'failed';\n\nexport type HydrationRecord = {\n readonly outcome: HydrationOutcome;\n readonly timeMs: number;\n /** Only for a finished hydration. */\n readonly rowCount?: number | undefined;\n /** Only for a finished hydration. */\n readonly rowsRead?: number | undefined;\n readonly planWarnings?: readonly PlanWarning[] | undefined;\n};\n\nexport type AdvanceRecord = {\n /** The time spent processing the pushes to the query. */\n readonly timeMs: number;\n /** The number of changes pushed to the query. */\n readonly changes: number;\n /** Whether the advancement timed out and reset the pipelines. */\n readonly timedOut: boolean;\n};\n\n/** Why an advancement stopped pushing to a query and hydrated it again. */\nexport type AbandonReason = 'estimate' | 'read' | 'cutoff';\n\nexport type QueryStatsOptions = {\n /**\n * The most query shapes tracked per interval. Work on shapes beyond this is\n * accounted to the interval's totals only, which bounds memory when the\n * shape space is unbounded (e.g. dynamically built queries).\n */\n readonly maxShapes?: number | undefined;\n\n /**\n * The most query shapes logged per interval, those that took the most\n * time. The rest are included in the interval's totals.\n */\n readonly maxReported?: number | undefined;\n\n readonly now?: (() => number) | undefined;\n\n /** Returns the index uses since the last call. See `recordIndexUsage()`. */\n readonly takeIndexUsage?: (() => Map<string, number>) | undefined;\n};\n\nclass Distribution {\n count = 0;\n sumMs = 0;\n minMs = Infinity;\n maxMs = 0;\n\n add(ms: number) {\n this.count++;\n this.sumMs += ms;\n this.minMs = Math.min(this.minMs, ms);\n this.maxMs = Math.max(this.maxMs, ms);\n }\n\n toJSON() {\n return {\n count: this.count,\n sumMs: round(this.sumMs),\n minMs: round(this.count === 0 ? 0 : this.minMs),\n maxMs: round(this.maxMs),\n };\n }\n}\n\nclass Totals {\n readonly hydrations = new Distribution();\n hydrationsAborted = 0;\n hydrationsFailed = 0;\n hydrationRowCount = 0;\n hydrationRowsRead = 0;\n readonly advances = new Distribution();\n advanceChanges = 0;\n advanceTimeouts = 0;\n /**\n * The advancements that hydrated the query again instead of pushing (all\n * of) their changes to it, by why. The hydrations are in `hydrations`.\n */\n readonly advancesAbandoned: Record<AbandonReason, number> = {\n estimate: 0,\n read: 0,\n cutoff: 0,\n };\n\n get timeMs() {\n return this.hydrations.sumMs + this.advances.sumMs;\n }\n\n addHydration({outcome, timeMs, rowCount, rowsRead}: HydrationRecord) {\n this.hydrations.add(timeMs);\n if (outcome === 'aborted') {\n this.hydrationsAborted++;\n } else if (outcome === 'failed') {\n this.hydrationsFailed++;\n }\n this.hydrationRowCount += rowCount ?? 0;\n this.hydrationRowsRead += rowsRead ?? 0;\n }\n\n addAdvance({timeMs, changes, timedOut}: AdvanceRecord) {\n this.advances.add(timeMs);\n this.advanceChanges += changes;\n if (timedOut) {\n this.advanceTimeouts++;\n }\n }\n\n toJSON() {\n return {\n timeMs: round(this.timeMs),\n hydrations: this.hydrations.toJSON(),\n hydrationsAborted: this.hydrationsAborted,\n hydrationsFailed: this.hydrationsFailed,\n hydrationRowCount: this.hydrationRowCount,\n hydrationRowsRead: this.hydrationRowsRead,\n advances: this.advances.toJSON(),\n advanceChanges: this.advanceChanges,\n advanceTimeouts: this.advanceTimeouts,\n // Only with cost-based sync, without which there are none.\n ...(Object.values(this.advancesAbandoned).some(n => n > 0) && {\n advancesAbandoned: {...this.advancesAbandoned},\n }),\n };\n }\n}\n\nclass ShapeStats extends Totals {\n readonly query: QueryIdentity;\n planWarnings: readonly PlanWarning[] | undefined;\n\n constructor(query: QueryIdentity) {\n super();\n this.query = query;\n }\n}\n\n/** A fixed-size linear-counting sketch for shapes beyond the tracking limit. */\nclass OverflowShapeCounter {\n readonly #buckets = new Uint8Array(4096);\n #occupied = 0;\n\n add(key: string): void {\n // FNV-1a gives a stable bucket without retaining the key.\n let hash = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n hash = Math.imul(hash ^ key.charCodeAt(i), 0x01000193);\n }\n const bucket = (hash >>> 0) % this.#buckets.length;\n if (this.#buckets[bucket] === 0) {\n this.#buckets[bucket] = 1;\n this.#occupied++;\n }\n }\n\n get estimatedCount(): number {\n const empty = this.#buckets.length - this.#occupied;\n return empty === 0\n ? this.#buckets.length\n : Math.round(\n -this.#buckets.length * Math.log(empty / this.#buckets.length),\n );\n }\n}\n\n/**\n * Aggregates the work done for each query shape (the query with its values\n * redacted, see {@link QueryShape}) on a worker, and periodically logs it,\n * like `pg_stat_statements` does for Postgres.\n *\n * Unlike the slow query logs, which are throttled and only written above a\n * threshold, every hydration and advancement is counted, so the logs can be\n * summed over any time range and across workers to rank queries by the time\n * they take.\n *\n * Each {@link flush} logs the interval since the previous one:\n *\n * - a `query-stats` event per query shape, for the {@link\n * QueryStatsOptions.maxReported} shapes that took the most time, and\n * - a `query-stats-summary` event with the totals of all shapes, including\n * those not reported individually, and the `indexUses`: how often the\n * process read each index of the replica, like `pg_stat_user_indexes`. An\n * index that is never read is absent. The workers of a single-process\n * zero-cache share these counts, which the first of them to flush reports.\n *\n * The events carry counts, sums, minimums and maximums, which compose across\n * intervals and workers.\n */\nexport class QueryStats {\n readonly #maxShapes: number;\n readonly #maxReported: number;\n readonly #now: () => number;\n readonly #takeIndexUsage: () => Map<string, number>;\n\n #shapes = new Map<string, ShapeStats>();\n #totals = new Totals();\n #overflowShapes = new OverflowShapeCounter();\n #intervalStart: number;\n\n constructor({\n maxShapes = 1000,\n maxReported = 100,\n now = Date.now,\n takeIndexUsage: takeIndexUsageFn = takeIndexUsage,\n }: QueryStatsOptions = {}) {\n assert(maxShapes > 0, 'maxShapes must be positive');\n assert(maxReported > 0, 'maxReported must be positive');\n this.#maxShapes = maxShapes;\n this.#maxReported = maxReported;\n this.#now = now;\n this.#takeIndexUsage = takeIndexUsageFn;\n this.#intervalStart = now();\n }\n\n recordHydration(query: QueryIdentity, record: HydrationRecord): void {\n this.#totals.addHydration(record);\n const stats = this.#stats(query);\n if (stats) {\n stats.addHydration(record);\n if (record.planWarnings !== undefined) {\n stats.planWarnings = record.planWarnings;\n }\n }\n }\n\n recordAdvance(query: QueryIdentity, record: AdvanceRecord): void {\n this.#totals.addAdvance(record);\n this.#stats(query)?.addAdvance(record);\n }\n\n /**\n * Records that an advancement abandoned the query's pipeline, to hydrate\n * the query again.\n */\n recordAbandoned(query: QueryIdentity, reason: AbandonReason): void {\n this.#totals.advancesAbandoned[reason]++;\n const stats = this.#stats(query);\n if (stats) {\n stats.advancesAbandoned[reason]++;\n }\n }\n\n #stats(query: QueryIdentity): ShapeStats | undefined {\n const key = `${query.queryName ?? ''}:${query.shape.hash}`;\n let stats = this.#shapes.get(key);\n if (stats === undefined) {\n if (this.#shapes.size >= this.#maxShapes) {\n this.#overflowShapes.add(key);\n return undefined;\n }\n stats = new ShapeStats({queryName: query.queryName, shape: query.shape});\n this.#shapes.set(key, stats);\n }\n return stats;\n }\n\n /**\n * Logs the stats of the interval since the previous flush, and starts a\n * new interval. Nothing is logged for an interval without any work.\n */\n flush(lc: LogContext): void {\n const now = this.#now();\n const intervalMs = now - this.#intervalStart;\n const shapes = this.#shapes;\n const totals = this.#totals;\n const overflowShapes = this.#overflowShapes.estimatedCount;\n const indexUses = this.#takeIndexUsage();\n this.#shapes = new Map();\n this.#totals = new Totals();\n this.#overflowShapes = new OverflowShapeCounter();\n this.#intervalStart = now;\n\n if (shapes.size === 0 && overflowShapes === 0 && indexUses.size === 0) {\n return;\n }\n const reported = [...shapes.values()]\n .toSorted((a, b) => b.timeMs - a.timeMs)\n .slice(0, this.#maxReported);\n for (const stats of reported) {\n const {queryName, shape} = stats.query;\n lc.info?.('query stats', {\n zeroEvent: 'query-stats',\n intervalMs,\n ...(queryName !== undefined && {queryName}),\n queryShape: shape.hash,\n ...stats.toJSON(),\n ...(stats.planWarnings !== undefined &&\n stats.planWarnings.length > 0 && {planWarnings: stats.planWarnings}),\n zql: shape.zql,\n });\n }\n lc.info?.('query stats summary', {\n zeroEvent: 'query-stats-summary',\n intervalMs,\n shapes: shapes.size + overflowShapes,\n shapesReported: reported.length,\n ...totals.toJSON(),\n ...(indexUses.size > 0 && {indexUses: Object.fromEntries(indexUses)}),\n });\n }\n\n /**\n * Flushes every `intervalMs` until the returned function is called, which\n * flushes one last time.\n */\n start(lc: LogContext, intervalMs: number): () => void {\n const timer = setInterval(() => this.flush(lc), intervalMs);\n timer.unref?.();\n return () => {\n clearInterval(timer);\n this.flush(lc);\n };\n }\n}\n\nfunction round(ms: number) {\n return Math.round(ms * 100) / 100;\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { promiseVoid } from "../../../../shared/src/resolved-promises.js";
2
+ import { resolver } from "../../../../shared/src/resolver.js";
2
3
  import { must } from "../../../../shared/src/must.js";
3
4
  import { cvrSchema } from "../../types/shards.js";
4
5
  import { startAsyncSpan } from "../../../../otel/src/span.js";
@@ -13,7 +14,6 @@ import { cmpVersions, versionString, versionToNullableCookie } from "./schema/ty
13
14
  import { rowRecordToRowsRow, rowsRowToRowRecord } from "./schema/cvr.js";
14
15
  import { tracer } from "./tracer.js";
15
16
  import { checkVersion } from "./cvr-store.js";
16
- import { resolver } from "@rocicorp/resolver";
17
17
  //#region ../zero-cache/src/services/view-syncer/row-record-cache.ts
18
18
  var FLUSH_TYPE_ATTRIBUTE = "flush.type";
19
19
  var RESULT_ATTRIBUTE = "result";
@@ -1 +1 @@
1
- {"version":3,"file":"row-record-cache.js","names":["Mode.READ_COMMITTED","Mode.READONLY"],"sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Resolver, resolver} from '@rocicorp/resolver';\nimport type {PendingQuery, Row} from 'postgres';\nimport {startAsyncSpan} from '../../../../otel/src/span.ts';\nimport {CustomKeyMap} from '../../../../shared/src/custom-key-map.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {runTx} from '../../db/run-transaction.ts';\nimport {TransactionPool} from '../../db/transaction-pool.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../../types/pg.ts';\nimport {rowIDString} from '../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {checkVersion, type CVRFlushStats} from './cvr-store.ts';\nimport type {CVRSnapshot} from './cvr.ts';\nimport {\n rowRecordToRowsRow,\n type RowsRow,\n rowsRowToRowRecord,\n} from './schema/cvr.ts';\nimport {\n cmpVersions,\n type CVRVersion,\n type NullableCVRVersion,\n type RowID,\n type RowRecord,\n versionString,\n versionToNullableCookie,\n} from './schema/types.ts';\nimport {tracer} from './tracer.ts';\n\nconst FLUSH_TYPE_ATTRIBUTE = 'flush.type';\nconst RESULT_ATTRIBUTE = 'result';\nconst ERROR_KIND_ATTRIBUTE = 'error.kind';\n\n/**\n * The RowRecordCache is an in-memory cache of the `cvr.rows` tables that\n * operates as both a write-through and write-back cache.\n *\n * For \"small\" CVR updates (i.e. zero or small numbers of rows) the\n * RowRecordCache operates as write-through, executing commits in\n * {@link executeRowUpdates()} before they are {@link apply}-ed to the\n * in-memory state.\n *\n * For \"large\" CVR updates (i.e. with many rows), the cache switches to a\n * write-back mode of operation, in which {@link executeRowUpdates()} is a\n * no-op, and {@link apply()} initiates a background task to flush the pending\n * row changes to the store. This allows the client poke to be completed and\n * committed on the client without waiting for the heavyweight operation of\n * committing the row records to the CVR store.\n *\n * Note that when the cache is in write-back mode, all updates become\n * write-back (i.e. asynchronously flushed) until the pending update queue is\n * fully flushed. This is required because updates must be applied in version\n * order. As with all pending work systems in zero-cache, multiple pending\n * updates are coalesced to reduce buildup of work.\n *\n * ### High level consistency\n *\n * Note that the above caching scheme only applies to the row data in `cvr.rows`\n * and corresponding `cvr.rowsVersion` tables. CVR metadata and query\n * information, on the other hand, are always committed before completing the\n * client poke. In this manner, the difference between the `version` column in\n * `cvr.instances` and the analogous column in `cvr.rowsVersion` determines\n * whether the data in the store is consistent, or whether it is awaiting a\n * pending update.\n *\n * The logic in {@link CVRStore#load()} takes this into account by loading both\n * the `cvr.instances` version and the `cvr.rowsVersion` version and checking\n * if they are in sync, waiting for a configurable delay until they are.\n *\n * ### Eventual conversion\n *\n * In the event of a continual stream of mutations (e.g. an animation-style\n * app), it is conceivable that the row record data be continually behind\n * the CVR metadata. In order to effect eventual convergence, a new view-syncer\n * signals the current view-syncer to stop updating by writing new `owner`\n * information to the `cvr.instances` row. This effectively stops the mutation\n * processing (in {@link CVRStore.#checkVersionAndOwnership}) so that the row\n * data can eventually catch up, allowing the new view-syncer to take over.\n *\n * Of course, there is the pathological situation in which a view-syncer\n * process crashes before the pending row updates are flushed. In this case,\n * the wait timeout will elapse and the CVR considered invalid.\n */\nexport class RowRecordCache {\n // The state in the #cache is always in sync with the CVR metadata\n // (i.e. cvr.instances). It may contain information that has not yet\n // been flushed to cvr.rows.\n #cache: Promise<CustomKeyMap<RowID, RowRecord>> | undefined;\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #cvrID: string;\n readonly #failService: (e: unknown) => void;\n readonly #deferredRowFlushThreshold: number;\n readonly #setTimeout: typeof setTimeout;\n\n // Write-back cache state.\n readonly #pending = new CustomKeyMap<RowID, RowRecord | null>(rowIDString);\n #pendingRowsVersion: CVRVersion | null = null;\n #flushedRowsVersion: CVRVersion | null = null;\n #flushing: Resolver<void> | null = null;\n\n readonly #cvrFlushTime = getOrCreateLatencyHistogram(\n 'sync',\n 'cvr.flush-time',\n 'Time to flush a CVR transaction. This includes both synchronous ' +\n 'and asynchronous flushes, distinguished by the flush.type attribute.',\n );\n readonly #cvrRowsFlushed = getOrCreateCounter(\n 'sync',\n 'cvr.rows-flushed',\n 'Number of (changed) rows flushed to a CVR',\n );\n readonly #cvrFlushes = getOrCreateCounter(\n 'sync',\n 'cvr.flush_attempts',\n 'CVR flush attempts, labeled by result and flush.type.',\n );\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n cvrID: string,\n failService: (e: unknown) => void,\n deferredRowFlushThreshold = 100,\n setTimeoutFn = setTimeout,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#cvrID = cvrID;\n this.#failService = failService;\n this.#deferredRowFlushThreshold = deferredRowFlushThreshold;\n this.#setTimeout = setTimeoutFn;\n }\n\n recordSyncFlushStats(stats: CVRFlushStats, elapsedMs: number) {\n this.#cvrFlushTime.recordMs(elapsedMs, {\n [FLUSH_TYPE_ATTRIBUTE]: 'sync',\n });\n if (stats.rowsDeferred === 0) {\n this.#cvrRowsFlushed.add(stats.rows);\n }\n }\n\n #recordAsyncFlushStats(rows: number, elapsedMs: number) {\n this.#cvrFlushTime.recordMs(elapsedMs, {\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n });\n this.#cvrRowsFlushed.add(rows);\n }\n\n #cvr(table: string) {\n return this.#db(`${this.#schema}.${table}`);\n }\n\n async #ensureLoaded(): Promise<CustomKeyMap<RowID, RowRecord>> {\n if (this.#cache) {\n return this.#cache;\n }\n const start = Date.now();\n const r = resolver<CustomKeyMap<RowID, RowRecord>>();\n r.promise.catch(() => {});\n // Set this.#cache immediately (before await) so that only one db\n // query is made even if there are multiple callers.\n this.#cache = r.promise;\n try {\n const cache = await startAsyncSpan(\n tracer,\n 'RowRecordCache.load',\n async span => {\n const cache: CustomKeyMap<RowID, RowRecord> = new CustomKeyMap(\n rowIDString,\n );\n for await (const rows of this.#db<RowsRow[]>`\n SELECT\n \"clientGroupID\",\n \"schema\",\n \"table\",\n \"rowKey\",\n \"rowVersion\",\n \"patchVersion\",\n \"refCounts\"\n FROM ${this.#cvr(`rows`)}\n WHERE \"clientGroupID\" = ${this.#cvrID} AND \"refCounts\" IS NOT NULL`\n // TODO(arv): Arbitrary page size\n .cursor(5000)) {\n for (const row of rows) {\n const rowRecord = rowsRowToRowRecord(row);\n cache.set(rowRecord.id, rowRecord);\n }\n }\n span.setAttribute('rows', cache.size);\n return cache;\n },\n );\n this.#lc.info?.(\n `Loaded ${cache.size} row records in ${Date.now() - start} ms`,\n );\n r.resolve(cache);\n return this.#cache;\n } catch (e) {\n r.reject(e); // Make sure the error is reflected in the cached promise\n throw e;\n }\n }\n\n getRowRecords(): Promise<ReadonlyMap<RowID, RowRecord>> {\n return this.#ensureLoaded();\n }\n\n /**\n * Applies the `rowRecords` corresponding to the `rowsVersion`\n * to the cache, indicating whether the corresponding updates\n * (generated by {@link executeRowUpdates}) were `flushed`.\n *\n * If `flushed` is false, the RowRecordCache will flush the records\n * asynchronously.\n *\n * Note that `apply()` indicates that the CVR metadata associated with\n * the `rowRecords` was successfully committed, which essentially means\n * that this process has the unconditional right (and responsibility) of\n * following up with a flush of the `rowRecords`. In particular, the\n * commit of row records are not conditioned on the version or ownership\n * columns of the `cvr.instances` row.\n */\n async apply(\n rowRecords: Map<RowID, RowRecord | null>,\n rowsVersion: CVRVersion,\n flushed: boolean,\n ): Promise<number> {\n const cache = await this.#ensureLoaded();\n for (const [id, row] of rowRecords.entries()) {\n if (row === null || row.refCounts === null) {\n cache.delete(id);\n } else {\n cache.set(id, row);\n }\n if (!flushed) {\n this.#pending.set(id, row);\n }\n }\n this.#pendingRowsVersion = rowsVersion;\n // Initiate a flush if not already flushing.\n if (!flushed && this.#flushing === null) {\n this.#flushing = resolver();\n // The #flush() method handles propagating errors to #failService.\n // Attach a rejection handler to this promise to avoid unhandled\n // rejections.\n this.#flushing.promise.catch(() => {});\n this.#setTimeout(() => this.#flush(), 0);\n }\n return cache.size;\n }\n\n async #flush() {\n const flushing = must(this.#flushing);\n try {\n while (this.#pendingRowsVersion !== this.#flushedRowsVersion) {\n const start = performance.now();\n\n const {rows, rowsVersion} = await runTx(\n this.#db,\n tx => {\n // Note: This code block is synchronous, guaranteeing that the\n // #pendingRowsVersion is consistent with the #pending rows.\n const rows = this.#pending.size;\n const rowsVersion = must(this.#pendingRowsVersion);\n // Awaiting all of the individual statements incurs too much\n // overhead. Instead, just catch and log exception(s); the outer\n // transaction will properly fail.\n void Promise.all(\n this.executeRowUpdates(tx, rowsVersion, this.#pending, 'force'),\n ).catch(e => this.#lc.error?.(`error flushing cvr rows`, e));\n\n this.#pending.clear();\n return {rows, rowsVersion};\n },\n {mode: Mode.READ_COMMITTED},\n );\n const elapsed = performance.now() - start;\n this.#lc.info?.(\n `flushed ${rows} rows@${versionString(rowsVersion)} (${elapsed} ms)`,\n );\n this.#recordAsyncFlushStats(rows, elapsed);\n this.#cvrFlushes.add(1, {\n [RESULT_ATTRIBUTE]: 'success',\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n });\n this.#flushedRowsVersion = rowsVersion;\n // Note: apply() may have called while the transaction was committing,\n // which will result in looping to commit the next #pendingRowsVersion.\n }\n this.#lc.info?.(\n `up to date rows@${versionToNullableCookie(this.#flushedRowsVersion)}`,\n );\n flushing.resolve();\n this.#flushing = null;\n } catch (e) {\n this.#lc.info?.(`row record flush failed`, e);\n this.#cvrFlushes.add(1, {\n [RESULT_ATTRIBUTE]: 'error',\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n [ERROR_KIND_ATTRIBUTE]: 'error',\n });\n flushing.reject(e);\n this.#failService(e);\n }\n }\n\n hasPendingUpdates() {\n return this.#flushing !== null;\n }\n\n /**\n * Returns a promise that resolves when all outstanding row-records\n * have been committed.\n */\n flushed(lc: LogContext): Promise<void> {\n if (this.#flushing) {\n lc.debug?.('awaiting pending row flush');\n return this.#flushing.promise;\n }\n return promiseVoid;\n }\n\n clear() {\n // Note: Only the #cache is cleared. #pending updates, on the other hand,\n // comprise canonical (i.e. already flushed) data and must be flushed\n // even if the snapshot of the present state (the #cache) is cleared.\n this.#cache = undefined;\n }\n\n async *catchupRowPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n excludeQueryHashes: string[] = [],\n ): AsyncGenerator<RowsRow[], void, undefined> {\n if (cmpVersions(afterVersion, upToCVR.version) >= 0) {\n return;\n }\n\n const startMs = Date.now();\n const start = afterVersion ? versionString(afterVersion) : '';\n const end = versionString(upToCVR.version);\n lc.debug?.(`scanning row patches for clients from ${start}`);\n\n // Before accessing the CVR db, pending row records must be flushed.\n // Note that because catchupRowPatches() is called from within the\n // view syncer lock, this flush is guaranteed to complete since no\n // new CVR updates can happen while the lock is held.\n await this.flushed(lc);\n const flushMs = Date.now() - startMs;\n\n const reader = new TransactionPool(lc, {mode: Mode.READONLY}).run(this.#db);\n try {\n // Verify that we are reading the right version of the CVR.\n await reader.processReadTask(tx =>\n checkVersion(tx, this.#schema, this.#cvrID, current),\n );\n\n const {query} = await reader.processReadTask(tx => {\n const query =\n excludeQueryHashes.length === 0\n ? tx<RowsRow[]>`SELECT\n \"clientGroupID\",\n \"schema\",\n \"table\",\n \"rowKey\",\n \"rowVersion\",\n \"patchVersion\",\n \"refCounts\"\n FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`\n : // Exclude rows that were already sent as part of query hydration.\n tx<RowsRow[]>`SELECT\n \"clientGroupID\",\n \"schema\",\n \"table\",\n \"rowKey\",\n \"rowVersion\",\n \"patchVersion\",\n \"refCounts\"\n FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}\n AND (\"refCounts\" IS NULL OR NOT \"refCounts\" ?| ${excludeQueryHashes})`;\n return {query};\n });\n\n yield* query.cursor(10000);\n } finally {\n reader.setDone();\n }\n\n const totalMs = Date.now() - startMs;\n lc.info?.(\n `finished row catchup (flush: ${flushMs} ms, total: ${totalMs} ms)`,\n );\n }\n\n executeRowUpdates(\n tx: PostgresTransaction,\n version: CVRVersion,\n rowUpdates: Map<RowID, RowRecord | null>,\n mode: 'allow-defer' | 'force',\n lc = this.#lc,\n ): PendingQuery<Row[]>[] {\n if (\n mode === 'allow-defer' &&\n // defer if pending rows are being flushed\n (this.#flushing !== null ||\n // or if the new batch is above the limit.\n rowUpdates.size > this.#deferredRowFlushThreshold)\n ) {\n return [];\n }\n const rowsVersion = {\n clientGroupID: this.#cvrID,\n version: versionString(version),\n };\n const pending: PendingQuery<Row[]>[] = [\n tx`INSERT INTO ${this.#cvr('rowsVersion')} ${tx(rowsVersion)}\n ON CONFLICT (\"clientGroupID\") \n DO UPDATE SET ${tx(rowsVersion)}`,\n ];\n\n const rowRecordRows: RowsRow[] = [];\n for (const [id, row] of rowUpdates.entries()) {\n if (row === null) {\n pending.push(\n tx`\n DELETE FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"schema\" = ${id.schema}\n AND \"table\" = ${id.table}\n AND \"rowKey\" = ${id.rowKey}\n `,\n );\n } else {\n rowRecordRows.push(rowRecordToRowsRow(this.#cvrID, row));\n }\n }\n if (rowRecordRows.length) {\n pending.push(\n tx`\n INSERT INTO ${this.#cvr('rows')}(\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n ) SELECT\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n FROM json_to_recordset(${rowRecordRows}) AS x(\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT,\n \"patchVersion\" TEXT,\n \"refCounts\" JSONB\n ) ON CONFLICT (\"clientGroupID\", \"schema\", \"table\", \"rowKey\")\n DO UPDATE SET \"rowVersion\" = excluded.\"rowVersion\",\n \"patchVersion\" = excluded.\"patchVersion\",\n \"refCounts\" = excluded.\"refCounts\"\n `,\n );\n lc.info?.(\n `flushing ${rowUpdates.size} rows (${rowRecordRows.length} inserts, ${\n rowUpdates.size - rowRecordRows.length\n } deletes)`,\n );\n }\n return pending;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"row-record-cache.js","names":["Mode.READ_COMMITTED","Mode.READONLY"],"sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {PendingQuery, Row} from 'postgres';\nimport {startAsyncSpan} from '../../../../otel/src/span.ts';\nimport {CustomKeyMap} from '../../../../shared/src/custom-key-map.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {type Resolver, resolver} from '../../../../shared/src/resolver.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {runTx} from '../../db/run-transaction.ts';\nimport {TransactionPool} from '../../db/transaction-pool.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../../types/pg.ts';\nimport {rowIDString} from '../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {checkVersion, type CVRFlushStats} from './cvr-store.ts';\nimport type {CVRSnapshot} from './cvr.ts';\nimport {\n rowRecordToRowsRow,\n type RowsRow,\n rowsRowToRowRecord,\n} from './schema/cvr.ts';\nimport {\n cmpVersions,\n type CVRVersion,\n type NullableCVRVersion,\n type RowID,\n type RowRecord,\n versionString,\n versionToNullableCookie,\n} from './schema/types.ts';\nimport {tracer} from './tracer.ts';\n\nconst FLUSH_TYPE_ATTRIBUTE = 'flush.type';\nconst RESULT_ATTRIBUTE = 'result';\nconst ERROR_KIND_ATTRIBUTE = 'error.kind';\n\n/**\n * The RowRecordCache is an in-memory cache of the `cvr.rows` tables that\n * operates as both a write-through and write-back cache.\n *\n * For \"small\" CVR updates (i.e. zero or small numbers of rows) the\n * RowRecordCache operates as write-through, executing commits in\n * {@link executeRowUpdates()} before they are {@link apply}-ed to the\n * in-memory state.\n *\n * For \"large\" CVR updates (i.e. with many rows), the cache switches to a\n * write-back mode of operation, in which {@link executeRowUpdates()} is a\n * no-op, and {@link apply()} initiates a background task to flush the pending\n * row changes to the store. This allows the client poke to be completed and\n * committed on the client without waiting for the heavyweight operation of\n * committing the row records to the CVR store.\n *\n * Note that when the cache is in write-back mode, all updates become\n * write-back (i.e. asynchronously flushed) until the pending update queue is\n * fully flushed. This is required because updates must be applied in version\n * order. As with all pending work systems in zero-cache, multiple pending\n * updates are coalesced to reduce buildup of work.\n *\n * ### High level consistency\n *\n * Note that the above caching scheme only applies to the row data in `cvr.rows`\n * and corresponding `cvr.rowsVersion` tables. CVR metadata and query\n * information, on the other hand, are always committed before completing the\n * client poke. In this manner, the difference between the `version` column in\n * `cvr.instances` and the analogous column in `cvr.rowsVersion` determines\n * whether the data in the store is consistent, or whether it is awaiting a\n * pending update.\n *\n * The logic in {@link CVRStore#load()} takes this into account by loading both\n * the `cvr.instances` version and the `cvr.rowsVersion` version and checking\n * if they are in sync, waiting for a configurable delay until they are.\n *\n * ### Eventual conversion\n *\n * In the event of a continual stream of mutations (e.g. an animation-style\n * app), it is conceivable that the row record data be continually behind\n * the CVR metadata. In order to effect eventual convergence, a new view-syncer\n * signals the current view-syncer to stop updating by writing new `owner`\n * information to the `cvr.instances` row. This effectively stops the mutation\n * processing (in {@link CVRStore.#checkVersionAndOwnership}) so that the row\n * data can eventually catch up, allowing the new view-syncer to take over.\n *\n * Of course, there is the pathological situation in which a view-syncer\n * process crashes before the pending row updates are flushed. In this case,\n * the wait timeout will elapse and the CVR considered invalid.\n */\nexport class RowRecordCache {\n // The state in the #cache is always in sync with the CVR metadata\n // (i.e. cvr.instances). It may contain information that has not yet\n // been flushed to cvr.rows.\n #cache: Promise<CustomKeyMap<RowID, RowRecord>> | undefined;\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #cvrID: string;\n readonly #failService: (e: unknown) => void;\n readonly #deferredRowFlushThreshold: number;\n readonly #setTimeout: typeof setTimeout;\n\n // Write-back cache state.\n readonly #pending = new CustomKeyMap<RowID, RowRecord | null>(rowIDString);\n #pendingRowsVersion: CVRVersion | null = null;\n #flushedRowsVersion: CVRVersion | null = null;\n #flushing: Resolver<void> | null = null;\n\n readonly #cvrFlushTime = getOrCreateLatencyHistogram(\n 'sync',\n 'cvr.flush-time',\n 'Time to flush a CVR transaction. This includes both synchronous ' +\n 'and asynchronous flushes, distinguished by the flush.type attribute.',\n );\n readonly #cvrRowsFlushed = getOrCreateCounter(\n 'sync',\n 'cvr.rows-flushed',\n 'Number of (changed) rows flushed to a CVR',\n );\n readonly #cvrFlushes = getOrCreateCounter(\n 'sync',\n 'cvr.flush_attempts',\n 'CVR flush attempts, labeled by result and flush.type.',\n );\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n cvrID: string,\n failService: (e: unknown) => void,\n deferredRowFlushThreshold = 100,\n setTimeoutFn = setTimeout,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#cvrID = cvrID;\n this.#failService = failService;\n this.#deferredRowFlushThreshold = deferredRowFlushThreshold;\n this.#setTimeout = setTimeoutFn;\n }\n\n recordSyncFlushStats(stats: CVRFlushStats, elapsedMs: number) {\n this.#cvrFlushTime.recordMs(elapsedMs, {\n [FLUSH_TYPE_ATTRIBUTE]: 'sync',\n });\n if (stats.rowsDeferred === 0) {\n this.#cvrRowsFlushed.add(stats.rows);\n }\n }\n\n #recordAsyncFlushStats(rows: number, elapsedMs: number) {\n this.#cvrFlushTime.recordMs(elapsedMs, {\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n });\n this.#cvrRowsFlushed.add(rows);\n }\n\n #cvr(table: string) {\n return this.#db(`${this.#schema}.${table}`);\n }\n\n async #ensureLoaded(): Promise<CustomKeyMap<RowID, RowRecord>> {\n if (this.#cache) {\n return this.#cache;\n }\n const start = Date.now();\n const r = resolver<CustomKeyMap<RowID, RowRecord>>();\n r.promise.catch(() => {});\n // Set this.#cache immediately (before await) so that only one db\n // query is made even if there are multiple callers.\n this.#cache = r.promise;\n try {\n const cache = await startAsyncSpan(\n tracer,\n 'RowRecordCache.load',\n async span => {\n const cache: CustomKeyMap<RowID, RowRecord> = new CustomKeyMap(\n rowIDString,\n );\n for await (const rows of this.#db<RowsRow[]>`\n SELECT\n \"clientGroupID\",\n \"schema\",\n \"table\",\n \"rowKey\",\n \"rowVersion\",\n \"patchVersion\",\n \"refCounts\"\n FROM ${this.#cvr(`rows`)}\n WHERE \"clientGroupID\" = ${this.#cvrID} AND \"refCounts\" IS NOT NULL`\n // TODO(arv): Arbitrary page size\n .cursor(5000)) {\n for (const row of rows) {\n const rowRecord = rowsRowToRowRecord(row);\n cache.set(rowRecord.id, rowRecord);\n }\n }\n span.setAttribute('rows', cache.size);\n return cache;\n },\n );\n this.#lc.info?.(\n `Loaded ${cache.size} row records in ${Date.now() - start} ms`,\n );\n r.resolve(cache);\n return this.#cache;\n } catch (e) {\n r.reject(e); // Make sure the error is reflected in the cached promise\n throw e;\n }\n }\n\n getRowRecords(): Promise<ReadonlyMap<RowID, RowRecord>> {\n return this.#ensureLoaded();\n }\n\n /**\n * Applies the `rowRecords` corresponding to the `rowsVersion`\n * to the cache, indicating whether the corresponding updates\n * (generated by {@link executeRowUpdates}) were `flushed`.\n *\n * If `flushed` is false, the RowRecordCache will flush the records\n * asynchronously.\n *\n * Note that `apply()` indicates that the CVR metadata associated with\n * the `rowRecords` was successfully committed, which essentially means\n * that this process has the unconditional right (and responsibility) of\n * following up with a flush of the `rowRecords`. In particular, the\n * commit of row records are not conditioned on the version or ownership\n * columns of the `cvr.instances` row.\n */\n async apply(\n rowRecords: Map<RowID, RowRecord | null>,\n rowsVersion: CVRVersion,\n flushed: boolean,\n ): Promise<number> {\n const cache = await this.#ensureLoaded();\n for (const [id, row] of rowRecords.entries()) {\n if (row === null || row.refCounts === null) {\n cache.delete(id);\n } else {\n cache.set(id, row);\n }\n if (!flushed) {\n this.#pending.set(id, row);\n }\n }\n this.#pendingRowsVersion = rowsVersion;\n // Initiate a flush if not already flushing.\n if (!flushed && this.#flushing === null) {\n this.#flushing = resolver();\n // The #flush() method handles propagating errors to #failService.\n // Attach a rejection handler to this promise to avoid unhandled\n // rejections.\n this.#flushing.promise.catch(() => {});\n this.#setTimeout(() => this.#flush(), 0);\n }\n return cache.size;\n }\n\n async #flush() {\n const flushing = must(this.#flushing);\n try {\n while (this.#pendingRowsVersion !== this.#flushedRowsVersion) {\n const start = performance.now();\n\n const {rows, rowsVersion} = await runTx(\n this.#db,\n tx => {\n // Note: This code block is synchronous, guaranteeing that the\n // #pendingRowsVersion is consistent with the #pending rows.\n const rows = this.#pending.size;\n const rowsVersion = must(this.#pendingRowsVersion);\n // Awaiting all of the individual statements incurs too much\n // overhead. Instead, just catch and log exception(s); the outer\n // transaction will properly fail.\n void Promise.all(\n this.executeRowUpdates(tx, rowsVersion, this.#pending, 'force'),\n ).catch(e => this.#lc.error?.(`error flushing cvr rows`, e));\n\n this.#pending.clear();\n return {rows, rowsVersion};\n },\n {mode: Mode.READ_COMMITTED},\n );\n const elapsed = performance.now() - start;\n this.#lc.info?.(\n `flushed ${rows} rows@${versionString(rowsVersion)} (${elapsed} ms)`,\n );\n this.#recordAsyncFlushStats(rows, elapsed);\n this.#cvrFlushes.add(1, {\n [RESULT_ATTRIBUTE]: 'success',\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n });\n this.#flushedRowsVersion = rowsVersion;\n // Note: apply() may have called while the transaction was committing,\n // which will result in looping to commit the next #pendingRowsVersion.\n }\n this.#lc.info?.(\n `up to date rows@${versionToNullableCookie(this.#flushedRowsVersion)}`,\n );\n flushing.resolve();\n this.#flushing = null;\n } catch (e) {\n this.#lc.info?.(`row record flush failed`, e);\n this.#cvrFlushes.add(1, {\n [RESULT_ATTRIBUTE]: 'error',\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n [ERROR_KIND_ATTRIBUTE]: 'error',\n });\n flushing.reject(e);\n this.#failService(e);\n }\n }\n\n hasPendingUpdates() {\n return this.#flushing !== null;\n }\n\n /**\n * Returns a promise that resolves when all outstanding row-records\n * have been committed.\n */\n flushed(lc: LogContext): Promise<void> {\n if (this.#flushing) {\n lc.debug?.('awaiting pending row flush');\n return this.#flushing.promise;\n }\n return promiseVoid;\n }\n\n clear() {\n // Note: Only the #cache is cleared. #pending updates, on the other hand,\n // comprise canonical (i.e. already flushed) data and must be flushed\n // even if the snapshot of the present state (the #cache) is cleared.\n this.#cache = undefined;\n }\n\n async *catchupRowPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n excludeQueryHashes: string[] = [],\n ): AsyncGenerator<RowsRow[], void, undefined> {\n if (cmpVersions(afterVersion, upToCVR.version) >= 0) {\n return;\n }\n\n const startMs = Date.now();\n const start = afterVersion ? versionString(afterVersion) : '';\n const end = versionString(upToCVR.version);\n lc.debug?.(`scanning row patches for clients from ${start}`);\n\n // Before accessing the CVR db, pending row records must be flushed.\n // Note that because catchupRowPatches() is called from within the\n // view syncer lock, this flush is guaranteed to complete since no\n // new CVR updates can happen while the lock is held.\n await this.flushed(lc);\n const flushMs = Date.now() - startMs;\n\n const reader = new TransactionPool(lc, {mode: Mode.READONLY}).run(this.#db);\n try {\n // Verify that we are reading the right version of the CVR.\n await reader.processReadTask(tx =>\n checkVersion(tx, this.#schema, this.#cvrID, current),\n );\n\n const {query} = await reader.processReadTask(tx => {\n const query =\n excludeQueryHashes.length === 0\n ? tx<RowsRow[]>`SELECT\n \"clientGroupID\",\n \"schema\",\n \"table\",\n \"rowKey\",\n \"rowVersion\",\n \"patchVersion\",\n \"refCounts\"\n FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`\n : // Exclude rows that were already sent as part of query hydration.\n tx<RowsRow[]>`SELECT\n \"clientGroupID\",\n \"schema\",\n \"table\",\n \"rowKey\",\n \"rowVersion\",\n \"patchVersion\",\n \"refCounts\"\n FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}\n AND (\"refCounts\" IS NULL OR NOT \"refCounts\" ?| ${excludeQueryHashes})`;\n return {query};\n });\n\n yield* query.cursor(10000);\n } finally {\n reader.setDone();\n }\n\n const totalMs = Date.now() - startMs;\n lc.info?.(\n `finished row catchup (flush: ${flushMs} ms, total: ${totalMs} ms)`,\n );\n }\n\n executeRowUpdates(\n tx: PostgresTransaction,\n version: CVRVersion,\n rowUpdates: Map<RowID, RowRecord | null>,\n mode: 'allow-defer' | 'force',\n lc = this.#lc,\n ): PendingQuery<Row[]>[] {\n if (\n mode === 'allow-defer' &&\n // defer if pending rows are being flushed\n (this.#flushing !== null ||\n // or if the new batch is above the limit.\n rowUpdates.size > this.#deferredRowFlushThreshold)\n ) {\n return [];\n }\n const rowsVersion = {\n clientGroupID: this.#cvrID,\n version: versionString(version),\n };\n const pending: PendingQuery<Row[]>[] = [\n tx`INSERT INTO ${this.#cvr('rowsVersion')} ${tx(rowsVersion)}\n ON CONFLICT (\"clientGroupID\") \n DO UPDATE SET ${tx(rowsVersion)}`,\n ];\n\n const rowRecordRows: RowsRow[] = [];\n for (const [id, row] of rowUpdates.entries()) {\n if (row === null) {\n pending.push(\n tx`\n DELETE FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"schema\" = ${id.schema}\n AND \"table\" = ${id.table}\n AND \"rowKey\" = ${id.rowKey}\n `,\n );\n } else {\n rowRecordRows.push(rowRecordToRowsRow(this.#cvrID, row));\n }\n }\n if (rowRecordRows.length) {\n pending.push(\n tx`\n INSERT INTO ${this.#cvr('rows')}(\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n ) SELECT\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n FROM json_to_recordset(${rowRecordRows}) AS x(\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT,\n \"patchVersion\" TEXT,\n \"refCounts\" JSONB\n ) ON CONFLICT (\"clientGroupID\", \"schema\", \"table\", \"rowKey\")\n DO UPDATE SET \"rowVersion\" = excluded.\"rowVersion\",\n \"patchVersion\" = excluded.\"patchVersion\",\n \"refCounts\" = excluded.\"refCounts\"\n `,\n );\n lc.info?.(\n `flushing ${rowUpdates.size} rows (${rowRecordRows.length} inserts, ${\n rowUpdates.size - rowRecordRows.length\n } deletes)`,\n );\n }\n return pending;\n }\n}\n"],"mappings":"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"}
@@ -202,6 +202,6 @@ async function setupCVRTables(lc, db, shard) {
202
202
  await db.unsafe(createTables(shard));
203
203
  }
204
204
  //#endregion
205
- export { createRowsVersionTable, rowRecordToRowsRow, rowsRowToRowID, rowsRowToRowRecord, setupCVRTables };
205
+ export { createRowsVersionTable, rowRecordToRowsRow, rowsRowToRowRecord, setupCVRTables };
206
206
 
207
207
  //# sourceMappingURL=cvr.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cvr.js","names":[],"sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/cvr.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {ident} from 'pg-format';\nimport type postgres from 'postgres';\nimport {\n type JSONObject,\n type JSONValue,\n stringify,\n} from '../../../../../shared/src/bigint-json.ts';\nimport type {ReadonlyJSONValue} from '../../../../../shared/src/json.ts';\nimport {stringCompare} from '../../../../../shared/src/string-compare.ts';\nimport type {ClientSchema} from '../../../../../zero-protocol/src/client-schema.ts';\nimport {normalizedKeyOrder, type RowKey} from '../../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../../types/shards.ts';\nimport type {TTLClock} from '../ttl-clock.ts';\nimport {\n type RowID,\n type RowRecord,\n versionFromString,\n versionString,\n} from './types.ts';\n\n// For readability in the sql statements.\nfunction schema(shard: ShardID) {\n return ident(cvrSchema(shard));\n}\n\nfunction createSchema(shard: ShardID) {\n return `CREATE SCHEMA IF NOT EXISTS ${schema(shard)};`;\n}\n\nexport type InstancesRow = {\n clientGroupID: string;\n version: string;\n lastActive: number;\n ttlClock: TTLClock;\n replicaVersion: string | null;\n owner: string | null;\n grantedAt: number | null;\n clientSchema: ClientSchema | null;\n profileID: string | null;\n};\n\nfunction createInstancesTable(shard: ShardID) {\n return /*sql*/ `\nCREATE TABLE ${schema(shard)}.instances (\n \"clientGroupID\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL, -- Sortable representation of CVRVersion, e.g. \"5nbqa2w:09\"\n \"lastActive\" TIMESTAMPTZ NOT NULL, -- For garbage collection\n \"ttlClock\" DOUBLE PRECISION NOT NULL DEFAULT 0, -- The ttl clock gets \"paused\" when disconnected.\n \"replicaVersion\" TEXT, -- Identifies the replica (i.e. initial-sync point) from which the CVR data comes.\n \"owner\" TEXT, -- The ID of the task / server that has been granted ownership of the CVR.\n \"grantedAt\" TIMESTAMPTZ, -- The time at which the current owner was last granted ownership (most recent connection time).\n \"clientSchema\" JSONB, -- ClientSchema of the client group\n \"profileID\" TEXT, -- Stable profile id (\"p...\"), falling back to the clientGroupID (\"cg{clientGroupID}\") for old clients\n \"deleted\" BOOL DEFAULT FALSE -- Tombstone column for deleted CVRs; instances rows are kept longer for usage stats\n);\n\n-- For garbage collection.\nCREATE INDEX instances_last_active\n ON ${schema(shard)}.instances (\"lastActive\") WHERE NOT \"deleted\";\nCREATE INDEX tombstones_last_active\n ON ${schema(shard)}.instances (\"lastActive\") WHERE \"deleted\";\n\n-- For usage stats; the composite index allows a \n-- SELECT COUNT(DISTINCT(\"profileID\")) query to be answered by\n-- an index scan without additional table lookups.\nCREATE INDEX profile_ids_last_active ON ${schema(shard)}.instances (\"lastActive\", \"profileID\")\n WHERE \"profileID\" IS NOT NULL;\n`;\n}\n\nexport function compareInstancesRows(a: InstancesRow, b: InstancesRow) {\n return stringCompare(a.clientGroupID, b.clientGroupID);\n}\n\nexport type ClientsRow = {\n clientGroupID: string;\n clientID: string;\n};\n\nfunction createClientsTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.clients (\n \"clientGroupID\" TEXT,\n \"clientID\" TEXT,\n\n PRIMARY KEY (\"clientGroupID\", \"clientID\"),\n\n CONSTRAINT fk_clients_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.instances(\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n`;\n}\nexport function compareClientsRows(a: ClientsRow, b: ClientsRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n return stringCompare(a.clientID, b.clientID);\n}\n\nexport type QueriesRow = {\n clientGroupID: string;\n queryHash: string;\n // This is the client AST _AFTER_ applying server name transformations.\n clientAST: JSONValue | null;\n queryName: string | null;\n queryArgs: readonly ReadonlyJSONValue[] | null;\n patchVersion: string | null;\n transformationHash: string | null;\n transformationVersion: string | null;\n internal: boolean | null;\n deleted: boolean | null;\n // Optional because (a) old DBs migrated to v17 will start with NULL, and\n // (b) test fixtures predate this column.\n rowSetSignature?: string | null;\n};\n\nfunction createQueriesTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.queries (\n \"clientGroupID\" TEXT,\n \"queryHash\" TEXT, -- this is the hash of the client query AST\n \"clientAST\" JSONB, -- this is nullable as custom queries will not persist an AST\n \"queryName\" TEXT, -- the name of the query if it is a custom query\n \"queryArgs\" JSON, -- the arguments of the query if it is a custom query\n \"patchVersion\" TEXT, -- NULL if only desired but not yet \"got\"\n \"transformationHash\" TEXT,\n \"transformationVersion\" TEXT,\n \"internal\" BOOL, -- If true, no need to track / send patches\n \"deleted\" BOOL, -- put vs del \"got\" query\n \"rowSetSignature\" TEXT, -- Hex XOR of h64([schema,table,rowKey]) for rows in this query (drift detection for Cap)\n\n PRIMARY KEY (\"clientGroupID\", \"queryHash\"),\n\n CONSTRAINT fk_queries_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.instances(\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX queries_patch_version \n ON ${schema(shard)}.queries (\"patchVersion\" NULLS FIRST);\n`;\n}\n\nexport function compareQueriesRows(a: QueriesRow, b: QueriesRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n return stringCompare(a.queryHash, b.queryHash);\n}\n\nexport type DesiresRow = {\n clientGroupID: string;\n clientID: string;\n queryHash: string;\n patchVersion: string;\n deleted: boolean | null;\n ttl: number | null;\n inactivatedAt: TTLClock | null;\n};\n\nfunction createDesiresTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.desires (\n \"clientGroupID\" TEXT,\n \"clientID\" TEXT,\n \"queryHash\" TEXT,\n \"patchVersion\" TEXT NOT NULL,\n \"deleted\" BOOL, -- put vs del \"desired\" query\n \"ttl\" INTERVAL, -- DEPRECATED: Use ttlMs instead. Time to live for this client\n \"ttlMs\" DOUBLE PRECISION, -- Time to live in milliseconds\n \"inactivatedAt\" TIMESTAMPTZ, -- DEPRECATED: Use inactivatedAtMs instead. Time at which this row was inactivated\n \"inactivatedAtMs\" DOUBLE PRECISION, -- Time at which this row was inactivated (milliseconds since client group start)\n\n PRIMARY KEY (\"clientGroupID\", \"clientID\", \"queryHash\"),\n\n CONSTRAINT fk_desires_query\n FOREIGN KEY(\"clientGroupID\", \"queryHash\")\n REFERENCES ${ident(cvrSchema(shard))}.queries(\"clientGroupID\", \"queryHash\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX desires_patch_version\n ON ${schema(shard)}.desires (\"patchVersion\");\n\nCREATE INDEX desires_inactivated_at\n ON ${schema(shard)}.desires (\"inactivatedAt\");\n`;\n}\n\nexport function compareDesiresRows(a: DesiresRow, b: DesiresRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n const clientIDComp = stringCompare(a.clientID, b.clientID);\n if (clientIDComp !== 0) {\n return clientIDComp;\n }\n return stringCompare(a.queryHash, b.queryHash);\n}\n\nexport type RowsRow = {\n clientGroupID: string;\n schema: string;\n table: string;\n rowKey: JSONObject;\n rowVersion: string;\n patchVersion: string;\n refCounts: {[queryHash: string]: number} | null;\n};\n\nexport function rowsRowToRowID(rowsRow: RowsRow): RowID {\n return {\n schema: rowsRow.schema,\n table: rowsRow.table,\n rowKey: rowsRow.rowKey as Record<string, JSONValue>,\n };\n}\n\nexport function rowsRowToRowRecord(rowsRow: RowsRow): RowRecord {\n return {\n id: rowsRowToRowID(rowsRow),\n rowVersion: rowsRow.rowVersion,\n patchVersion: versionFromString(rowsRow.patchVersion),\n refCounts: rowsRow.refCounts,\n };\n}\n\nexport function rowRecordToRowsRow(\n clientGroupID: string,\n rowRecord: RowRecord,\n): RowsRow {\n return {\n clientGroupID,\n schema: rowRecord.id.schema,\n table: rowRecord.id.table,\n rowKey: rowRecord.id.rowKey as Record<string, JSONValue>,\n rowVersion: rowRecord.rowVersion,\n patchVersion: versionString(rowRecord.patchVersion),\n refCounts: rowRecord.refCounts,\n };\n}\n\nexport function compareRowsRows(a: RowsRow, b: RowsRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n const schemaComp = stringCompare(a.schema, b.schema);\n if (schemaComp !== 0) {\n return schemaComp;\n }\n const tableComp = stringCompare(b.table, b.table);\n if (tableComp !== 0) {\n return tableComp;\n }\n return stringCompare(\n stringifySorted(a.rowKey as RowKey),\n stringifySorted(b.rowKey as RowKey),\n );\n}\n\n/**\n * The version of the data in the `cvr.rows` table. This may lag\n * `version` in `cvr.instances` but eventually catches up, modulo\n * exceptional circumstances like a server crash.\n *\n * The `rowsVersion` is tracked in a separate table (as opposed to\n * a column in the `cvr.instances` table) so that general `cvr` updates\n * and `row` updates can be executed independently without serialization\n * conflicts.\n *\n * Note: Although `clientGroupID` logically references the same column in\n * `cvr.instances`, a FOREIGN KEY constraint must not be declared as the\n * `cvr.rows` TABLE needs to be updated without affecting the\n * `SELECT ... FOR UPDATE` lock when `cvr.instances` is updated.\n */\nexport function createRowsVersionTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.\"rowsVersion\" (\n \"clientGroupID\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL\n);\n`;\n}\n\n/**\n * CVR `rows` are updated asynchronously from the CVR metadata\n * (i.e. `instances`). The `rowsVersion` table is updated atomically with\n * updates to the `rows` data.\n */\nfunction createRowsTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.rows (\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT NOT NULL,\n \"patchVersion\" TEXT NOT NULL,\n \"refCounts\" JSONB, -- {[queryHash: string]: number}, NULL for tombstone\n\n PRIMARY KEY (\"clientGroupID\", \"schema\", \"table\", \"rowKey\"),\n\n CONSTRAINT fk_rows_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.\"rowsVersion\" (\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX row_patch_version \n ON ${schema(shard)}.rows (\"patchVersion\");\n\n-- For listing rows returned by one or more query hashes. e.g.\n-- SELECT * FROM cvr_shard.rows WHERE \"refCounts\" ?| array[...queryHashes...];\nCREATE INDEX row_ref_counts ON ${schema(shard)}.rows \n USING GIN (\"refCounts\");\n`;\n}\n\nexport type RowsVersionRow = {\n clientGroupID: string;\n version: string;\n};\n\nfunction createTables(shard: ShardID) {\n return (\n createSchema(shard) +\n createInstancesTable(shard) +\n createClientsTable(shard) +\n createQueriesTable(shard) +\n createDesiresTable(shard) +\n createRowsVersionTable(shard) +\n createRowsTable(shard)\n );\n}\n\nexport async function setupCVRTables(\n lc: LogContext,\n db: postgres.TransactionSql,\n shard: ShardID,\n) {\n lc.info?.(`Setting up CVR tables`);\n await db.unsafe(createTables(shard));\n}\n\nfunction stringifySorted(r: RowKey) {\n return stringify(normalizedKeyOrder(r));\n}\n"],"mappings":";;;;;;AAsBA,SAAS,OAAO,OAAgB;CAC9B,OAAO,MAAM,UAAU,KAAK,CAAC;AAC/B;AAEA,SAAS,aAAa,OAAgB;CACpC,OAAO,+BAA+B,OAAO,KAAK,EAAE;AACtD;AAcA,SAAS,qBAAqB,OAAgB;CAC5C,OAAe;eACF,OAAO,KAAK,EAAE;;;;;;;;;;;;;;;OAetB,OAAO,KAAK,EAAE;;OAEd,OAAO,KAAK,EAAE;;;;;0CAKqB,OAAO,KAAK,EAAE;;;AAGxD;AAWA,SAAS,mBAAmB,OAAgB;CAC1C,OAAO;eACM,OAAO,KAAK,EAAE;;;;;;;;iBAQZ,OAAO,KAAK,EAAE;;;;;AAK/B;AA0BA,SAAS,mBAAmB,OAAgB;CAC1C,OAAO;eACM,OAAO,KAAK,EAAE;;;;;;;;;;;;;;;;;iBAiBZ,OAAO,KAAK,EAAE;;;;;;OAMxB,OAAO,KAAK,EAAE;;AAErB;AAoBA,SAAS,mBAAmB,OAAgB;CAC1C,OAAO;eACM,OAAO,KAAK,EAAE;;;;;;;;;;;;;;;iBAeZ,MAAM,UAAU,KAAK,CAAC,EAAE;;;;;;OAMlC,OAAO,KAAK,EAAE;;;OAGd,OAAO,KAAK,EAAE;;AAErB;AAwBA,SAAgB,eAAe,SAAyB;CACtD,OAAO;EACL,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,QAAQ,QAAQ;CAClB;AACF;AAEA,SAAgB,mBAAmB,SAA6B;CAC9D,OAAO;EACL,IAAI,eAAe,OAAO;EAC1B,YAAY,QAAQ;EACpB,cAAc,kBAAkB,QAAQ,YAAY;EACpD,WAAW,QAAQ;CACrB;AACF;AAEA,SAAgB,mBACd,eACA,WACS;CACT,OAAO;EACL;EACA,QAAQ,UAAU,GAAG;EACrB,OAAO,UAAU,GAAG;EACpB,QAAQ,UAAU,GAAG;EACrB,YAAY,UAAU;EACtB,cAAc,cAAc,UAAU,YAAY;EAClD,WAAW,UAAU;CACvB;AACF;;;;;;;;;;;;;;;;AAoCA,SAAgB,uBAAuB,OAAgB;CACrD,OAAO;eACM,OAAO,KAAK,EAAE;;;;;AAK7B;;;;;;AAOA,SAAS,gBAAgB,OAAgB;CACvC,OAAO;eACM,OAAO,KAAK,EAAE;;;;;;;;;;;;;iBAaZ,OAAO,KAAK,EAAE;;;;;;OAMxB,OAAO,KAAK,EAAE;;;;iCAIY,OAAO,KAAK,EAAE;;;AAG/C;AAOA,SAAS,aAAa,OAAgB;CACpC,OACE,aAAa,KAAK,IAClB,qBAAqB,KAAK,IAC1B,mBAAmB,KAAK,IACxB,mBAAmB,KAAK,IACxB,mBAAmB,KAAK,IACxB,uBAAuB,KAAK,IAC5B,gBAAgB,KAAK;AAEzB;AAEA,eAAsB,eACpB,IACA,IACA,OACA;CACA,GAAG,OAAO,uBAAuB;CACjC,MAAM,GAAG,OAAO,aAAa,KAAK,CAAC;AACrC"}
1
+ {"version":3,"file":"cvr.js","names":[],"sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/cvr.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {ident} from 'pg-format';\nimport type postgres from 'postgres';\nimport {\n type JSONObject,\n type JSONValue,\n stringify,\n} from '../../../../../shared/src/bigint-json.ts';\nimport type {ReadonlyJSONValue} from '../../../../../shared/src/json.ts';\nimport {stringCompare} from '../../../../../shared/src/string-compare.ts';\nimport type {ClientSchema} from '../../../../../zero-protocol/src/client-schema.ts';\nimport {normalizedKeyOrder, type RowKey} from '../../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../../types/shards.ts';\nimport type {TTLClock} from '../ttl-clock.ts';\nimport {\n type RowID,\n type RowRecord,\n versionFromString,\n versionString,\n} from './types.ts';\n\n// For readability in the sql statements.\nfunction schema(shard: ShardID) {\n return ident(cvrSchema(shard));\n}\n\nfunction createSchema(shard: ShardID) {\n return `CREATE SCHEMA IF NOT EXISTS ${schema(shard)};`;\n}\n\nexport type InstancesRow = {\n clientGroupID: string;\n version: string;\n lastActive: number;\n ttlClock: TTLClock;\n replicaVersion: string | null;\n owner: string | null;\n grantedAt: number | null;\n clientSchema: ClientSchema | null;\n profileID: string | null;\n};\n\nfunction createInstancesTable(shard: ShardID) {\n return /*sql*/ `\nCREATE TABLE ${schema(shard)}.instances (\n \"clientGroupID\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL, -- Sortable representation of CVRVersion, e.g. \"5nbqa2w:09\"\n \"lastActive\" TIMESTAMPTZ NOT NULL, -- For garbage collection\n \"ttlClock\" DOUBLE PRECISION NOT NULL DEFAULT 0, -- The ttl clock gets \"paused\" when disconnected.\n \"replicaVersion\" TEXT, -- Identifies the replica (i.e. initial-sync point) from which the CVR data comes.\n \"owner\" TEXT, -- The ID of the task / server that has been granted ownership of the CVR.\n \"grantedAt\" TIMESTAMPTZ, -- The time at which the current owner was last granted ownership (most recent connection time).\n \"clientSchema\" JSONB, -- ClientSchema of the client group\n \"profileID\" TEXT, -- Stable profile id (\"p...\"), falling back to the clientGroupID (\"cg{clientGroupID}\") for old clients\n \"deleted\" BOOL DEFAULT FALSE -- Tombstone column for deleted CVRs; instances rows are kept longer for usage stats\n);\n\n-- For garbage collection.\nCREATE INDEX instances_last_active\n ON ${schema(shard)}.instances (\"lastActive\") WHERE NOT \"deleted\";\nCREATE INDEX tombstones_last_active\n ON ${schema(shard)}.instances (\"lastActive\") WHERE \"deleted\";\n\n-- For usage stats; the composite index allows a \n-- SELECT COUNT(DISTINCT(\"profileID\")) query to be answered by\n-- an index scan without additional table lookups.\nCREATE INDEX profile_ids_last_active ON ${schema(shard)}.instances (\"lastActive\", \"profileID\")\n WHERE \"profileID\" IS NOT NULL;\n`;\n}\n\nexport function compareInstancesRows(a: InstancesRow, b: InstancesRow) {\n return stringCompare(a.clientGroupID, b.clientGroupID);\n}\n\nexport type ClientsRow = {\n clientGroupID: string;\n clientID: string;\n};\n\nfunction createClientsTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.clients (\n \"clientGroupID\" TEXT,\n \"clientID\" TEXT,\n\n PRIMARY KEY (\"clientGroupID\", \"clientID\"),\n\n CONSTRAINT fk_clients_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.instances(\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n`;\n}\nexport function compareClientsRows(a: ClientsRow, b: ClientsRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n return stringCompare(a.clientID, b.clientID);\n}\n\nexport type QueriesRow = {\n clientGroupID: string;\n queryHash: string;\n // This is the client AST _AFTER_ applying server name transformations.\n clientAST: JSONValue | null;\n queryName: string | null;\n queryArgs: readonly ReadonlyJSONValue[] | null;\n patchVersion: string | null;\n transformationHash: string | null;\n transformationVersion: string | null;\n internal: boolean | null;\n deleted: boolean | null;\n // Optional because (a) old DBs migrated to v17 will start with NULL, and\n // (b) test fixtures predate this column.\n rowSetSignature?: string | null;\n};\n\nfunction createQueriesTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.queries (\n \"clientGroupID\" TEXT,\n \"queryHash\" TEXT, -- this is the hash of the client query AST\n \"clientAST\" JSONB, -- this is nullable as custom queries will not persist an AST\n \"queryName\" TEXT, -- the name of the query if it is a custom query\n \"queryArgs\" JSON, -- the arguments of the query if it is a custom query\n \"patchVersion\" TEXT, -- NULL if only desired but not yet \"got\"\n \"transformationHash\" TEXT,\n \"transformationVersion\" TEXT,\n \"internal\" BOOL, -- If true, no need to track / send patches\n \"deleted\" BOOL, -- put vs del \"got\" query\n \"rowSetSignature\" TEXT, -- Hex XOR of h64([schema,table,rowKey]) for rows in this query (drift detection for Cap)\n\n PRIMARY KEY (\"clientGroupID\", \"queryHash\"),\n\n CONSTRAINT fk_queries_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.instances(\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX queries_patch_version \n ON ${schema(shard)}.queries (\"patchVersion\" NULLS FIRST);\n`;\n}\n\nexport function compareQueriesRows(a: QueriesRow, b: QueriesRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n return stringCompare(a.queryHash, b.queryHash);\n}\n\nexport type DesiresRow = {\n clientGroupID: string;\n clientID: string;\n queryHash: string;\n patchVersion: string;\n deleted: boolean | null;\n ttl: number | null;\n inactivatedAt: TTLClock | null;\n};\n\nfunction createDesiresTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.desires (\n \"clientGroupID\" TEXT,\n \"clientID\" TEXT,\n \"queryHash\" TEXT,\n \"patchVersion\" TEXT NOT NULL,\n \"deleted\" BOOL, -- put vs del \"desired\" query\n \"ttl\" INTERVAL, -- DEPRECATED: Use ttlMs instead. Time to live for this client\n \"ttlMs\" DOUBLE PRECISION, -- Time to live in milliseconds\n \"inactivatedAt\" TIMESTAMPTZ, -- DEPRECATED: Use inactivatedAtMs instead. Time at which this row was inactivated\n \"inactivatedAtMs\" DOUBLE PRECISION, -- Time at which this row was inactivated (milliseconds since client group start)\n\n PRIMARY KEY (\"clientGroupID\", \"clientID\", \"queryHash\"),\n\n CONSTRAINT fk_desires_query\n FOREIGN KEY(\"clientGroupID\", \"queryHash\")\n REFERENCES ${ident(cvrSchema(shard))}.queries(\"clientGroupID\", \"queryHash\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX desires_patch_version\n ON ${schema(shard)}.desires (\"patchVersion\");\n\nCREATE INDEX desires_inactivated_at\n ON ${schema(shard)}.desires (\"inactivatedAt\");\n`;\n}\n\nexport function compareDesiresRows(a: DesiresRow, b: DesiresRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n const clientIDComp = stringCompare(a.clientID, b.clientID);\n if (clientIDComp !== 0) {\n return clientIDComp;\n }\n return stringCompare(a.queryHash, b.queryHash);\n}\n\nexport type RowsRow = {\n clientGroupID: string;\n schema: string;\n table: string;\n rowKey: JSONObject;\n rowVersion: string;\n patchVersion: string;\n refCounts: {[queryHash: string]: number} | null;\n};\n\nfunction rowsRowToRowID(rowsRow: RowsRow): RowID {\n return {\n schema: rowsRow.schema,\n table: rowsRow.table,\n rowKey: rowsRow.rowKey as Record<string, JSONValue>,\n };\n}\n\nexport function rowsRowToRowRecord(rowsRow: RowsRow): RowRecord {\n return {\n id: rowsRowToRowID(rowsRow),\n rowVersion: rowsRow.rowVersion,\n patchVersion: versionFromString(rowsRow.patchVersion),\n refCounts: rowsRow.refCounts,\n };\n}\n\nexport function rowRecordToRowsRow(\n clientGroupID: string,\n rowRecord: RowRecord,\n): RowsRow {\n return {\n clientGroupID,\n schema: rowRecord.id.schema,\n table: rowRecord.id.table,\n rowKey: rowRecord.id.rowKey as Record<string, JSONValue>,\n rowVersion: rowRecord.rowVersion,\n patchVersion: versionString(rowRecord.patchVersion),\n refCounts: rowRecord.refCounts,\n };\n}\n\nexport function compareRowsRows(a: RowsRow, b: RowsRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n const schemaComp = stringCompare(a.schema, b.schema);\n if (schemaComp !== 0) {\n return schemaComp;\n }\n const tableComp = stringCompare(b.table, b.table);\n if (tableComp !== 0) {\n return tableComp;\n }\n return stringCompare(\n stringifySorted(a.rowKey as RowKey),\n stringifySorted(b.rowKey as RowKey),\n );\n}\n\n/**\n * The version of the data in the `cvr.rows` table. This may lag\n * `version` in `cvr.instances` but eventually catches up, modulo\n * exceptional circumstances like a server crash.\n *\n * The `rowsVersion` is tracked in a separate table (as opposed to\n * a column in the `cvr.instances` table) so that general `cvr` updates\n * and `row` updates can be executed independently without serialization\n * conflicts.\n *\n * Note: Although `clientGroupID` logically references the same column in\n * `cvr.instances`, a FOREIGN KEY constraint must not be declared as the\n * `cvr.rows` TABLE needs to be updated without affecting the\n * `SELECT ... FOR UPDATE` lock when `cvr.instances` is updated.\n */\nexport function createRowsVersionTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.\"rowsVersion\" (\n \"clientGroupID\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL\n);\n`;\n}\n\n/**\n * CVR `rows` are updated asynchronously from the CVR metadata\n * (i.e. `instances`). The `rowsVersion` table is updated atomically with\n * updates to the `rows` data.\n */\nfunction createRowsTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.rows (\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT NOT NULL,\n \"patchVersion\" TEXT NOT NULL,\n \"refCounts\" JSONB, -- {[queryHash: string]: number}, NULL for tombstone\n\n PRIMARY KEY (\"clientGroupID\", \"schema\", \"table\", \"rowKey\"),\n\n CONSTRAINT fk_rows_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.\"rowsVersion\" (\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX row_patch_version \n ON ${schema(shard)}.rows (\"patchVersion\");\n\n-- For listing rows returned by one or more query hashes. e.g.\n-- SELECT * FROM cvr_shard.rows WHERE \"refCounts\" ?| array[...queryHashes...];\nCREATE INDEX row_ref_counts ON ${schema(shard)}.rows \n USING GIN (\"refCounts\");\n`;\n}\n\nexport type RowsVersionRow = {\n clientGroupID: string;\n version: string;\n};\n\nfunction createTables(shard: ShardID) {\n return (\n createSchema(shard) +\n createInstancesTable(shard) +\n createClientsTable(shard) +\n createQueriesTable(shard) +\n createDesiresTable(shard) +\n createRowsVersionTable(shard) +\n createRowsTable(shard)\n );\n}\n\nexport async function setupCVRTables(\n lc: LogContext,\n db: postgres.TransactionSql,\n shard: ShardID,\n) {\n lc.info?.(`Setting up CVR tables`);\n await db.unsafe(createTables(shard));\n}\n\nfunction stringifySorted(r: RowKey) {\n return stringify(normalizedKeyOrder(r));\n}\n"],"mappings":";;;;;;AAsBA,SAAS,OAAO,OAAgB;CAC9B,OAAO,MAAM,UAAU,KAAK,CAAC;AAC/B;AAEA,SAAS,aAAa,OAAgB;CACpC,OAAO,+BAA+B,OAAO,KAAK,EAAE;AACtD;AAcA,SAAS,qBAAqB,OAAgB;CAC5C,OAAe;eACF,OAAO,KAAK,EAAE;;;;;;;;;;;;;;;OAetB,OAAO,KAAK,EAAE;;OAEd,OAAO,KAAK,EAAE;;;;;0CAKqB,OAAO,KAAK,EAAE;;;AAGxD;AAWA,SAAS,mBAAmB,OAAgB;CAC1C,OAAO;eACM,OAAO,KAAK,EAAE;;;;;;;;iBAQZ,OAAO,KAAK,EAAE;;;;;AAK/B;AA0BA,SAAS,mBAAmB,OAAgB;CAC1C,OAAO;eACM,OAAO,KAAK,EAAE;;;;;;;;;;;;;;;;;iBAiBZ,OAAO,KAAK,EAAE;;;;;;OAMxB,OAAO,KAAK,EAAE;;AAErB;AAoBA,SAAS,mBAAmB,OAAgB;CAC1C,OAAO;eACM,OAAO,KAAK,EAAE;;;;;;;;;;;;;;;iBAeZ,MAAM,UAAU,KAAK,CAAC,EAAE;;;;;;OAMlC,OAAO,KAAK,EAAE;;;OAGd,OAAO,KAAK,EAAE;;AAErB;AAwBA,SAAS,eAAe,SAAyB;CAC/C,OAAO;EACL,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,QAAQ,QAAQ;CAClB;AACF;AAEA,SAAgB,mBAAmB,SAA6B;CAC9D,OAAO;EACL,IAAI,eAAe,OAAO;EAC1B,YAAY,QAAQ;EACpB,cAAc,kBAAkB,QAAQ,YAAY;EACpD,WAAW,QAAQ;CACrB;AACF;AAEA,SAAgB,mBACd,eACA,WACS;CACT,OAAO;EACL;EACA,QAAQ,UAAU,GAAG;EACrB,OAAO,UAAU,GAAG;EACpB,QAAQ,UAAU,GAAG;EACrB,YAAY,UAAU;EACtB,cAAc,cAAc,UAAU,YAAY;EAClD,WAAW,UAAU;CACvB;AACF;;;;;;;;;;;;;;;;AAoCA,SAAgB,uBAAuB,OAAgB;CACrD,OAAO;eACM,OAAO,KAAK,EAAE;;;;;AAK7B;;;;;;AAOA,SAAS,gBAAgB,OAAgB;CACvC,OAAO;eACM,OAAO,KAAK,EAAE;;;;;;;;;;;;;iBAaZ,OAAO,KAAK,EAAE;;;;;;OAMxB,OAAO,KAAK,EAAE;;;;iCAIY,OAAO,KAAK,EAAE;;;AAG/C;AAOA,SAAS,aAAa,OAAgB;CACpC,OACE,aAAa,KAAK,IAClB,qBAAqB,KAAK,IAC1B,mBAAmB,KAAK,IACxB,mBAAmB,KAAK,IACxB,mBAAmB,KAAK,IACxB,uBAAuB,KAAK,IAC5B,gBAAgB,KAAK;AAEzB;AAEA,eAAsB,eACpB,IACA,IACA,OACA;CACA,GAAG,OAAO,uBAAuB;CACjC,MAAM,GAAG,OAAO,aAAa,KAAK,CAAC;AACrC"}
@@ -52,7 +52,6 @@ function cookieToVersion(cookie) {
52
52
  if (cookie === null) return null;
53
53
  return versionFromString(cookie);
54
54
  }
55
- valita_exports.object({ id: valita_exports.string() });
56
55
  var cvrRecordSchema = valita_exports.object({
57
56
  /**
58
57
  * CVR records store the CVRVersion at which the record was last patched into
@@ -171,23 +170,10 @@ cvrRecordSchema.extend({
171
170
  rowVersion: valita_exports.string(),
172
171
  refCounts: valita_exports.record(valita_exports.number()).nullable()
173
172
  });
174
- var patchSchema = valita_exports.object({
173
+ valita_exports.object({
175
174
  type: literalUnion("row", "query"),
176
175
  op: literalUnion("put", "del")
177
- });
178
- var putRowPatchSchema = patchSchema.extend({
179
- type: valita_exports.literal("row"),
180
- op: valita_exports.literal("put"),
181
- id: rowIDSchema,
182
- rowVersion: valita_exports.string()
183
- });
184
- var delRowPatchSchema = patchSchema.extend({
185
- type: valita_exports.literal("row"),
186
- op: valita_exports.literal("del"),
187
- id: rowIDSchema
188
- });
189
- valita_exports.union(putRowPatchSchema, delRowPatchSchema);
190
- patchSchema.extend({
176
+ }).extend({
191
177
  type: valita_exports.literal("query"),
192
178
  id: valita_exports.string(),
193
179
  clientID: valita_exports.string().optional()
@@ -258,6 +244,6 @@ function queryRecordToQueryRow(clientGroupID, query, rowSetSignature) {
258
244
  }
259
245
  var maybeVersionString = (v) => v ? versionString(v) : null;
260
246
  //#endregion
261
- export { EMPTY_CVR_VERSION, baseQueryRecordSchema, clientQueryRecordSchema, cmpVersions, cookieToVersion, customQueryRecordSchema, cvrVersionSchema, delRowPatchSchema, internalQueryRecordSchema, maxVersion, maybeVersionString, oneAfter, patchSchema, putRowPatchSchema, queryRecordToQueryRow, rowIDSchema, versionFromString, versionString, versionToCookie, versionToNullableCookie };
247
+ export { EMPTY_CVR_VERSION, cmpVersions, cookieToVersion, maxVersion, oneAfter, queryRecordToQueryRow, versionFromString, versionString, versionToCookie, versionToNullableCookie };
262
248
 
263
249
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["v","v.readonly","v.literalUnion"],"sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/types.ts"],"sourcesContent":["import {jsonValueSchema} from '../../../../../shared/src/bigint-json.ts';\nimport {jsonSchema} from '../../../../../shared/src/json-schema.ts';\nimport * as v from '../../../../../shared/src/valita.ts';\nimport {astSchema} from '../../../../../zero-protocol/src/ast.ts';\nimport {versionFromLexi, versionToLexi} from '../../../types/lexi-version.ts';\nimport {\n majorVersionToString,\n stateVersionFromString,\n} from '../../../types/state-version.ts';\nimport {ttlClockSchema} from '../ttl-clock.ts';\nimport type {QueriesRow} from './cvr.ts';\n\nexport const cvrVersionSchema = v.object({\n /**\n * The database `stateVersion` with which the rows in the CVR are consistent.\n */\n stateVersion: v.string(), // LexiVersion\n\n /**\n * `configVersion` is subversion of `stateVersion` that is initially absent for each\n * `stateVersion`, and incremented for configuration changes that affect the contents\n * of the CVR such as:\n *\n * * query set changes\n * * query transformation changes (which may happen for changes\n * in server-side logic or authorization policies)\n *\n * Such configuration changes are always correlated with a change to one or more\n * `/meta/...` records in the CVR, often (but not always) with corresponding\n * patches in `/patches/meta/...`.\n *\n * When the `stateVersion` moves forward, the `minorVersion` is reset to absent.\n * In this manner it behaves like the analogous concept in semantic versioning.\n */\n configVersion: v.number().optional(),\n});\n\nexport type CVRVersion = v.Infer<typeof cvrVersionSchema>;\n\nexport const EMPTY_CVR_VERSION: CVRVersion = {\n stateVersion: majorVersionToString(0),\n} as const;\n\nexport function oneAfter(v: NullableCVRVersion): CVRVersion {\n return v === null\n ? {stateVersion: majorVersionToString(0)}\n : {\n stateVersion: v.stateVersion,\n configVersion: (v.configVersion ?? 0) + 1,\n };\n}\n\nexport type NullableCVRVersion = CVRVersion | null;\n\nexport function cmpVersions(\n a: NullableCVRVersion,\n b: NullableCVRVersion,\n): number {\n return a === null && b === null\n ? 0\n : a === null\n ? -1\n : b === null\n ? 1\n : a.stateVersion < b.stateVersion\n ? -1\n : a.stateVersion > b.stateVersion\n ? 1\n : (a.configVersion ?? 0) - (b.configVersion ?? 0);\n}\n\nexport function maxVersion(a: CVRVersion, b?: CVRVersion): CVRVersion {\n return !b ? a : cmpVersions(b, a) > 0 ? b : a;\n}\n\nexport function versionToCookie(v: CVRVersion): string {\n return versionString(v);\n}\n\nexport function versionToNullableCookie(v: NullableCVRVersion): string | null {\n return v === null ? null : versionToCookie(v);\n}\n\nexport function cookieToVersion(cookie: string | null): NullableCVRVersion {\n if (cookie === null) {\n return null;\n }\n return versionFromString(cookie);\n}\n\n// Last Active tracking.\n\nexport const cvrIDSchema = v.object({id: v.string()});\nexport type CvrID = v.Infer<typeof cvrIDSchema>;\n\nconst cvrRecordSchema = v.object({\n /**\n * CVR records store the CVRVersion at which the record was last patched into\n * the CVR, which corresponds with a patch row that is cleaned up when the\n * record is changed (updated, deleted, and re-added in the case of rows).\n *\n * Tombstones are stored for row records but not for config records. This means\n * that \"orphaned\" delete patches for config records may exist, and therefore\n * scans of config patches must always run until the end of the list. On the\n * contrary, for row patches, the row record tombstones allow cleanup of delete\n * patches.\n */\n patchVersion: cvrVersionSchema,\n});\n\nexport const clientRecordSchema = v.object({\n /** The client ID, of which there can be multiple for a client group view. */\n id: v.string(),\n\n /** The client's desired query IDs. Patch information is stored in the QueryRecord. */\n desiredQueryIDs: v.array(v.string()),\n});\n\nexport type ClientRecord = v.Infer<typeof clientRecordSchema>;\n\nexport const baseQueryRecordSchema = v.object({\n /** The client-specified ID used to identify this query. Typically a hash. */\n id: v.string(),\n\n /**\n * The hash of the query after server-side transformations, which include:\n *\n * * Normalization (which may differ from what the client does)\n * * Query \"expansion\" to include primary keys and query-execution-related columns\n * * Authorization transforms\n *\n * Transformations depend on conditions that are independent of the db state version,\n * such as server-side logic and authorization policies. As such, the version of a CVR\n * version may need to be advanced independent of db state changes. This is done\n * via the `minorVersion` counter of the CVRVersion object, which is used to account\n * for both changes to the query set and changes to query transformations (which are\n * effectively remove-old-query + add-new-query).\n *\n * Note that the transformed AST itself is **not** stored, as the result of the previous\n * transformation is not useful in and of itself. If the current transformation results in\n * a different hash than that of the transformation used for the last version of the CVR,\n * it is simply handled by invalidating the existing rows, re-executed the query with\n * the new transformation, and advancing the CVR's `minorVersion` and this query's\n * `transformationVersion`.\n *\n * Note that the transformationHash is only stored when the query has reached the \"gotten\"\n * state. If the query is in the \"desired\" but not yet \"gotten\" state, the field is absent.\n */\n transformationHash: v.string().optional(),\n\n /**\n * The CVR version corresponding to the `transformationHash`. This essentially tracks when\n * this version of the query was effectively added to the CVR (as opposed to the\n * `patchVersion`, which is simply when the client was notified that its query was added\n * to the gotten set). Catchup of clients from old CVR versions require executing all\n * queries with a newer `transformationVersion`.\n */\n transformationVersion: cvrVersionSchema.optional(),\n});\n\n/**\n * Internal queries track rows in the database for internal use, such as the\n * `lastMutationID`s in the `zero.clients` table. They participate in the standard\n * invalidation / update logic for row contents, but not in the desired/got or\n * size-based quota logic for client-requested queries.\n */\nexport const internalQueryRecordSchema = baseQueryRecordSchema.extend({\n type: v.literal('internal'),\n ast: astSchema,\n});\n\nexport type InternalQueryRecord = v.Infer<typeof internalQueryRecordSchema>;\n\nconst clientStateSchema = v.object({\n /**\n * The time at which the query was last inactivated. If this undefined or\n * missing then the query is active.\n *\n * Desired queries are always active and have an undefined inactivatedAt.\n */\n inactivatedAt: ttlClockSchema.optional(),\n\n /**\n * TTL, time to live in milliseconds. If the query is not updated within this\n * time. The time to live is the time after it has become inactive. Negative\n * values are treated as `'forever'`.\n *\n * We do clamp this to a maximum of 10 minutes, so that queries do not\n * live for a very long time in the CVR.\n */\n ttl: v.number(),\n\n /**\n * The version at which the client state changed (i.e. individual `patchVersion`s).\n */\n version: cvrVersionSchema,\n});\n\nconst externalQueryRecordSchema = baseQueryRecordSchema.extend({\n /**\n * The client state for this query, which includes the inactivatedAt, ttl and\n * version. The client state is stored in a record with the client ID as the\n * key.\n */\n clientState: v.record(clientStateSchema),\n\n // For queries, the `patchVersion` indicates when query was added to the got set,\n // and is absent if not yet gotten.\n patchVersion: cvrVersionSchema.optional(),\n});\n\nexport const clientQueryRecordSchema = externalQueryRecordSchema.extend({\n type: v.literal('client'),\n\n /** The original AST as supplied by the client. */\n ast: astSchema,\n});\n\nexport type ClientQueryRecord = v.Infer<typeof clientQueryRecordSchema>;\n\nexport const customQueryRecordSchema = externalQueryRecordSchema.extend({\n type: v.literal('custom'),\n name: v.string(),\n args: v.readonly(v.array(jsonSchema)),\n});\n\nexport type CustomQueryRecord = v.Infer<typeof customQueryRecordSchema>;\n\nexport const queryRecordSchema = v.union(\n clientQueryRecordSchema,\n customQueryRecordSchema,\n internalQueryRecordSchema,\n);\n\nexport type QueryRecord = v.Infer<typeof queryRecordSchema>;\n\nexport const rowIDSchema = v.object({\n schema: v.string(),\n table: v.string(),\n rowKey: v.record(jsonValueSchema),\n});\n\nexport type RowID = v.Infer<typeof rowIDSchema>;\n\nexport const rowRecordSchema = cvrRecordSchema.extend({\n id: rowIDSchema,\n rowVersion: v.string(), // '_0_version' of the row\n // query hashes => refCount, or `null` for a row that was removed from the\n // view (i.e. tombstone).\n refCounts: v.record(v.number()).nullable(),\n});\n\nexport type RowRecord = v.Infer<typeof rowRecordSchema>;\n\nexport const patchSchema = v.object({\n type: v.literalUnion('row', 'query'),\n op: v.literalUnion('put', 'del'),\n});\n\nexport const putRowPatchSchema = patchSchema.extend({\n type: v.literal('row'),\n op: v.literal('put'),\n id: rowIDSchema,\n rowVersion: v.string(), // '_0_version' of the row\n});\n\nexport type PutRowPatch = v.Infer<typeof putRowPatchSchema>;\n\nexport const delRowPatchSchema = patchSchema.extend({\n type: v.literal('row'),\n op: v.literal('del'),\n id: rowIDSchema,\n});\n\nexport type DelRowPatch = v.Infer<typeof delRowPatchSchema>;\n\nexport const rowPatchSchema = v.union(putRowPatchSchema, delRowPatchSchema);\n\nexport type RowPatch = v.Infer<typeof rowPatchSchema>;\n\nexport const queryPatchSchema = patchSchema.extend({\n type: v.literal('query'),\n id: v.string(),\n clientID: v.string().optional(), // defined for \"desired\", undefined for \"got\"\n});\n\nexport type QueryPatch = v.Infer<typeof queryPatchSchema>;\n\nexport type PutQueryPatch = QueryPatch & {op: 'put'};\nexport type DelQueryPatch = QueryPatch & {op: 'del'};\n\nexport const metadataPatchSchema = queryPatchSchema;\n\nexport type MetadataPatch = v.Infer<typeof metadataPatchSchema>;\n\nexport function versionString(v: CVRVersion) {\n // The separator (e.g. \":\") needs to be lexicographically greater than the\n // storage key path separator (e.g. \"/\") so that \"01/row-hash\" is less than \"01:01/row-hash\".\n // In particular, the traditional separator for major.minor versions (\".\") does not\n // satisfy this quality.\n return v.configVersion\n ? `${v.stateVersion}:${versionToLexi(v.configVersion)}`\n : v.stateVersion;\n}\n\nexport function versionFromString(str: string): CVRVersion {\n const parts = str.split(':');\n const stateVersion = parts[0];\n switch (parts.length) {\n case 1: {\n stateVersionFromString(stateVersion); // Purely for validation.\n return {stateVersion};\n }\n case 2: {\n const configVersion = versionFromLexi(parts[1]);\n if (configVersion > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new Error(`minorVersion ${parts[1]} exceeds max safe integer`);\n }\n return {stateVersion, configVersion: Number(configVersion)};\n }\n default:\n throw new TypeError(`Invalid version string ${str}`);\n }\n}\n\nexport function queryRecordToQueryRow(\n clientGroupID: string,\n query: QueryRecord,\n rowSetSignature: string | null,\n): QueriesRow {\n switch (query.type) {\n case 'internal':\n return {\n clientGroupID,\n queryHash: query.id,\n clientAST: query.ast,\n queryName: null,\n queryArgs: null,\n patchVersion: null,\n transformationHash: query.transformationHash ?? null,\n transformationVersion: maybeVersionString(query.transformationVersion),\n internal: true,\n deleted: false, // put vs del \"got\" query\n rowSetSignature,\n };\n case 'client':\n return {\n clientGroupID,\n queryHash: query.id,\n clientAST: query.ast,\n queryName: null,\n queryArgs: null,\n patchVersion: maybeVersionString(query.patchVersion),\n transformationHash: query.transformationHash ?? null,\n transformationVersion: maybeVersionString(query.transformationVersion),\n internal: null,\n deleted: false, // put vs del \"got\" query\n rowSetSignature,\n };\n case 'custom':\n return {\n clientGroupID,\n queryHash: query.id,\n clientAST: null,\n queryName: query.name,\n queryArgs: query.args,\n patchVersion: maybeVersionString(query.patchVersion),\n transformationHash: query.transformationHash ?? null,\n transformationVersion: maybeVersionString(query.transformationVersion),\n internal: null,\n deleted: false, // put vs del \"got\" query\n rowSetSignature,\n };\n }\n}\n\nexport const maybeVersionString = (v: CVRVersion | undefined) =>\n v ? versionString(v) : null;\n"],"mappings":"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"}
1
+ {"version":3,"file":"types.js","names":["v","v.readonly","v.literalUnion"],"sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/types.ts"],"sourcesContent":["import {jsonValueSchema} from '../../../../../shared/src/bigint-json.ts';\nimport {jsonSchema} from '../../../../../shared/src/json-schema.ts';\nimport * as v from '../../../../../shared/src/valita.ts';\nimport {astSchema} from '../../../../../zero-protocol/src/ast.ts';\nimport {versionFromLexi, versionToLexi} from '../../../types/lexi-version.ts';\nimport {\n majorVersionToString,\n stateVersionFromString,\n} from '../../../types/state-version.ts';\nimport {ttlClockSchema} from '../ttl-clock.ts';\nimport type {QueriesRow} from './cvr.ts';\n\nconst cvrVersionSchema = v.object({\n /**\n * The database `stateVersion` with which the rows in the CVR are consistent.\n */\n stateVersion: v.string(), // LexiVersion\n\n /**\n * `configVersion` is subversion of `stateVersion` that is initially absent for each\n * `stateVersion`, and incremented for configuration changes that affect the contents\n * of the CVR such as:\n *\n * * query set changes\n * * query transformation changes (which may happen for changes\n * in server-side logic or authorization policies)\n *\n * Such configuration changes are always correlated with a change to one or more\n * `/meta/...` records in the CVR, often (but not always) with corresponding\n * patches in `/patches/meta/...`.\n *\n * When the `stateVersion` moves forward, the `minorVersion` is reset to absent.\n * In this manner it behaves like the analogous concept in semantic versioning.\n */\n configVersion: v.number().optional(),\n});\n\nexport type CVRVersion = v.Infer<typeof cvrVersionSchema>;\n\nexport const EMPTY_CVR_VERSION: CVRVersion = {\n stateVersion: majorVersionToString(0),\n} as const;\n\nexport function oneAfter(v: NullableCVRVersion): CVRVersion {\n return v === null\n ? {stateVersion: majorVersionToString(0)}\n : {\n stateVersion: v.stateVersion,\n configVersion: (v.configVersion ?? 0) + 1,\n };\n}\n\nexport type NullableCVRVersion = CVRVersion | null;\n\nexport function cmpVersions(\n a: NullableCVRVersion,\n b: NullableCVRVersion,\n): number {\n return a === null && b === null\n ? 0\n : a === null\n ? -1\n : b === null\n ? 1\n : a.stateVersion < b.stateVersion\n ? -1\n : a.stateVersion > b.stateVersion\n ? 1\n : (a.configVersion ?? 0) - (b.configVersion ?? 0);\n}\n\nexport function maxVersion(a: CVRVersion, b?: CVRVersion): CVRVersion {\n return !b ? a : cmpVersions(b, a) > 0 ? b : a;\n}\n\nexport function versionToCookie(v: CVRVersion): string {\n return versionString(v);\n}\n\nexport function versionToNullableCookie(v: NullableCVRVersion): string | null {\n return v === null ? null : versionToCookie(v);\n}\n\nexport function cookieToVersion(cookie: string | null): NullableCVRVersion {\n if (cookie === null) {\n return null;\n }\n return versionFromString(cookie);\n}\n\n// Last Active tracking.\n\nconst cvrRecordSchema = v.object({\n /**\n * CVR records store the CVRVersion at which the record was last patched into\n * the CVR, which corresponds with a patch row that is cleaned up when the\n * record is changed (updated, deleted, and re-added in the case of rows).\n *\n * Tombstones are stored for row records but not for config records. This means\n * that \"orphaned\" delete patches for config records may exist, and therefore\n * scans of config patches must always run until the end of the list. On the\n * contrary, for row patches, the row record tombstones allow cleanup of delete\n * patches.\n */\n patchVersion: cvrVersionSchema,\n});\n\nconst clientRecordSchema = v.object({\n /** The client ID, of which there can be multiple for a client group view. */\n id: v.string(),\n\n /** The client's desired query IDs. Patch information is stored in the QueryRecord. */\n desiredQueryIDs: v.array(v.string()),\n});\n\nexport type ClientRecord = v.Infer<typeof clientRecordSchema>;\n\nconst baseQueryRecordSchema = v.object({\n /** The client-specified ID used to identify this query. Typically a hash. */\n id: v.string(),\n\n /**\n * The hash of the query after server-side transformations, which include:\n *\n * * Normalization (which may differ from what the client does)\n * * Query \"expansion\" to include primary keys and query-execution-related columns\n * * Authorization transforms\n *\n * Transformations depend on conditions that are independent of the db state version,\n * such as server-side logic and authorization policies. As such, the version of a CVR\n * version may need to be advanced independent of db state changes. This is done\n * via the `minorVersion` counter of the CVRVersion object, which is used to account\n * for both changes to the query set and changes to query transformations (which are\n * effectively remove-old-query + add-new-query).\n *\n * Note that the transformed AST itself is **not** stored, as the result of the previous\n * transformation is not useful in and of itself. If the current transformation results in\n * a different hash than that of the transformation used for the last version of the CVR,\n * it is simply handled by invalidating the existing rows, re-executed the query with\n * the new transformation, and advancing the CVR's `minorVersion` and this query's\n * `transformationVersion`.\n *\n * Note that the transformationHash is only stored when the query has reached the \"gotten\"\n * state. If the query is in the \"desired\" but not yet \"gotten\" state, the field is absent.\n */\n transformationHash: v.string().optional(),\n\n /**\n * The CVR version corresponding to the `transformationHash`. This essentially tracks when\n * this version of the query was effectively added to the CVR (as opposed to the\n * `patchVersion`, which is simply when the client was notified that its query was added\n * to the gotten set). Catchup of clients from old CVR versions require executing all\n * queries with a newer `transformationVersion`.\n */\n transformationVersion: cvrVersionSchema.optional(),\n});\n\n/**\n * Internal queries track rows in the database for internal use, such as the\n * `lastMutationID`s in the `zero.clients` table. They participate in the standard\n * invalidation / update logic for row contents, but not in the desired/got or\n * size-based quota logic for client-requested queries.\n */\nconst internalQueryRecordSchema = baseQueryRecordSchema.extend({\n type: v.literal('internal'),\n ast: astSchema,\n});\n\nexport type InternalQueryRecord = v.Infer<typeof internalQueryRecordSchema>;\n\nconst clientStateSchema = v.object({\n /**\n * The time at which the query was last inactivated. If this undefined or\n * missing then the query is active.\n *\n * Desired queries are always active and have an undefined inactivatedAt.\n */\n inactivatedAt: ttlClockSchema.optional(),\n\n /**\n * TTL, time to live in milliseconds. If the query is not updated within this\n * time. The time to live is the time after it has become inactive. Negative\n * values are treated as `'forever'`.\n *\n * We do clamp this to a maximum of 10 minutes, so that queries do not\n * live for a very long time in the CVR.\n */\n ttl: v.number(),\n\n /**\n * The version at which the client state changed (i.e. individual `patchVersion`s).\n */\n version: cvrVersionSchema,\n});\n\nconst externalQueryRecordSchema = baseQueryRecordSchema.extend({\n /**\n * The client state for this query, which includes the inactivatedAt, ttl and\n * version. The client state is stored in a record with the client ID as the\n * key.\n */\n clientState: v.record(clientStateSchema),\n\n // For queries, the `patchVersion` indicates when query was added to the got set,\n // and is absent if not yet gotten.\n patchVersion: cvrVersionSchema.optional(),\n});\n\nconst clientQueryRecordSchema = externalQueryRecordSchema.extend({\n type: v.literal('client'),\n\n /** The original AST as supplied by the client. */\n ast: astSchema,\n});\n\nexport type ClientQueryRecord = v.Infer<typeof clientQueryRecordSchema>;\n\nconst customQueryRecordSchema = externalQueryRecordSchema.extend({\n type: v.literal('custom'),\n name: v.string(),\n args: v.readonly(v.array(jsonSchema)),\n});\n\nexport type CustomQueryRecord = v.Infer<typeof customQueryRecordSchema>;\n\nconst queryRecordSchema = v.union(\n clientQueryRecordSchema,\n customQueryRecordSchema,\n internalQueryRecordSchema,\n);\n\nexport type QueryRecord = v.Infer<typeof queryRecordSchema>;\n\nconst rowIDSchema = v.object({\n schema: v.string(),\n table: v.string(),\n rowKey: v.record(jsonValueSchema),\n});\n\nexport type RowID = v.Infer<typeof rowIDSchema>;\n\nconst rowRecordSchema = cvrRecordSchema.extend({\n id: rowIDSchema,\n rowVersion: v.string(), // '_0_version' of the row\n // query hashes => refCount, or `null` for a row that was removed from the\n // view (i.e. tombstone).\n refCounts: v.record(v.number()).nullable(),\n});\n\nexport type RowRecord = v.Infer<typeof rowRecordSchema>;\n\nconst patchSchema = v.object({\n type: v.literalUnion('row', 'query'),\n op: v.literalUnion('put', 'del'),\n});\n\nconst queryPatchSchema = patchSchema.extend({\n type: v.literal('query'),\n id: v.string(),\n clientID: v.string().optional(), // defined for \"desired\", undefined for \"got\"\n});\n\nexport type QueryPatch = v.Infer<typeof queryPatchSchema>;\n\nexport type PutQueryPatch = QueryPatch & {op: 'put'};\nexport type DelQueryPatch = QueryPatch & {op: 'del'};\n\nexport function versionString(v: CVRVersion) {\n // The separator (e.g. \":\") needs to be lexicographically greater than the\n // storage key path separator (e.g. \"/\") so that \"01/row-hash\" is less than \"01:01/row-hash\".\n // In particular, the traditional separator for major.minor versions (\".\") does not\n // satisfy this quality.\n return v.configVersion\n ? `${v.stateVersion}:${versionToLexi(v.configVersion)}`\n : v.stateVersion;\n}\n\nexport function versionFromString(str: string): CVRVersion {\n const parts = str.split(':');\n const stateVersion = parts[0];\n switch (parts.length) {\n case 1: {\n stateVersionFromString(stateVersion); // Purely for validation.\n return {stateVersion};\n }\n case 2: {\n const configVersion = versionFromLexi(parts[1]);\n if (configVersion > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new Error(`minorVersion ${parts[1]} exceeds max safe integer`);\n }\n return {stateVersion, configVersion: Number(configVersion)};\n }\n default:\n throw new TypeError(`Invalid version string ${str}`);\n }\n}\n\nexport function queryRecordToQueryRow(\n clientGroupID: string,\n query: QueryRecord,\n rowSetSignature: string | null,\n): QueriesRow {\n switch (query.type) {\n case 'internal':\n return {\n clientGroupID,\n queryHash: query.id,\n clientAST: query.ast,\n queryName: null,\n queryArgs: null,\n patchVersion: null,\n transformationHash: query.transformationHash ?? null,\n transformationVersion: maybeVersionString(query.transformationVersion),\n internal: true,\n deleted: false, // put vs del \"got\" query\n rowSetSignature,\n };\n case 'client':\n return {\n clientGroupID,\n queryHash: query.id,\n clientAST: query.ast,\n queryName: null,\n queryArgs: null,\n patchVersion: maybeVersionString(query.patchVersion),\n transformationHash: query.transformationHash ?? null,\n transformationVersion: maybeVersionString(query.transformationVersion),\n internal: null,\n deleted: false, // put vs del \"got\" query\n rowSetSignature,\n };\n case 'custom':\n return {\n clientGroupID,\n queryHash: query.id,\n clientAST: null,\n queryName: query.name,\n queryArgs: query.args,\n patchVersion: maybeVersionString(query.patchVersion),\n transformationHash: query.transformationHash ?? null,\n transformationVersion: maybeVersionString(query.transformationVersion),\n internal: null,\n deleted: false, // put vs del \"got\" query\n rowSetSignature,\n };\n }\n}\n\nconst maybeVersionString = (v: CVRVersion | undefined) =>\n v ? versionString(v) : null;\n"],"mappings":"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"}
@@ -738,6 +738,6 @@ var InvalidDiffError = class extends Error {
738
738
  }
739
739
  };
740
740
  //#endregion
741
- export { CHANGE_LOG_SAMPLE_SIZE, InvalidDiffError, ResetPipelinesSignal, Snapshotter };
741
+ export { InvalidDiffError, ResetPipelinesSignal, Snapshotter };
742
742
 
743
743
  //# sourceMappingURL=snapshotter.js.map