@rocicorp/zero 0.26.0-canary.2 → 0.26.0-canary.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/README.md +1 -1
  2. package/out/replicache/src/persist/collect-idb-databases.d.ts +4 -4
  3. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  4. package/out/replicache/src/persist/collect-idb-databases.js +22 -19
  5. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  6. package/out/replicache/src/persist/refresh.d.ts.map +1 -1
  7. package/out/replicache/src/persist/refresh.js +0 -8
  8. package/out/replicache/src/persist/refresh.js.map +1 -1
  9. package/out/replicache/src/process-scheduler.d.ts +23 -0
  10. package/out/replicache/src/process-scheduler.d.ts.map +1 -1
  11. package/out/replicache/src/process-scheduler.js +50 -1
  12. package/out/replicache/src/process-scheduler.js.map +1 -1
  13. package/out/replicache/src/replicache-impl.d.ts +8 -0
  14. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  15. package/out/replicache/src/replicache-impl.js +11 -2
  16. package/out/replicache/src/replicache-impl.js.map +1 -1
  17. package/out/shared/src/falsy.d.ts +3 -0
  18. package/out/shared/src/falsy.d.ts.map +1 -0
  19. package/out/zero/package.json.js +1 -1
  20. package/out/zero/src/adapters/drizzle.js +1 -2
  21. package/out/zero/src/adapters/prisma.d.ts +2 -0
  22. package/out/zero/src/adapters/prisma.d.ts.map +1 -0
  23. package/out/zero/src/adapters/prisma.js +6 -0
  24. package/out/zero/src/adapters/prisma.js.map +1 -0
  25. package/out/zero/src/pg.js +4 -7
  26. package/out/zero/src/react.js +3 -1
  27. package/out/zero/src/react.js.map +1 -1
  28. package/out/zero/src/server.js +5 -8
  29. package/out/zero-cache/src/auth/load-permissions.d.ts +3 -2
  30. package/out/zero-cache/src/auth/load-permissions.d.ts.map +1 -1
  31. package/out/zero-cache/src/auth/load-permissions.js +14 -8
  32. package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
  33. package/out/zero-cache/src/auth/write-authorizer.d.ts +6 -0
  34. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  35. package/out/zero-cache/src/auth/write-authorizer.js +16 -3
  36. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  37. package/out/zero-cache/src/config/zero-config.d.ts +44 -8
  38. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  39. package/out/zero-cache/src/config/zero-config.js +53 -13
  40. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  41. package/out/zero-cache/src/custom/fetch.d.ts +3 -0
  42. package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
  43. package/out/zero-cache/src/custom/fetch.js +26 -0
  44. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  45. package/out/zero-cache/src/db/lite-tables.js +1 -1
  46. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  47. package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
  48. package/out/zero-cache/src/db/migration-lite.js +9 -3
  49. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  50. package/out/zero-cache/src/db/migration.d.ts.map +1 -1
  51. package/out/zero-cache/src/db/migration.js +9 -3
  52. package/out/zero-cache/src/db/migration.js.map +1 -1
  53. package/out/zero-cache/src/db/specs.d.ts +4 -3
  54. package/out/zero-cache/src/db/specs.d.ts.map +1 -1
  55. package/out/zero-cache/src/db/specs.js +4 -1
  56. package/out/zero-cache/src/db/specs.js.map +1 -1
  57. package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
  58. package/out/zero-cache/src/db/transaction-pool.js +9 -3
  59. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  60. package/out/zero-cache/src/server/inspector-delegate.d.ts +1 -1
  61. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  62. package/out/zero-cache/src/server/inspector-delegate.js +11 -30
  63. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  64. package/out/zero-cache/src/server/main.js +1 -1
  65. package/out/zero-cache/src/server/main.js.map +1 -1
  66. package/out/zero-cache/src/server/priority-op.d.ts +8 -0
  67. package/out/zero-cache/src/server/priority-op.d.ts.map +1 -0
  68. package/out/zero-cache/src/server/priority-op.js +29 -0
  69. package/out/zero-cache/src/server/priority-op.js.map +1 -0
  70. package/out/zero-cache/src/server/syncer.d.ts +0 -1
  71. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  72. package/out/zero-cache/src/server/syncer.js +3 -21
  73. package/out/zero-cache/src/server/syncer.js.map +1 -1
  74. package/out/zero-cache/src/services/analyze.js +1 -1
  75. package/out/zero-cache/src/services/analyze.js.map +1 -1
  76. package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
  77. package/out/zero-cache/src/services/change-source/custom/change-source.js +4 -3
  78. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  79. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  80. package/out/zero-cache/src/services/change-source/pg/change-source.js +68 -13
  81. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  82. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  83. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +7 -2
  84. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  85. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
  86. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +7 -4
  87. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  88. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +125 -180
  89. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  90. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/change-source/pg/schema/init.js +18 -10
  92. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  93. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +36 -90
  94. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
  95. package/out/zero-cache/src/services/change-source/pg/schema/published.js +51 -14
  96. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  97. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +31 -36
  98. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  99. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +24 -3
  100. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  101. package/out/zero-cache/src/services/change-source/pg/schema/validation.d.ts +2 -2
  102. package/out/zero-cache/src/services/change-source/pg/schema/validation.d.ts.map +1 -1
  103. package/out/zero-cache/src/services/change-source/pg/schema/validation.js +2 -4
  104. package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
  105. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +158 -53
  106. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  107. package/out/zero-cache/src/services/change-source/protocol/current/data.js +55 -10
  108. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  109. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +210 -72
  110. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  111. package/out/zero-cache/src/services/change-source/protocol/current.js +4 -2
  112. package/out/zero-cache/src/services/change-source/replica-schema.d.ts.map +1 -1
  113. package/out/zero-cache/src/services/change-source/replica-schema.js +19 -10
  114. package/out/zero-cache/src/services/change-source/replica-schema.js.map +1 -1
  115. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +1 -1
  116. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  117. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +71 -25
  118. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  119. package/out/zero-cache/src/services/change-streamer/change-streamer.js +1 -1
  120. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  121. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +1 -0
  122. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
  123. package/out/zero-cache/src/services/change-streamer/schema/tables.js +6 -5
  124. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  125. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  126. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  127. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +2 -0
  128. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  129. package/out/zero-cache/src/services/change-streamer/subscriber.js +14 -1
  130. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  131. package/out/zero-cache/src/services/heapz.d.ts.map +1 -1
  132. package/out/zero-cache/src/services/heapz.js +1 -0
  133. package/out/zero-cache/src/services/heapz.js.map +1 -1
  134. package/out/zero-cache/src/services/mutagen/error.d.ts.map +1 -1
  135. package/out/zero-cache/src/services/mutagen/error.js +4 -1
  136. package/out/zero-cache/src/services/mutagen/error.js.map +1 -1
  137. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  138. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -0
  139. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  140. package/out/zero-cache/src/services/mutagen/pusher.d.ts +7 -4
  141. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  142. package/out/zero-cache/src/services/mutagen/pusher.js +80 -8
  143. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  144. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  145. package/out/zero-cache/src/services/replicator/change-processor.js +21 -29
  146. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  147. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +1 -2
  148. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts.map +1 -1
  149. package/out/zero-cache/src/services/replicator/schema/change-log.js +2 -5
  150. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  151. package/out/zero-cache/src/services/{change-source → replicator/schema}/column-metadata.d.ts +3 -3
  152. package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts.map +1 -0
  153. package/out/zero-cache/src/services/{change-source → replicator/schema}/column-metadata.js +3 -3
  154. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -0
  155. package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts.map +1 -1
  156. package/out/zero-cache/src/services/replicator/schema/replication-state.js +3 -1
  157. package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
  158. package/out/zero-cache/src/services/run-ast.js +1 -1
  159. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  160. package/out/zero-cache/src/services/statz.d.ts.map +1 -1
  161. package/out/zero-cache/src/services/statz.js +1 -0
  162. package/out/zero-cache/src/services/statz.js.map +1 -1
  163. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +1 -1
  164. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  165. package/out/zero-cache/src/services/view-syncer/cvr-store.js +59 -40
  166. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  167. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +0 -1
  168. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  169. package/out/zero-cache/src/services/view-syncer/cvr.js +23 -6
  170. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  171. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +13 -14
  172. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  173. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +44 -56
  174. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  175. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts +1 -1
  176. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
  177. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +22 -11
  178. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  179. package/out/zero-cache/src/services/view-syncer/snapshotter.js +1 -1
  180. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  181. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +6 -3
  182. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  183. package/out/zero-cache/src/services/view-syncer/view-syncer.js +192 -217
  184. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  185. package/out/zero-cache/src/types/lexi-version.d.ts.map +1 -1
  186. package/out/zero-cache/src/types/lexi-version.js +4 -1
  187. package/out/zero-cache/src/types/lexi-version.js.map +1 -1
  188. package/out/zero-cache/src/types/lite.d.ts.map +1 -1
  189. package/out/zero-cache/src/types/lite.js +8 -2
  190. package/out/zero-cache/src/types/lite.js.map +1 -1
  191. package/out/zero-cache/src/types/shards.js +1 -1
  192. package/out/zero-cache/src/types/shards.js.map +1 -1
  193. package/out/zero-cache/src/types/sql.d.ts +5 -0
  194. package/out/zero-cache/src/types/sql.d.ts.map +1 -1
  195. package/out/zero-cache/src/types/sql.js +5 -1
  196. package/out/zero-cache/src/types/sql.js.map +1 -1
  197. package/out/zero-cache/src/types/subscription.js +1 -1
  198. package/out/zero-cache/src/types/subscription.js.map +1 -1
  199. package/out/zero-cache/src/workers/connect-params.d.ts +1 -0
  200. package/out/zero-cache/src/workers/connect-params.d.ts.map +1 -1
  201. package/out/zero-cache/src/workers/connect-params.js +2 -1
  202. package/out/zero-cache/src/workers/connect-params.js.map +1 -1
  203. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  204. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +14 -6
  205. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  206. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  207. package/out/zero-cache/src/workers/syncer.js +17 -10
  208. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  209. package/out/zero-client/src/client/connection-manager.d.ts +8 -0
  210. package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
  211. package/out/zero-client/src/client/connection-manager.js +33 -0
  212. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  213. package/out/zero-client/src/client/connection.d.ts.map +1 -1
  214. package/out/zero-client/src/client/connection.js +6 -3
  215. package/out/zero-client/src/client/connection.js.map +1 -1
  216. package/out/zero-client/src/client/error.js +1 -1
  217. package/out/zero-client/src/client/error.js.map +1 -1
  218. package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
  219. package/out/zero-client/src/client/mutator-proxy.js +15 -1
  220. package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
  221. package/out/zero-client/src/client/options.d.ts +10 -0
  222. package/out/zero-client/src/client/options.d.ts.map +1 -1
  223. package/out/zero-client/src/client/options.js.map +1 -1
  224. package/out/zero-client/src/client/query-manager.d.ts +4 -0
  225. package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
  226. package/out/zero-client/src/client/query-manager.js +7 -0
  227. package/out/zero-client/src/client/query-manager.js.map +1 -1
  228. package/out/zero-client/src/client/version.js +1 -1
  229. package/out/zero-client/src/client/zero.d.ts +3 -1
  230. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  231. package/out/zero-client/src/client/zero.js +52 -7
  232. package/out/zero-client/src/client/zero.js.map +1 -1
  233. package/out/zero-client/src/mod.d.ts +1 -0
  234. package/out/zero-client/src/mod.d.ts.map +1 -1
  235. package/out/zero-protocol/src/connect.d.ts +4 -0
  236. package/out/zero-protocol/src/connect.d.ts.map +1 -1
  237. package/out/zero-protocol/src/connect.js +3 -1
  238. package/out/zero-protocol/src/connect.js.map +1 -1
  239. package/out/zero-protocol/src/protocol-version.d.ts +1 -1
  240. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  241. package/out/zero-protocol/src/protocol-version.js +1 -1
  242. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  243. package/out/zero-protocol/src/push.d.ts +11 -2
  244. package/out/zero-protocol/src/push.d.ts.map +1 -1
  245. package/out/zero-protocol/src/push.js +22 -6
  246. package/out/zero-protocol/src/push.js.map +1 -1
  247. package/out/zero-protocol/src/up.d.ts +2 -0
  248. package/out/zero-protocol/src/up.d.ts.map +1 -1
  249. package/out/zero-react/src/mod.d.ts +3 -1
  250. package/out/zero-react/src/mod.d.ts.map +1 -1
  251. package/out/zero-react/src/paging-reducer.d.ts +61 -0
  252. package/out/zero-react/src/paging-reducer.d.ts.map +1 -0
  253. package/out/zero-react/src/paging-reducer.js +77 -0
  254. package/out/zero-react/src/paging-reducer.js.map +1 -0
  255. package/out/zero-react/src/use-query.d.ts +11 -1
  256. package/out/zero-react/src/use-query.d.ts.map +1 -1
  257. package/out/zero-react/src/use-query.js +13 -11
  258. package/out/zero-react/src/use-query.js.map +1 -1
  259. package/out/zero-react/src/use-rows.d.ts +39 -0
  260. package/out/zero-react/src/use-rows.d.ts.map +1 -0
  261. package/out/zero-react/src/use-rows.js +130 -0
  262. package/out/zero-react/src/use-rows.js.map +1 -0
  263. package/out/zero-react/src/use-zero-virtualizer.d.ts +122 -0
  264. package/out/zero-react/src/use-zero-virtualizer.d.ts.map +1 -0
  265. package/out/zero-react/src/use-zero-virtualizer.js +342 -0
  266. package/out/zero-react/src/use-zero-virtualizer.js.map +1 -0
  267. package/out/zero-react/src/zero-provider.js +1 -1
  268. package/out/zero-react/src/zero-provider.js.map +1 -1
  269. package/out/zero-server/src/adapters/drizzle.d.ts +18 -18
  270. package/out/zero-server/src/adapters/drizzle.d.ts.map +1 -1
  271. package/out/zero-server/src/adapters/drizzle.js +8 -22
  272. package/out/zero-server/src/adapters/drizzle.js.map +1 -1
  273. package/out/zero-server/src/adapters/pg.d.ts +19 -13
  274. package/out/zero-server/src/adapters/pg.d.ts.map +1 -1
  275. package/out/zero-server/src/adapters/pg.js.map +1 -1
  276. package/out/zero-server/src/adapters/postgresjs.d.ts +19 -13
  277. package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
  278. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  279. package/out/zero-server/src/adapters/prisma.d.ts +66 -0
  280. package/out/zero-server/src/adapters/prisma.d.ts.map +1 -0
  281. package/out/zero-server/src/adapters/prisma.js +63 -0
  282. package/out/zero-server/src/adapters/prisma.js.map +1 -0
  283. package/out/zero-server/src/custom.js +1 -15
  284. package/out/zero-server/src/custom.js.map +1 -1
  285. package/out/zero-server/src/mod.d.ts +9 -8
  286. package/out/zero-server/src/mod.d.ts.map +1 -1
  287. package/out/zero-server/src/process-mutations.d.ts +2 -2
  288. package/out/zero-server/src/process-mutations.d.ts.map +1 -1
  289. package/out/zero-server/src/process-mutations.js +4 -8
  290. package/out/zero-server/src/process-mutations.js.map +1 -1
  291. package/out/zero-server/src/push-processor.js +1 -1
  292. package/out/zero-server/src/push-processor.js.map +1 -1
  293. package/out/zero-server/src/schema.d.ts.map +1 -1
  294. package/out/zero-server/src/schema.js +4 -1
  295. package/out/zero-server/src/schema.js.map +1 -1
  296. package/out/zero-server/src/zql-database.d.ts.map +1 -1
  297. package/out/zero-server/src/zql-database.js +17 -8
  298. package/out/zero-server/src/zql-database.js.map +1 -1
  299. package/out/zero-solid/src/mod.d.ts +1 -1
  300. package/out/zero-solid/src/mod.d.ts.map +1 -1
  301. package/out/zero-solid/src/use-query.d.ts +10 -1
  302. package/out/zero-solid/src/use-query.d.ts.map +1 -1
  303. package/out/zero-solid/src/use-query.js +21 -5
  304. package/out/zero-solid/src/use-query.js.map +1 -1
  305. package/out/zero-solid/src/use-zero.js +1 -1
  306. package/out/zero-solid/src/use-zero.js.map +1 -1
  307. package/out/zql/src/ivm/constraint.d.ts.map +1 -1
  308. package/out/zql/src/ivm/constraint.js +4 -1
  309. package/out/zql/src/ivm/constraint.js.map +1 -1
  310. package/out/zql/src/ivm/exists.d.ts.map +1 -1
  311. package/out/zql/src/ivm/exists.js +4 -1
  312. package/out/zql/src/ivm/exists.js.map +1 -1
  313. package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
  314. package/out/zql/src/ivm/join-utils.js +8 -2
  315. package/out/zql/src/ivm/join-utils.js.map +1 -1
  316. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  317. package/out/zql/src/ivm/memory-source.js +12 -3
  318. package/out/zql/src/ivm/memory-source.js.map +1 -1
  319. package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
  320. package/out/zql/src/ivm/push-accumulated.js +25 -2
  321. package/out/zql/src/ivm/push-accumulated.js.map +1 -1
  322. package/out/zql/src/ivm/take.d.ts.map +1 -1
  323. package/out/zql/src/ivm/take.js +24 -6
  324. package/out/zql/src/ivm/take.js.map +1 -1
  325. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  326. package/out/zql/src/ivm/union-fan-in.js +12 -3
  327. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  328. package/out/zqlite/src/table-source.d.ts.map +1 -1
  329. package/out/zqlite/src/table-source.js +1 -2
  330. package/out/zqlite/src/table-source.js.map +1 -1
  331. package/package.json +6 -2
  332. package/out/zero-cache/src/services/change-source/column-metadata.d.ts.map +0 -1
  333. package/out/zero-cache/src/services/change-source/column-metadata.js.map +0 -1
@@ -52,7 +52,10 @@ class Subscriber {
52
52
  */
53
53
  setCaughtUp() {
54
54
  this.#ensureInitialStatusSent();
55
- assert(this.#backlog);
55
+ assert(
56
+ this.#backlog,
57
+ "setCaughtUp() called but subscriber is not in catchup mode"
58
+ );
56
59
  for (const change of this.#backlog) {
57
60
  this.#send(change);
58
61
  }
@@ -63,6 +66,9 @@ class Subscriber {
63
66
  if (watermark <= this.watermark) {
64
67
  return ALREADY_CONSUMED_RESULT;
65
68
  }
69
+ if (!this.supportsMessage(downstream[1])) {
70
+ return ALREADY_CONSUMED_RESULT;
71
+ }
66
72
  const pending = this.#downstream.push(downstream);
67
73
  if (downstream[0] === "commit") {
68
74
  this.#watermark = watermark;
@@ -74,6 +80,13 @@ class Subscriber {
74
80
  }
75
81
  return pending;
76
82
  }
83
+ supportsMessage(change) {
84
+ switch (change.tag) {
85
+ case "update-table-metadata":
86
+ return this.#protocolVersion >= 5;
87
+ }
88
+ return true;
89
+ }
77
90
  fail(err) {
78
91
  this.close(Unknown, String(err));
79
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"subscriber.js","sources":["../../../../../../zero-cache/src/services/change-streamer/subscriber.ts"],"sourcesContent":["import {assert} from '../../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../../shared/src/enum.ts';\nimport {max} from '../../types/lexi-version.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {type PendingResult} from '../../types/subscription.ts';\nimport type {WatermarkedChange} from './change-streamer-service.ts';\nimport {type Downstream} from './change-streamer.ts';\nimport * as ErrorType from './error-type-enum.ts';\n\ntype ErrorType = Enum<typeof ErrorType>;\n\n/**\n * Encapsulates a subscriber to changes. All subscribers start in a\n * \"catchup\" phase in which changes are buffered in a backlog while the\n * storer is queried to send any changes that were committed since the\n * subscriber's watermark. Once the catchup is complete, calls to\n * {@link send()} result in immediately sending the change.\n */\nexport class Subscriber {\n readonly #protocolVersion: number;\n readonly id: string;\n readonly #downstream: Subscription<Downstream>;\n #watermark: string;\n #acked: string;\n #backlog: WatermarkedChange[] | null;\n\n constructor(\n protocolVersion: number,\n id: string,\n watermark: string,\n downstream: Subscription<Downstream>,\n ) {\n this.#protocolVersion = protocolVersion;\n this.id = id;\n this.#downstream = downstream;\n this.#watermark = watermark;\n this.#acked = watermark;\n this.#backlog = [];\n }\n\n get watermark() {\n return this.#watermark;\n }\n\n get acked() {\n return this.#acked;\n }\n\n send(change: WatermarkedChange) {\n const [watermark] = change;\n if (watermark > this.#watermark) {\n if (this.#backlog) {\n this.#backlog.push(change);\n } else {\n this.#send(change);\n }\n }\n }\n\n #initialStatusSent = false;\n\n #ensureInitialStatusSent() {\n if (this.#protocolVersion >= 2 && !this.#initialStatusSent) {\n this.#downstream.push(['status', {tag: 'status'}]);\n this.#initialStatusSent = true;\n }\n }\n\n /** catchup() is called on ChangeEntries loaded from the store. */\n catchup(change: WatermarkedChange): PendingResult {\n this.#ensureInitialStatusSent();\n return this.#send(change);\n }\n\n /**\n * Marks the Subscribe as \"caught up\" and flushes any backlog of\n * entries that were received during the catchup.\n */\n setCaughtUp() {\n this.#ensureInitialStatusSent();\n assert(this.#backlog);\n for (const change of this.#backlog) {\n this.#send(change);\n }\n this.#backlog = null;\n }\n\n #send(change: WatermarkedChange): PendingResult {\n const [watermark, downstream] = change;\n if (watermark <= this.watermark) {\n return ALREADY_CONSUMED_RESULT;\n }\n const pending = this.#downstream.push(downstream);\n if (downstream[0] === 'commit') {\n this.#watermark = watermark;\n void pending.result.then(val => {\n if (val === 'consumed') {\n this.#acked = max(this.#acked, watermark);\n }\n });\n }\n return pending;\n }\n\n fail(err?: unknown) {\n this.close(ErrorType.Unknown, String(err));\n }\n\n close(error?: ErrorType, message?: string) {\n if (error) {\n const {result} = this.#downstream.push(['error', {type: error, message}]);\n // Wait for the ACK of the error message before closing the connection.\n void result.then(() => this.#downstream.cancel());\n } else {\n this.#downstream.cancel();\n }\n }\n}\n\nconst ALREADY_CONSUMED_RESULT: PendingResult = {\n result: Promise.resolve('consumed'),\n};\n"],"names":["ErrorType.Unknown"],"mappings":";;;;;AAkBO,MAAM,WAAW;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YACE,iBACA,IACA,WACA,YACA;AACA,SAAK,mBAAmB;AACxB,SAAK,KAAK;AACV,SAAK,cAAc;AACnB,SAAK,aAAa;AAClB,SAAK,SAAS;AACd,SAAK,WAAW,CAAA;AAAA,EAClB;AAAA,EAEA,IAAI,YAAY;AACd,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAQ;AACV,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,QAA2B;AAC9B,UAAM,CAAC,SAAS,IAAI;AACpB,QAAI,YAAY,KAAK,YAAY;AAC/B,UAAI,KAAK,UAAU;AACjB,aAAK,SAAS,KAAK,MAAM;AAAA,MAC3B,OAAO;AACL,aAAK,MAAM,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,qBAAqB;AAAA,EAErB,2BAA2B;AACzB,QAAI,KAAK,oBAAoB,KAAK,CAAC,KAAK,oBAAoB;AAC1D,WAAK,YAAY,KAAK,CAAC,UAAU,EAAC,KAAK,SAAA,CAAS,CAAC;AACjD,WAAK,qBAAqB;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA,EAGA,QAAQ,QAA0C;AAChD,SAAK,yBAAA;AACL,WAAO,KAAK,MAAM,MAAM;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc;AACZ,SAAK,yBAAA;AACL,WAAO,KAAK,QAAQ;AACpB,eAAW,UAAU,KAAK,UAAU;AAClC,WAAK,MAAM,MAAM;AAAA,IACnB;AACA,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,QAA0C;AAC9C,UAAM,CAAC,WAAW,UAAU,IAAI;AAChC,QAAI,aAAa,KAAK,WAAW;AAC/B,aAAO;AAAA,IACT;AACA,UAAM,UAAU,KAAK,YAAY,KAAK,UAAU;AAChD,QAAI,WAAW,CAAC,MAAM,UAAU;AAC9B,WAAK,aAAa;AAClB,WAAK,QAAQ,OAAO,KAAK,CAAA,QAAO;AAC9B,YAAI,QAAQ,YAAY;AACtB,eAAK,SAAS,IAAI,KAAK,QAAQ,SAAS;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,KAAe;AAClB,SAAK,MAAMA,SAAmB,OAAO,GAAG,CAAC;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAmB,SAAkB;AACzC,QAAI,OAAO;AACT,YAAM,EAAC,OAAA,IAAU,KAAK,YAAY,KAAK,CAAC,SAAS,EAAC,MAAM,OAAO,QAAA,CAAQ,CAAC;AAExE,WAAK,OAAO,KAAK,MAAM,KAAK,YAAY,QAAQ;AAAA,IAClD,OAAO;AACL,WAAK,YAAY,OAAA;AAAA,IACnB;AAAA,EACF;AACF;AAEA,MAAM,0BAAyC;AAAA,EAC7C,QAAQ,QAAQ,QAAQ,UAAU;AACpC;"}
1
+ {"version":3,"file":"subscriber.js","sources":["../../../../../../zero-cache/src/services/change-streamer/subscriber.ts"],"sourcesContent":["import {assert} from '../../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../../shared/src/enum.ts';\nimport {max} from '../../types/lexi-version.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {type PendingResult} from '../../types/subscription.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current.ts';\nimport type {WatermarkedChange} from './change-streamer-service.ts';\nimport {type Downstream} from './change-streamer.ts';\nimport * as ErrorType from './error-type-enum.ts';\n\ntype ErrorType = Enum<typeof ErrorType>;\n\n/**\n * Encapsulates a subscriber to changes. All subscribers start in a\n * \"catchup\" phase in which changes are buffered in a backlog while the\n * storer is queried to send any changes that were committed since the\n * subscriber's watermark. Once the catchup is complete, calls to\n * {@link send()} result in immediately sending the change.\n */\nexport class Subscriber {\n readonly #protocolVersion: number;\n readonly id: string;\n readonly #downstream: Subscription<Downstream>;\n #watermark: string;\n #acked: string;\n #backlog: WatermarkedChange[] | null;\n\n constructor(\n protocolVersion: number,\n id: string,\n watermark: string,\n downstream: Subscription<Downstream>,\n ) {\n this.#protocolVersion = protocolVersion;\n this.id = id;\n this.#downstream = downstream;\n this.#watermark = watermark;\n this.#acked = watermark;\n this.#backlog = [];\n }\n\n get watermark() {\n return this.#watermark;\n }\n\n get acked() {\n return this.#acked;\n }\n\n send(change: WatermarkedChange) {\n const [watermark] = change;\n if (watermark > this.#watermark) {\n if (this.#backlog) {\n this.#backlog.push(change);\n } else {\n this.#send(change);\n }\n }\n }\n\n #initialStatusSent = false;\n\n #ensureInitialStatusSent() {\n if (this.#protocolVersion >= 2 && !this.#initialStatusSent) {\n this.#downstream.push(['status', {tag: 'status'}]);\n this.#initialStatusSent = true;\n }\n }\n\n /** catchup() is called on ChangeEntries loaded from the store. */\n catchup(change: WatermarkedChange): PendingResult {\n this.#ensureInitialStatusSent();\n return this.#send(change);\n }\n\n /**\n * Marks the Subscribe as \"caught up\" and flushes any backlog of\n * entries that were received during the catchup.\n */\n setCaughtUp() {\n this.#ensureInitialStatusSent();\n assert(\n this.#backlog,\n 'setCaughtUp() called but subscriber is not in catchup mode',\n );\n for (const change of this.#backlog) {\n this.#send(change);\n }\n this.#backlog = null;\n }\n\n #send(change: WatermarkedChange): PendingResult {\n const [watermark, downstream] = change;\n if (watermark <= this.watermark) {\n return ALREADY_CONSUMED_RESULT;\n }\n if (!this.supportsMessage(downstream[1])) {\n return ALREADY_CONSUMED_RESULT;\n }\n const pending = this.#downstream.push(downstream);\n if (downstream[0] === 'commit') {\n this.#watermark = watermark;\n void pending.result.then(val => {\n if (val === 'consumed') {\n this.#acked = max(this.#acked, watermark);\n }\n });\n }\n return pending;\n }\n\n supportsMessage(change: ChangeStreamData[1]) {\n switch (change.tag) {\n case 'update-table-metadata':\n // update-table-row-key is only understood by subscribers >= protocol v5\n return this.#protocolVersion >= 5;\n }\n return true;\n }\n\n fail(err?: unknown) {\n this.close(ErrorType.Unknown, String(err));\n }\n\n close(error?: ErrorType, message?: string) {\n if (error) {\n const {result} = this.#downstream.push(['error', {type: error, message}]);\n // Wait for the ACK of the error message before closing the connection.\n void result.then(() => this.#downstream.cancel());\n } else {\n this.#downstream.cancel();\n }\n }\n}\n\nconst ALREADY_CONSUMED_RESULT: PendingResult = {\n result: Promise.resolve('consumed'),\n};\n"],"names":["ErrorType.Unknown"],"mappings":";;;;;AAmBO,MAAM,WAAW;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YACE,iBACA,IACA,WACA,YACA;AACA,SAAK,mBAAmB;AACxB,SAAK,KAAK;AACV,SAAK,cAAc;AACnB,SAAK,aAAa;AAClB,SAAK,SAAS;AACd,SAAK,WAAW,CAAA;AAAA,EAClB;AAAA,EAEA,IAAI,YAAY;AACd,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAQ;AACV,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,QAA2B;AAC9B,UAAM,CAAC,SAAS,IAAI;AACpB,QAAI,YAAY,KAAK,YAAY;AAC/B,UAAI,KAAK,UAAU;AACjB,aAAK,SAAS,KAAK,MAAM;AAAA,MAC3B,OAAO;AACL,aAAK,MAAM,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,qBAAqB;AAAA,EAErB,2BAA2B;AACzB,QAAI,KAAK,oBAAoB,KAAK,CAAC,KAAK,oBAAoB;AAC1D,WAAK,YAAY,KAAK,CAAC,UAAU,EAAC,KAAK,SAAA,CAAS,CAAC;AACjD,WAAK,qBAAqB;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA,EAGA,QAAQ,QAA0C;AAChD,SAAK,yBAAA;AACL,WAAO,KAAK,MAAM,MAAM;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc;AACZ,SAAK,yBAAA;AACL;AAAA,MACE,KAAK;AAAA,MACL;AAAA,IAAA;AAEF,eAAW,UAAU,KAAK,UAAU;AAClC,WAAK,MAAM,MAAM;AAAA,IACnB;AACA,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,QAA0C;AAC9C,UAAM,CAAC,WAAW,UAAU,IAAI;AAChC,QAAI,aAAa,KAAK,WAAW;AAC/B,aAAO;AAAA,IACT;AACA,QAAI,CAAC,KAAK,gBAAgB,WAAW,CAAC,CAAC,GAAG;AACxC,aAAO;AAAA,IACT;AACA,UAAM,UAAU,KAAK,YAAY,KAAK,UAAU;AAChD,QAAI,WAAW,CAAC,MAAM,UAAU;AAC9B,WAAK,aAAa;AAClB,WAAK,QAAQ,OAAO,KAAK,CAAA,QAAO;AAC9B,YAAI,QAAQ,YAAY;AACtB,eAAK,SAAS,IAAI,KAAK,QAAQ,SAAS;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,gBAAgB,QAA6B;AAC3C,YAAQ,OAAO,KAAA;AAAA,MACb,KAAK;AAEH,eAAO,KAAK,oBAAoB;AAAA,IAAA;AAEpC,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,KAAe;AAClB,SAAK,MAAMA,SAAmB,OAAO,GAAG,CAAC;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAmB,SAAkB;AACzC,QAAI,OAAO;AACT,YAAM,EAAC,OAAA,IAAU,KAAK,YAAY,KAAK,CAAC,SAAS,EAAC,MAAM,OAAO,QAAA,CAAQ,CAAC;AAExE,WAAK,OAAO,KAAK,MAAM,KAAK,YAAY,QAAQ;AAAA,IAClD,OAAO;AACL,WAAK,YAAY,OAAA;AAAA,IACnB;AAAA,EACF;AACF;AAEA,MAAM,0BAAyC;AAAA,EAC7C,QAAQ,QAAQ,QAAQ,UAAU;AACpC;"}
@@ -1 +1 @@
1
- {"version":3,"file":"heapz.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/services/heapz.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,SAAS,CAAC;AAG1D,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAGjE,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,oBAAoB,EAC5B,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,YAAY,QAyBlB"}
1
+ {"version":3,"file":"heapz.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/services/heapz.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,SAAS,CAAC;AAG1D,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAGjE,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,oBAAoB,EAC5B,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,YAAY,QA0BlB"}
@@ -6,6 +6,7 @@ function handleHeapzRequest(lc, config, req, res) {
6
6
  const credentials = auth(req);
7
7
  if (!isAdminPasswordValid(lc, config, credentials?.pass)) {
8
8
  void res.code(401).header("WWW-Authenticate", 'Basic realm="Heapz Protected Area"').send("Unauthorized");
9
+ return;
9
10
  }
10
11
  const filename = v8.writeHeapSnapshot();
11
12
  const stream = fs.createReadStream(filename);
@@ -1 +1 @@
1
- {"version":3,"file":"heapz.js","sources":["../../../../../zero-cache/src/services/heapz.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport auth from 'basic-auth';\nimport type {FastifyReply, FastifyRequest} from 'fastify';\nimport fs from 'fs';\nimport v8 from 'v8';\nimport type {NormalizedZeroConfig} from '../config/normalize.ts';\nimport {isAdminPasswordValid} from '../config/zero-config.ts';\n\nexport function handleHeapzRequest(\n lc: LogContext,\n config: NormalizedZeroConfig,\n req: FastifyRequest,\n res: FastifyReply,\n) {\n const credentials = auth(req);\n if (!isAdminPasswordValid(lc, config, credentials?.pass)) {\n void res\n .code(401)\n .header('WWW-Authenticate', 'Basic realm=\"Heapz Protected Area\"')\n .send('Unauthorized');\n }\n\n const filename = v8.writeHeapSnapshot();\n const stream = fs.createReadStream(filename);\n void res\n .header('Content-Type', 'application/octet-stream')\n .header('Content-Disposition', `attachment; filename=${filename}`)\n .send(stream);\n\n // Clean up temp file after streaming\n stream.on('end', () => {\n fs.unlink(filename, err => {\n if (err) {\n lc.error?.('Error deleting heap snapshot:', err);\n }\n });\n });\n}\n"],"names":[],"mappings":";;;;AAQO,SAAS,mBACd,IACA,QACA,KACA,KACA;AACA,QAAM,cAAc,KAAK,GAAG;AAC5B,MAAI,CAAC,qBAAqB,IAAI,QAAQ,aAAa,IAAI,GAAG;AACxD,SAAK,IACF,KAAK,GAAG,EACR,OAAO,oBAAoB,oCAAoC,EAC/D,KAAK,cAAc;AAAA,EACxB;AAEA,QAAM,WAAW,GAAG,kBAAA;AACpB,QAAM,SAAS,GAAG,iBAAiB,QAAQ;AAC3C,OAAK,IACF,OAAO,gBAAgB,0BAA0B,EACjD,OAAO,uBAAuB,wBAAwB,QAAQ,EAAE,EAChE,KAAK,MAAM;AAGd,SAAO,GAAG,OAAO,MAAM;AACrB,OAAG,OAAO,UAAU,CAAA,QAAO;AACzB,UAAI,KAAK;AACP,WAAG,QAAQ,iCAAiC,GAAG;AAAA,MACjD;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;"}
1
+ {"version":3,"file":"heapz.js","sources":["../../../../../zero-cache/src/services/heapz.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport auth from 'basic-auth';\nimport type {FastifyReply, FastifyRequest} from 'fastify';\nimport fs from 'fs';\nimport v8 from 'v8';\nimport type {NormalizedZeroConfig} from '../config/normalize.ts';\nimport {isAdminPasswordValid} from '../config/zero-config.ts';\n\nexport function handleHeapzRequest(\n lc: LogContext,\n config: NormalizedZeroConfig,\n req: FastifyRequest,\n res: FastifyReply,\n) {\n const credentials = auth(req);\n if (!isAdminPasswordValid(lc, config, credentials?.pass)) {\n void res\n .code(401)\n .header('WWW-Authenticate', 'Basic realm=\"Heapz Protected Area\"')\n .send('Unauthorized');\n return;\n }\n\n const filename = v8.writeHeapSnapshot();\n const stream = fs.createReadStream(filename);\n void res\n .header('Content-Type', 'application/octet-stream')\n .header('Content-Disposition', `attachment; filename=${filename}`)\n .send(stream);\n\n // Clean up temp file after streaming\n stream.on('end', () => {\n fs.unlink(filename, err => {\n if (err) {\n lc.error?.('Error deleting heap snapshot:', err);\n }\n });\n });\n}\n"],"names":[],"mappings":";;;;AAQO,SAAS,mBACd,IACA,QACA,KACA,KACA;AACA,QAAM,cAAc,KAAK,GAAG;AAC5B,MAAI,CAAC,qBAAqB,IAAI,QAAQ,aAAa,IAAI,GAAG;AACxD,SAAK,IACF,KAAK,GAAG,EACR,OAAO,oBAAoB,oCAAoC,EAC/D,KAAK,cAAc;AACtB;AAAA,EACF;AAEA,QAAM,WAAW,GAAG,kBAAA;AACpB,QAAM,SAAS,GAAG,iBAAiB,QAAQ;AAC3C,OAAK,IACF,OAAO,gBAAgB,0BAA0B,EACjD,OAAO,uBAAuB,wBAAwB,QAAQ,EAAE,EAChE,KAAK,MAAM;AAGd,SAAO,GAAG,OAAO,MAAM;AACrB,OAAG,OAAO,UAAU,CAAA,QAAO;AACzB,UAAI,KAAK;AACP,WAAG,QAAQ,iCAAiC,GAAG;AAAA,MACjD;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;"}
@@ -1 +1 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/mutagen/error.ts"],"names":[],"mappings":"AAEA,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;CAMxE"}
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/mutagen/error.ts"],"names":[],"mappings":"AAEA,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;CAUxE"}
@@ -4,7 +4,10 @@ class MutationAlreadyProcessedError extends Error {
4
4
  super(
5
5
  `Ignoring mutation from ${clientID} with ID ${received} as it was already processed. Expected: ${actual}`
6
6
  );
7
- assert(received < actual);
7
+ assert(
8
+ received < actual,
9
+ () => `MutationAlreadyProcessedError: received (${received}) must be < actual (${actual})`
10
+ );
8
11
  }
9
12
  }
10
13
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"error.js","sources":["../../../../../../zero-cache/src/services/mutagen/error.ts"],"sourcesContent":["import {assert} from '../../../../shared/src/asserts.ts';\n\nexport class MutationAlreadyProcessedError extends Error {\n constructor(clientID: string, received: number, actual: number | bigint) {\n super(\n `Ignoring mutation from ${clientID} with ID ${received} as it was already processed. Expected: ${actual}`,\n );\n assert(received < actual);\n }\n}\n"],"names":[],"mappings":";AAEO,MAAM,sCAAsC,MAAM;AAAA,EACvD,YAAY,UAAkB,UAAkB,QAAyB;AACvE;AAAA,MACE,0BAA0B,QAAQ,YAAY,QAAQ,2CAA2C,MAAM;AAAA,IAAA;AAEzG,WAAO,WAAW,MAAM;AAAA,EAC1B;AACF;"}
1
+ {"version":3,"file":"error.js","sources":["../../../../../../zero-cache/src/services/mutagen/error.ts"],"sourcesContent":["import {assert} from '../../../../shared/src/asserts.ts';\n\nexport class MutationAlreadyProcessedError extends Error {\n constructor(clientID: string, received: number, actual: number | bigint) {\n super(\n `Ignoring mutation from ${clientID} with ID ${received} as it was already processed. Expected: ${actual}`,\n );\n assert(\n received < actual,\n () =>\n `MutationAlreadyProcessedError: received (${received}) must be < actual (${actual})`,\n );\n }\n}\n"],"names":[],"mappings":";AAEO,MAAM,sCAAsC,MAAM;AAAA,EACvD,YAAY,UAAkB,UAAkB,QAAyB;AACvE;AAAA,MACE,0BAA0B,QAAQ,YAAY,QAAQ,2CAA2C,MAAM;AAAA,IAAA;AAEzG;AAAA,MACE,WAAW;AAAA,MACX,MACE,4CAA4C,QAAQ,uBAAuB,MAAM;AAAA,IAAA;AAAA,EAEvF;AACF;"}
@@ -1 +1 @@
1
- {"version":3,"file":"mutagen.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/mutagen/mutagen.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mutagen.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/mutagen/mutagen.ts"],"names":[],"mappings":"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"}
@@ -191,6 +191,7 @@ async function processMutationWithTx(lc, tx, authData, shard, clientGroupID, mut
191
191
  authorizer.reloadPermissions();
192
192
  if (!errorMode) {
193
193
  const { ops } = mutation.args[0];
194
+ authorizer.validateTableNames(ops);
194
195
  const normalizedOps = authorizer.normalizeOps(ops);
195
196
  const [canPre, canPost] = await Promise.all([
196
197
  authorizer.canPreMutation(authData, normalizedOps),
@@ -1 +1 @@
1
- {"version":3,"file":"mutagen.js","sources":["../../../../../../zero-cache/src/services/mutagen/mutagen.ts"],"sourcesContent":["import {PG_SERIALIZATION_FAILURE} from '@drdgvhbh/postgres-error-codes';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport type {JWTPayload} from 'jose';\nimport postgres from 'postgres';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport {\n isProtocolError,\n ProtocolError,\n} from '../../../../zero-protocol/src/error.ts';\nimport * as MutationType from '../../../../zero-protocol/src/mutation-type-enum.ts';\nimport {\n primaryKeyValueSchema,\n type PrimaryKeyValue,\n} from '../../../../zero-protocol/src/primary-key.ts';\nimport {\n type CRUDMutation,\n type DeleteOp,\n type InsertOp,\n type Mutation,\n type UpdateOp,\n type UpsertOp,\n} from '../../../../zero-protocol/src/push.ts';\nimport type {DatabaseStorage} from '../../../../zqlite/src/database-storage.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {\n WriteAuthorizerImpl,\n type WriteAuthorizer,\n} from '../../auth/write-authorizer.ts';\nimport {type ZeroConfig} from '../../config/zero-config.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport {recordMutation} from '../../server/anonymous-otel-start.ts';\nimport type {PostgresDB, PostgresTransaction} from '../../types/pg.ts';\nimport {upstreamSchema, type ShardID} from '../../types/shards.ts';\nimport {SlidingWindowLimiter} from '../limiter/sliding-window-limiter.ts';\nimport type {RefCountedService, Service} from '../service.ts';\nimport {MutationAlreadyProcessedError} from './error.ts';\n\n// An error encountered processing a mutation.\n// Returned back to application for display to user.\nexport type MutationError = [\n kind: ErrorKind.MutationFailed | ErrorKind.MutationRateLimited,\n desc: string,\n];\n\nexport interface Mutagen extends RefCountedService {\n processMutation(\n mutation: Mutation,\n authData: JWTPayload | undefined,\n customMutatorsEnabled: boolean,\n ): Promise<MutationError | undefined>;\n}\n\nexport class MutagenService implements Mutagen, Service {\n readonly id: string;\n readonly #lc: LogContext;\n readonly #upstream: PostgresDB;\n readonly #shard: ShardID;\n readonly #stopped = resolver();\n readonly #replica: Database;\n readonly #writeAuthorizer: WriteAuthorizerImpl;\n readonly #limiter: SlidingWindowLimiter | undefined;\n #refCount = 0;\n #isStopped = false;\n\n readonly #crudMutations = getOrCreateCounter(\n 'mutation',\n 'crud',\n 'Number of CRUD mutations processed',\n );\n\n constructor(\n lc: LogContext,\n shard: ShardID,\n clientGroupID: string,\n upstream: PostgresDB,\n config: ZeroConfig,\n writeAuthzStorage: DatabaseStorage,\n ) {\n this.id = clientGroupID;\n this.#lc = lc;\n this.#upstream = upstream;\n this.#shard = shard;\n this.#replica = new Database(this.#lc, config.replica.file, {\n fileMustExist: true,\n });\n this.#writeAuthorizer = new WriteAuthorizerImpl(\n this.#lc,\n config,\n this.#replica,\n shard.appID,\n clientGroupID,\n writeAuthzStorage,\n );\n\n if (config.perUserMutationLimit.max !== undefined) {\n this.#limiter = new SlidingWindowLimiter(\n config.perUserMutationLimit.windowMs,\n config.perUserMutationLimit.max,\n );\n }\n }\n\n ref() {\n assert(!this.#isStopped, 'MutagenService is already stopped');\n ++this.#refCount;\n }\n\n unref() {\n assert(!this.#isStopped, 'MutagenService is already stopped');\n --this.#refCount;\n if (this.#refCount <= 0) {\n void this.stop();\n }\n }\n\n hasRefs(): boolean {\n return this.#refCount > 0;\n }\n\n processMutation(\n mutation: Mutation,\n authData: JWTPayload | undefined,\n customMutatorsEnabled = false,\n ): Promise<MutationError | undefined> {\n if (this.#limiter?.canDo() === false) {\n return Promise.resolve([\n ErrorKind.MutationRateLimited,\n 'Rate limit exceeded',\n ]);\n }\n this.#crudMutations.add(1, {\n clientGroupID: this.id,\n });\n return processMutation(\n this.#lc,\n authData,\n this.#upstream,\n this.#shard,\n this.id,\n mutation,\n this.#writeAuthorizer,\n undefined,\n customMutatorsEnabled,\n );\n }\n\n run(): Promise<void> {\n return this.#stopped.promise;\n }\n\n stop(): Promise<void> {\n if (this.#isStopped) {\n return this.#stopped.promise;\n }\n this.#writeAuthorizer.destroy();\n this.#isStopped = true;\n this.#stopped.resolve();\n return this.#stopped.promise;\n }\n}\n\nconst MAX_SERIALIZATION_ATTEMPTS = 10;\n\nexport async function processMutation(\n lc: LogContext,\n authData: JWTPayload | undefined,\n db: PostgresDB,\n shard: ShardID,\n clientGroupID: string,\n mutation: Mutation,\n writeAuthorizer: WriteAuthorizer,\n onTxStart?: () => void | Promise<void>, // for testing\n customMutatorsEnabled = false,\n): Promise<MutationError | undefined> {\n assert(\n mutation.type === MutationType.CRUD,\n 'Only CRUD mutations are supported',\n );\n lc = lc.withContext('mutationID', mutation.id);\n lc = lc.withContext('processMutation');\n lc.debug?.('Process mutation start', mutation);\n\n // Record mutation processing attempt for telemetry (regardless of success/failure)\n recordMutation('crud');\n\n let result: MutationError | undefined;\n\n const start = Date.now();\n try {\n // Mutations can fail for a variety of reasons:\n //\n // - application error\n // - network/db error\n // - zero bug\n //\n // For application errors what we want is to re-run the mutation in\n // \"error mode\", which skips the actual mutation and just updates the\n // lastMutationID. Then return the error to the app.\n //\n // However, it's hard to tell the difference between application errors\n // and the other types.\n //\n // A reasonable policy ends up being to just retry every mutation once\n // in error mode. If the error mode mutation succeeds then we assume it\n // was an application error and return the error to the app. Otherwise,\n // we know it was something internal and we log it.\n //\n // This is not 100% correct - there are theoretical cases where we\n // return an internal error to the app that shouldn't have been. But it\n // would have to be a crazy coincidence: we'd have to have a network\n // error on the first attempt that resolves by the second attempt.\n //\n // One might ask why not try/catch just the calls to the mutators and\n // consider those application errors. That is actually what we do in\n // Replicache:\n //\n // https://github.com/rocicorp/todo-row-versioning/blob/9a0a79dc2d2de32c4fac61b5d1634bd9a9e66b7c/server/src/push.ts#L131\n //\n // We don't do it here because:\n //\n // 1. It's still not perfect. It's hard to isolate SQL errors in\n // mutators due to app developer mistakes from SQL errors due to\n // Zero mistakes.\n // 2. It's not possible to do this with the pg library we're using in\n // Zero anyway: https://github.com/porsager/postgres/issues/455.\n //\n // Personally I think this simple retry policy is nice.\n let errorMode = false;\n for (let i = 0; i < MAX_SERIALIZATION_ATTEMPTS; i++) {\n try {\n await db.begin(Mode.SERIALIZABLE, async tx => {\n // Simulates a concurrent request for testing. In production this is a noop.\n const done = onTxStart?.();\n try {\n return await processMutationWithTx(\n lc,\n tx,\n authData,\n shard,\n clientGroupID,\n mutation,\n errorMode,\n writeAuthorizer,\n );\n } finally {\n await done;\n }\n });\n if (errorMode) {\n lc.debug?.('Ran mutation successfully in error mode');\n }\n break;\n } catch (e) {\n if (e instanceof MutationAlreadyProcessedError) {\n lc.debug?.(e.message);\n // Don't double-count already processed mutations, but they were counted above\n return undefined;\n }\n if (\n isProtocolError(e) &&\n !errorMode &&\n e.kind === ErrorKind.InvalidPush &&\n customMutatorsEnabled &&\n i < 2\n ) {\n // We're temporarily supporting custom mutators AND CRUD mutators at the same time.\n // This can create a lot of OOO mutation errors since we do not know when the API server\n // has applied a custom mutation before moving on to process CRUD mutations.\n // The temporary workaround (since CRUD is being deprecated) is to retry the mutation\n // after a small delay. Users are not expected to be running both CRUD and Custom mutators.\n // They should migrate completely to custom mutators.\n lc.info?.(\n 'Both CRUD and Custom mutators are being used at once. This is supported for now but IS NOT RECOMMENDED. Migrate completely to custom mutators.',\n e,\n );\n await new Promise(resolve => setTimeout(resolve, 100));\n continue;\n }\n if (isProtocolError(e) || errorMode) {\n lc.error?.('Process mutation error', e);\n throw e;\n }\n if (\n e instanceof postgres.PostgresError &&\n e.code === PG_SERIALIZATION_FAILURE\n ) {\n lc.info?.(`attempt ${i + 1}: ${String(e)}`, e);\n continue; // Retry up to MAX_SERIALIZATION_ATTEMPTS.\n }\n result = [ErrorKind.MutationFailed, String(e)];\n if (errorMode) {\n break;\n }\n lc.error?.('Got error running mutation, re-running in error mode', e);\n errorMode = true;\n i--;\n }\n }\n } finally {\n lc.debug?.('Process mutation complete in', Date.now() - start);\n }\n return result;\n}\n\nexport async function processMutationWithTx(\n lc: LogContext,\n tx: PostgresTransaction,\n authData: JWTPayload | undefined,\n shard: ShardID,\n clientGroupID: string,\n mutation: CRUDMutation,\n errorMode: boolean,\n authorizer: WriteAuthorizer,\n) {\n const tasks: (() => Promise<unknown>)[] = [];\n\n async function execute(stmt: postgres.PendingQuery<postgres.Row[]>) {\n try {\n return await stmt.execute();\n } finally {\n const q = stmt as unknown as Query;\n lc.debug?.(`${q.string}: ${JSON.stringify(q.parameters)}`);\n }\n }\n\n authorizer.reloadPermissions();\n\n if (!errorMode) {\n const {ops} = mutation.args[0];\n const normalizedOps = authorizer.normalizeOps(ops);\n const [canPre, canPost] = await Promise.all([\n authorizer.canPreMutation(authData, normalizedOps),\n authorizer.canPostMutation(authData, normalizedOps),\n ]);\n if (canPre && canPost) {\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n tasks.push(() => execute(getInsertSQL(tx, op)));\n break;\n case 'upsert':\n tasks.push(() => execute(getUpsertSQL(tx, op)));\n break;\n case 'update':\n tasks.push(() => execute(getUpdateSQL(tx, op)));\n break;\n case 'delete':\n tasks.push(() => execute(getDeleteSQL(tx, op)));\n break;\n default:\n unreachable(op);\n }\n }\n }\n }\n\n // Confirm the mutation even though it may have been blocked by the authorizer.\n // Authorizer blocking a mutation is not an error but the correct result of the mutation.\n tasks.unshift(() =>\n checkSchemaVersionAndIncrementLastMutationID(\n tx,\n shard,\n clientGroupID,\n mutation.clientID,\n mutation.id,\n ),\n );\n\n // Note: An error thrown from any Promise aborts the entire transaction.\n await Promise.all(tasks.map(task => task()));\n}\n\nexport function getInsertSQL(\n tx: postgres.TransactionSql,\n create: InsertOp,\n): postgres.PendingQuery<postgres.Row[]> {\n return tx`INSERT INTO ${tx(create.tableName)} ${tx(create.value)}`;\n}\n\nexport function getUpsertSQL(\n tx: postgres.TransactionSql,\n set: UpsertOp,\n): postgres.PendingQuery<postgres.Row[]> {\n const {tableName, primaryKey, value} = set;\n return tx`\n INSERT INTO ${tx(tableName)} ${tx(value)}\n ON CONFLICT (${tx(primaryKey)})\n DO UPDATE SET ${tx(value)}\n `;\n}\n\nfunction getUpdateSQL(\n tx: postgres.TransactionSql,\n update: UpdateOp,\n): postgres.PendingQuery<postgres.Row[]> {\n const table = update.tableName;\n const {primaryKey, value} = update;\n const id: Record<string, PrimaryKeyValue> = {};\n for (const key of primaryKey) {\n id[key] = v.parse(value[key], primaryKeyValueSchema);\n }\n return tx`UPDATE ${tx(table)} SET ${tx(value)} WHERE ${Object.entries(\n id,\n ).flatMap(([key, value], i) =>\n i ? [tx`AND`, tx`${tx(key)} = ${value}`] : tx`${tx(key)} = ${value}`,\n )}`;\n}\n\nfunction getDeleteSQL(\n tx: postgres.TransactionSql,\n deleteOp: DeleteOp,\n): postgres.PendingQuery<postgres.Row[]> {\n const {tableName, primaryKey, value} = deleteOp;\n\n const conditions = [];\n for (const key of primaryKey) {\n if (conditions.length > 0) {\n conditions.push(tx`AND`);\n }\n conditions.push(tx`${tx(key)} = ${value[key]}`);\n }\n\n return tx`DELETE FROM ${tx(tableName)} WHERE ${conditions}`;\n}\n\nasync function checkSchemaVersionAndIncrementLastMutationID(\n tx: PostgresTransaction,\n shard: ShardID,\n clientGroupID: string,\n clientID: string,\n receivedMutationID: number,\n) {\n const [{lastMutationID}] = await tx<{lastMutationID: bigint}[]>`\n INSERT INTO ${tx(upstreamSchema(shard))}.clients \n as current (\"clientGroupID\", \"clientID\", \"lastMutationID\")\n VALUES (${clientGroupID}, ${clientID}, ${1})\n ON CONFLICT (\"clientGroupID\", \"clientID\")\n DO UPDATE SET \"lastMutationID\" = current.\"lastMutationID\" + 1\n RETURNING \"lastMutationID\"\n `;\n\n // ABORT if the resulting lastMutationID is not equal to the receivedMutationID.\n if (receivedMutationID < lastMutationID) {\n throw new MutationAlreadyProcessedError(\n clientID,\n receivedMutationID,\n lastMutationID,\n );\n } else if (receivedMutationID > lastMutationID) {\n throw new ProtocolError({\n kind: ErrorKind.InvalidPush,\n message: `Push contains unexpected mutation id ${receivedMutationID} for client ${clientID}. Expected mutation id ${lastMutationID.toString()}.`,\n origin: ErrorOrigin.ZeroCache,\n });\n }\n}\n\n// The slice of information from the Query object in Postgres.js that gets logged for debugging.\n// https://github.com/porsager/postgres/blob/f58cd4f3affd3e8ce8f53e42799672d86cd2c70b/src/connection.js#L219\ntype Query = {string: string; parameters: object[]};\n"],"names":["ErrorKind.MutationRateLimited","MutationType.CRUD","Mode.SERIALIZABLE","ErrorKind.InvalidPush","ErrorKind.MutationFailed","v.parse","value","ErrorOrigin.ZeroCache"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyDO,MAAM,eAA2C;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,SAAA;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY;AAAA,EACZ,aAAa;AAAA,EAEJ,iBAAiB;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAGF,YACE,IACA,OACA,eACA,UACA,QACA,mBACA;AACA,SAAK,KAAK;AACV,SAAK,MAAM;AACX,SAAK,YAAY;AACjB,SAAK,SAAS;AACd,SAAK,WAAW,IAAI,SAAS,KAAK,KAAK,OAAO,QAAQ,MAAM;AAAA,MAC1D,eAAe;AAAA,IAAA,CAChB;AACD,SAAK,mBAAmB,IAAI;AAAA,MAC1B,KAAK;AAAA,MACL;AAAA,MACA,KAAK;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IAAA;AAGF,QAAI,OAAO,qBAAqB,QAAQ,QAAW;AACjD,WAAK,WAAW,IAAI;AAAA,QAClB,OAAO,qBAAqB;AAAA,QAC5B,OAAO,qBAAqB;AAAA,MAAA;AAAA,IAEhC;AAAA,EACF;AAAA,EAEA,MAAM;AACJ,WAAO,CAAC,KAAK,YAAY,mCAAmC;AAC5D,MAAE,KAAK;AAAA,EACT;AAAA,EAEA,QAAQ;AACN,WAAO,CAAC,KAAK,YAAY,mCAAmC;AAC5D,MAAE,KAAK;AACP,QAAI,KAAK,aAAa,GAAG;AACvB,WAAK,KAAK,KAAA;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,UAAmB;AACjB,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA,EAEA,gBACE,UACA,UACA,wBAAwB,OACY;AACpC,QAAI,KAAK,UAAU,MAAA,MAAY,OAAO;AACpC,aAAO,QAAQ,QAAQ;AAAA,QACrBA;AAAAA,QACA;AAAA,MAAA,CACD;AAAA,IACH;AACA,SAAK,eAAe,IAAI,GAAG;AAAA,MACzB,eAAe,KAAK;AAAA,IAAA,CACrB;AACD,WAAO;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAqB;AACnB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,OAAsB;AACpB,QAAI,KAAK,YAAY;AACnB,aAAO,KAAK,SAAS;AAAA,IACvB;AACA,SAAK,iBAAiB,QAAA;AACtB,SAAK,aAAa;AAClB,SAAK,SAAS,QAAA;AACd,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;AAEA,MAAM,6BAA6B;AAEnC,eAAsB,gBACpB,IACA,UACA,IACA,OACA,eACA,UACA,iBACA,WACA,wBAAwB,OACY;AACpC;AAAA,IACE,SAAS,SAASC;AAAAA,IAClB;AAAA,EAAA;AAEF,OAAK,GAAG,YAAY,cAAc,SAAS,EAAE;AAC7C,OAAK,GAAG,YAAY,iBAAiB;AACrC,KAAG,QAAQ,0BAA0B,QAAQ;AAG7C,iBAAe,MAAM;AAErB,MAAI;AAEJ,QAAM,QAAQ,KAAK,IAAA;AACnB,MAAI;AAuCF,QAAI,YAAY;AAChB,aAAS,IAAI,GAAG,IAAI,4BAA4B,KAAK;AACnD,UAAI;AACF,cAAM,GAAG,MAAMC,cAAmB,OAAM,OAAM;AAE5C,gBAAM,OAAO,YAAA;AACb,cAAI;AACF,mBAAO,MAAM;AAAA,cACX;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,UAEJ,UAAA;AACE,kBAAM;AAAA,UACR;AAAA,QACF,CAAC;AACD,YAAI,WAAW;AACb,aAAG,QAAQ,yCAAyC;AAAA,QACtD;AACA;AAAA,MACF,SAAS,GAAG;AACV,YAAI,aAAa,+BAA+B;AAC9C,aAAG,QAAQ,EAAE,OAAO;AAEpB,iBAAO;AAAA,QACT;AACA,YACE,gBAAgB,CAAC,KACjB,CAAC,aACD,EAAE,SAASC,eACX,yBACA,IAAI,GACJ;AAOA,aAAG;AAAA,YACD;AAAA,YACA;AAAA,UAAA;AAEF,gBAAM,IAAI,QAAQ,CAAA,YAAW,WAAW,SAAS,GAAG,CAAC;AACrD;AAAA,QACF;AACA,YAAI,gBAAgB,CAAC,KAAK,WAAW;AACnC,aAAG,QAAQ,0BAA0B,CAAC;AACtC,gBAAM;AAAA,QACR;AACA,YACE,aAAa,SAAS,iBACtB,EAAE,SAAS,0BACX;AACA,aAAG,OAAO,WAAW,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC;AAC7C;AAAA,QACF;AACA,iBAAS,CAACC,gBAA0B,OAAO,CAAC,CAAC;AAC7C,YAAI,WAAW;AACb;AAAA,QACF;AACA,WAAG,QAAQ,wDAAwD,CAAC;AACpE,oBAAY;AACZ;AAAA,MACF;AAAA,IACF;AAAA,EACF,UAAA;AACE,OAAG,QAAQ,gCAAgC,KAAK,IAAA,IAAQ,KAAK;AAAA,EAC/D;AACA,SAAO;AACT;AAEA,eAAsB,sBACpB,IACA,IACA,UACA,OACA,eACA,UACA,WACA,YACA;AACA,QAAM,QAAoC,CAAA;AAE1C,iBAAe,QAAQ,MAA6C;AAClE,QAAI;AACF,aAAO,MAAM,KAAK,QAAA;AAAA,IACpB,UAAA;AACE,YAAM,IAAI;AACV,SAAG,QAAQ,GAAG,EAAE,MAAM,KAAK,KAAK,UAAU,EAAE,UAAU,CAAC,EAAE;AAAA,IAC3D;AAAA,EACF;AAEA,aAAW,kBAAA;AAEX,MAAI,CAAC,WAAW;AACd,UAAM,EAAC,IAAA,IAAO,SAAS,KAAK,CAAC;AAC7B,UAAM,gBAAgB,WAAW,aAAa,GAAG;AACjD,UAAM,CAAC,QAAQ,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC1C,WAAW,eAAe,UAAU,aAAa;AAAA,MACjD,WAAW,gBAAgB,UAAU,aAAa;AAAA,IAAA,CACnD;AACD,QAAI,UAAU,SAAS;AACrB,iBAAW,MAAM,KAAK;AACpB,gBAAQ,GAAG,IAAA;AAAA,UACT,KAAK;AACH,kBAAM,KAAK,MAAM,QAAQ,aAAa,IAAI,EAAE,CAAC,CAAC;AAC9C;AAAA,UACF,KAAK;AACH,kBAAM,KAAK,MAAM,QAAQ,aAAa,IAAI,EAAE,CAAC,CAAC;AAC9C;AAAA,UACF,KAAK;AACH,kBAAM,KAAK,MAAM,QAAQ,aAAa,IAAI,EAAE,CAAC,CAAC;AAC9C;AAAA,UACF,KAAK;AACH,kBAAM,KAAK,MAAM,QAAQ,aAAa,IAAI,EAAE,CAAC,CAAC;AAC9C;AAAA,UACF;AACE,wBAAc;AAAA,QAAA;AAAA,MAEpB;AAAA,IACF;AAAA,EACF;AAIA,QAAM;AAAA,IAAQ,MACZ;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,IAAA;AAAA,EACX;AAIF,QAAM,QAAQ,IAAI,MAAM,IAAI,CAAA,SAAQ,KAAA,CAAM,CAAC;AAC7C;AAEO,SAAS,aACd,IACA,QACuC;AACvC,SAAO,iBAAiB,GAAG,OAAO,SAAS,CAAC,IAAI,GAAG,OAAO,KAAK,CAAC;AAClE;AAEO,SAAS,aACd,IACA,KACuC;AACvC,QAAM,EAAC,WAAW,YAAY,MAAA,IAAS;AACvC,SAAO;AAAA,kBACS,GAAG,SAAS,CAAC,IAAI,GAAG,KAAK,CAAC;AAAA,mBACzB,GAAG,UAAU,CAAC;AAAA,oBACb,GAAG,KAAK,CAAC;AAAA;AAE7B;AAEA,SAAS,aACP,IACA,QACuC;AACvC,QAAM,QAAQ,OAAO;AACrB,QAAM,EAAC,YAAY,MAAA,IAAS;AAC5B,QAAM,KAAsC,CAAA;AAC5C,aAAW,OAAO,YAAY;AAC5B,OAAG,GAAG,IAAIC,MAAQ,MAAM,GAAG,GAAG,qBAAqB;AAAA,EACrD;AACA,SAAO,YAAY,GAAG,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,UAAU,OAAO;AAAA,IAC5D;AAAA,EAAA,EACA;AAAA,IAAQ,CAAC,CAAC,KAAKC,MAAK,GAAG,MACvB,IAAI,CAAC,SAAS,KAAK,GAAG,GAAG,CAAC,MAAMA,MAAK,EAAE,IAAI,KAAK,GAAG,GAAG,CAAC,MAAMA,MAAK;AAAA,EAAA,CACnE;AACH;AAEA,SAAS,aACP,IACA,UACuC;AACvC,QAAM,EAAC,WAAW,YAAY,MAAA,IAAS;AAEvC,QAAM,aAAa,CAAA;AACnB,aAAW,OAAO,YAAY;AAC5B,QAAI,WAAW,SAAS,GAAG;AACzB,iBAAW,KAAK,OAAO;AAAA,IACzB;AACA,eAAW,KAAK,KAAK,GAAG,GAAG,CAAC,MAAM,MAAM,GAAG,CAAC,EAAE;AAAA,EAChD;AAEA,SAAO,iBAAiB,GAAG,SAAS,CAAC,UAAU,UAAU;AAC3D;AAEA,eAAe,6CACb,IACA,OACA,eACA,UACA,oBACA;AACA,QAAM,CAAC,EAAC,gBAAe,IAAI,MAAM;AAAA,kBACjB,GAAG,eAAe,KAAK,CAAC,CAAC;AAAA;AAAA,oBAEvB,aAAa,KAAK,QAAQ,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAOlD,MAAI,qBAAqB,gBAAgB;AACvC,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ,WAAW,qBAAqB,gBAAgB;AAC9C,UAAM,IAAI,cAAc;AAAA,MACtB,MAAMH;AAAAA,MACN,SAAS,wCAAwC,kBAAkB,eAAe,QAAQ,0BAA0B,eAAe,UAAU;AAAA,MAC7I,QAAQI;AAAAA,IAAY,CACrB;AAAA,EACH;AACF;"}
1
+ {"version":3,"file":"mutagen.js","sources":["../../../../../../zero-cache/src/services/mutagen/mutagen.ts"],"sourcesContent":["import {PG_SERIALIZATION_FAILURE} from '@drdgvhbh/postgres-error-codes';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport type {JWTPayload} from 'jose';\nimport postgres from 'postgres';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport {\n isProtocolError,\n ProtocolError,\n} from '../../../../zero-protocol/src/error.ts';\nimport * as MutationType from '../../../../zero-protocol/src/mutation-type-enum.ts';\nimport {\n primaryKeyValueSchema,\n type PrimaryKeyValue,\n} from '../../../../zero-protocol/src/primary-key.ts';\nimport {\n type CRUDMutation,\n type DeleteOp,\n type InsertOp,\n type Mutation,\n type UpdateOp,\n type UpsertOp,\n} from '../../../../zero-protocol/src/push.ts';\nimport type {DatabaseStorage} from '../../../../zqlite/src/database-storage.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {\n WriteAuthorizerImpl,\n type WriteAuthorizer,\n} from '../../auth/write-authorizer.ts';\nimport {type ZeroConfig} from '../../config/zero-config.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport {recordMutation} from '../../server/anonymous-otel-start.ts';\nimport type {PostgresDB, PostgresTransaction} from '../../types/pg.ts';\nimport {upstreamSchema, type ShardID} from '../../types/shards.ts';\nimport {SlidingWindowLimiter} from '../limiter/sliding-window-limiter.ts';\nimport type {RefCountedService, Service} from '../service.ts';\nimport {MutationAlreadyProcessedError} from './error.ts';\n\n// An error encountered processing a mutation.\n// Returned back to application for display to user.\nexport type MutationError = [\n kind: ErrorKind.MutationFailed | ErrorKind.MutationRateLimited,\n desc: string,\n];\n\nexport interface Mutagen extends RefCountedService {\n processMutation(\n mutation: Mutation,\n authData: JWTPayload | undefined,\n customMutatorsEnabled: boolean,\n ): Promise<MutationError | undefined>;\n}\n\nexport class MutagenService implements Mutagen, Service {\n readonly id: string;\n readonly #lc: LogContext;\n readonly #upstream: PostgresDB;\n readonly #shard: ShardID;\n readonly #stopped = resolver();\n readonly #replica: Database;\n readonly #writeAuthorizer: WriteAuthorizerImpl;\n readonly #limiter: SlidingWindowLimiter | undefined;\n #refCount = 0;\n #isStopped = false;\n\n readonly #crudMutations = getOrCreateCounter(\n 'mutation',\n 'crud',\n 'Number of CRUD mutations processed',\n );\n\n constructor(\n lc: LogContext,\n shard: ShardID,\n clientGroupID: string,\n upstream: PostgresDB,\n config: ZeroConfig,\n writeAuthzStorage: DatabaseStorage,\n ) {\n this.id = clientGroupID;\n this.#lc = lc;\n this.#upstream = upstream;\n this.#shard = shard;\n this.#replica = new Database(this.#lc, config.replica.file, {\n fileMustExist: true,\n });\n this.#writeAuthorizer = new WriteAuthorizerImpl(\n this.#lc,\n config,\n this.#replica,\n shard.appID,\n clientGroupID,\n writeAuthzStorage,\n );\n\n if (config.perUserMutationLimit.max !== undefined) {\n this.#limiter = new SlidingWindowLimiter(\n config.perUserMutationLimit.windowMs,\n config.perUserMutationLimit.max,\n );\n }\n }\n\n ref() {\n assert(!this.#isStopped, 'MutagenService is already stopped');\n ++this.#refCount;\n }\n\n unref() {\n assert(!this.#isStopped, 'MutagenService is already stopped');\n --this.#refCount;\n if (this.#refCount <= 0) {\n void this.stop();\n }\n }\n\n hasRefs(): boolean {\n return this.#refCount > 0;\n }\n\n processMutation(\n mutation: Mutation,\n authData: JWTPayload | undefined,\n customMutatorsEnabled = false,\n ): Promise<MutationError | undefined> {\n if (this.#limiter?.canDo() === false) {\n return Promise.resolve([\n ErrorKind.MutationRateLimited,\n 'Rate limit exceeded',\n ]);\n }\n this.#crudMutations.add(1, {\n clientGroupID: this.id,\n });\n return processMutation(\n this.#lc,\n authData,\n this.#upstream,\n this.#shard,\n this.id,\n mutation,\n this.#writeAuthorizer,\n undefined,\n customMutatorsEnabled,\n );\n }\n\n run(): Promise<void> {\n return this.#stopped.promise;\n }\n\n stop(): Promise<void> {\n if (this.#isStopped) {\n return this.#stopped.promise;\n }\n this.#writeAuthorizer.destroy();\n this.#isStopped = true;\n this.#stopped.resolve();\n return this.#stopped.promise;\n }\n}\n\nconst MAX_SERIALIZATION_ATTEMPTS = 10;\n\nexport async function processMutation(\n lc: LogContext,\n authData: JWTPayload | undefined,\n db: PostgresDB,\n shard: ShardID,\n clientGroupID: string,\n mutation: Mutation,\n writeAuthorizer: WriteAuthorizer,\n onTxStart?: () => void | Promise<void>, // for testing\n customMutatorsEnabled = false,\n): Promise<MutationError | undefined> {\n assert(\n mutation.type === MutationType.CRUD,\n 'Only CRUD mutations are supported',\n );\n lc = lc.withContext('mutationID', mutation.id);\n lc = lc.withContext('processMutation');\n lc.debug?.('Process mutation start', mutation);\n\n // Record mutation processing attempt for telemetry (regardless of success/failure)\n recordMutation('crud');\n\n let result: MutationError | undefined;\n\n const start = Date.now();\n try {\n // Mutations can fail for a variety of reasons:\n //\n // - application error\n // - network/db error\n // - zero bug\n //\n // For application errors what we want is to re-run the mutation in\n // \"error mode\", which skips the actual mutation and just updates the\n // lastMutationID. Then return the error to the app.\n //\n // However, it's hard to tell the difference between application errors\n // and the other types.\n //\n // A reasonable policy ends up being to just retry every mutation once\n // in error mode. If the error mode mutation succeeds then we assume it\n // was an application error and return the error to the app. Otherwise,\n // we know it was something internal and we log it.\n //\n // This is not 100% correct - there are theoretical cases where we\n // return an internal error to the app that shouldn't have been. But it\n // would have to be a crazy coincidence: we'd have to have a network\n // error on the first attempt that resolves by the second attempt.\n //\n // One might ask why not try/catch just the calls to the mutators and\n // consider those application errors. That is actually what we do in\n // Replicache:\n //\n // https://github.com/rocicorp/todo-row-versioning/blob/9a0a79dc2d2de32c4fac61b5d1634bd9a9e66b7c/server/src/push.ts#L131\n //\n // We don't do it here because:\n //\n // 1. It's still not perfect. It's hard to isolate SQL errors in\n // mutators due to app developer mistakes from SQL errors due to\n // Zero mistakes.\n // 2. It's not possible to do this with the pg library we're using in\n // Zero anyway: https://github.com/porsager/postgres/issues/455.\n //\n // Personally I think this simple retry policy is nice.\n let errorMode = false;\n for (let i = 0; i < MAX_SERIALIZATION_ATTEMPTS; i++) {\n try {\n await db.begin(Mode.SERIALIZABLE, async tx => {\n // Simulates a concurrent request for testing. In production this is a noop.\n const done = onTxStart?.();\n try {\n return await processMutationWithTx(\n lc,\n tx,\n authData,\n shard,\n clientGroupID,\n mutation,\n errorMode,\n writeAuthorizer,\n );\n } finally {\n await done;\n }\n });\n if (errorMode) {\n lc.debug?.('Ran mutation successfully in error mode');\n }\n break;\n } catch (e) {\n if (e instanceof MutationAlreadyProcessedError) {\n lc.debug?.(e.message);\n // Don't double-count already processed mutations, but they were counted above\n return undefined;\n }\n if (\n isProtocolError(e) &&\n !errorMode &&\n e.kind === ErrorKind.InvalidPush &&\n customMutatorsEnabled &&\n i < 2\n ) {\n // We're temporarily supporting custom mutators AND CRUD mutators at the same time.\n // This can create a lot of OOO mutation errors since we do not know when the API server\n // has applied a custom mutation before moving on to process CRUD mutations.\n // The temporary workaround (since CRUD is being deprecated) is to retry the mutation\n // after a small delay. Users are not expected to be running both CRUD and Custom mutators.\n // They should migrate completely to custom mutators.\n lc.info?.(\n 'Both CRUD and Custom mutators are being used at once. This is supported for now but IS NOT RECOMMENDED. Migrate completely to custom mutators.',\n e,\n );\n await new Promise(resolve => setTimeout(resolve, 100));\n continue;\n }\n if (isProtocolError(e) || errorMode) {\n lc.error?.('Process mutation error', e);\n throw e;\n }\n if (\n e instanceof postgres.PostgresError &&\n e.code === PG_SERIALIZATION_FAILURE\n ) {\n lc.info?.(`attempt ${i + 1}: ${String(e)}`, e);\n continue; // Retry up to MAX_SERIALIZATION_ATTEMPTS.\n }\n result = [ErrorKind.MutationFailed, String(e)];\n if (errorMode) {\n break;\n }\n lc.error?.('Got error running mutation, re-running in error mode', e);\n errorMode = true;\n i--;\n }\n }\n } finally {\n lc.debug?.('Process mutation complete in', Date.now() - start);\n }\n return result;\n}\n\nexport async function processMutationWithTx(\n lc: LogContext,\n tx: PostgresTransaction,\n authData: JWTPayload | undefined,\n shard: ShardID,\n clientGroupID: string,\n mutation: CRUDMutation,\n errorMode: boolean,\n authorizer: WriteAuthorizer,\n) {\n const tasks: (() => Promise<unknown>)[] = [];\n\n async function execute(stmt: postgres.PendingQuery<postgres.Row[]>) {\n try {\n return await stmt.execute();\n } finally {\n const q = stmt as unknown as Query;\n lc.debug?.(`${q.string}: ${JSON.stringify(q.parameters)}`);\n }\n }\n\n authorizer.reloadPermissions();\n\n if (!errorMode) {\n const {ops} = mutation.args[0];\n\n // Validate all table names before processing\n authorizer.validateTableNames(ops);\n\n const normalizedOps = authorizer.normalizeOps(ops);\n const [canPre, canPost] = await Promise.all([\n authorizer.canPreMutation(authData, normalizedOps),\n authorizer.canPostMutation(authData, normalizedOps),\n ]);\n if (canPre && canPost) {\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n tasks.push(() => execute(getInsertSQL(tx, op)));\n break;\n case 'upsert':\n tasks.push(() => execute(getUpsertSQL(tx, op)));\n break;\n case 'update':\n tasks.push(() => execute(getUpdateSQL(tx, op)));\n break;\n case 'delete':\n tasks.push(() => execute(getDeleteSQL(tx, op)));\n break;\n default:\n unreachable(op);\n }\n }\n }\n }\n\n // Confirm the mutation even though it may have been blocked by the authorizer.\n // Authorizer blocking a mutation is not an error but the correct result of the mutation.\n tasks.unshift(() =>\n checkSchemaVersionAndIncrementLastMutationID(\n tx,\n shard,\n clientGroupID,\n mutation.clientID,\n mutation.id,\n ),\n );\n\n // Note: An error thrown from any Promise aborts the entire transaction.\n await Promise.all(tasks.map(task => task()));\n}\n\nexport function getInsertSQL(\n tx: postgres.TransactionSql,\n create: InsertOp,\n): postgres.PendingQuery<postgres.Row[]> {\n return tx`INSERT INTO ${tx(create.tableName)} ${tx(create.value)}`;\n}\n\nexport function getUpsertSQL(\n tx: postgres.TransactionSql,\n set: UpsertOp,\n): postgres.PendingQuery<postgres.Row[]> {\n const {tableName, primaryKey, value} = set;\n return tx`\n INSERT INTO ${tx(tableName)} ${tx(value)}\n ON CONFLICT (${tx(primaryKey)})\n DO UPDATE SET ${tx(value)}\n `;\n}\n\nfunction getUpdateSQL(\n tx: postgres.TransactionSql,\n update: UpdateOp,\n): postgres.PendingQuery<postgres.Row[]> {\n const table = update.tableName;\n const {primaryKey, value} = update;\n const id: Record<string, PrimaryKeyValue> = {};\n for (const key of primaryKey) {\n id[key] = v.parse(value[key], primaryKeyValueSchema);\n }\n return tx`UPDATE ${tx(table)} SET ${tx(value)} WHERE ${Object.entries(\n id,\n ).flatMap(([key, value], i) =>\n i ? [tx`AND`, tx`${tx(key)} = ${value}`] : tx`${tx(key)} = ${value}`,\n )}`;\n}\n\nfunction getDeleteSQL(\n tx: postgres.TransactionSql,\n deleteOp: DeleteOp,\n): postgres.PendingQuery<postgres.Row[]> {\n const {tableName, primaryKey, value} = deleteOp;\n\n const conditions = [];\n for (const key of primaryKey) {\n if (conditions.length > 0) {\n conditions.push(tx`AND`);\n }\n conditions.push(tx`${tx(key)} = ${value[key]}`);\n }\n\n return tx`DELETE FROM ${tx(tableName)} WHERE ${conditions}`;\n}\n\nasync function checkSchemaVersionAndIncrementLastMutationID(\n tx: PostgresTransaction,\n shard: ShardID,\n clientGroupID: string,\n clientID: string,\n receivedMutationID: number,\n) {\n const [{lastMutationID}] = await tx<{lastMutationID: bigint}[]>`\n INSERT INTO ${tx(upstreamSchema(shard))}.clients \n as current (\"clientGroupID\", \"clientID\", \"lastMutationID\")\n VALUES (${clientGroupID}, ${clientID}, ${1})\n ON CONFLICT (\"clientGroupID\", \"clientID\")\n DO UPDATE SET \"lastMutationID\" = current.\"lastMutationID\" + 1\n RETURNING \"lastMutationID\"\n `;\n\n // ABORT if the resulting lastMutationID is not equal to the receivedMutationID.\n if (receivedMutationID < lastMutationID) {\n throw new MutationAlreadyProcessedError(\n clientID,\n receivedMutationID,\n lastMutationID,\n );\n } else if (receivedMutationID > lastMutationID) {\n throw new ProtocolError({\n kind: ErrorKind.InvalidPush,\n message: `Push contains unexpected mutation id ${receivedMutationID} for client ${clientID}. Expected mutation id ${lastMutationID.toString()}.`,\n origin: ErrorOrigin.ZeroCache,\n });\n }\n}\n\n// The slice of information from the Query object in Postgres.js that gets logged for debugging.\n// https://github.com/porsager/postgres/blob/f58cd4f3affd3e8ce8f53e42799672d86cd2c70b/src/connection.js#L219\ntype Query = {string: string; parameters: object[]};\n"],"names":["ErrorKind.MutationRateLimited","MutationType.CRUD","Mode.SERIALIZABLE","ErrorKind.InvalidPush","ErrorKind.MutationFailed","v.parse","value","ErrorOrigin.ZeroCache"],"mappings":"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"}
@@ -8,9 +8,10 @@ import type { HandlerResult, StreamResult } from '../../workers/connection.ts';
8
8
  import type { RefCountedService, Service } from '../service.ts';
9
9
  export interface Pusher extends RefCountedService {
10
10
  readonly pushURL: string | undefined;
11
- initConnection(clientID: string, wsID: string, userPushURL: string | undefined): Source<Downstream>;
12
- enqueuePush(clientID: string, push: PushBody, auth: string | undefined, httpCookie: string | undefined): HandlerResult;
11
+ initConnection(clientID: string, wsID: string, userPushURL: string | undefined, userPushHeaders: Record<string, string> | undefined): Source<Downstream>;
12
+ enqueuePush(clientID: string, push: PushBody, auth: string | undefined, httpCookie: string | undefined, origin: string | undefined): HandlerResult;
13
13
  ackMutationResponses(upToID: MutationID): Promise<void>;
14
+ deleteClientMutations(clientIDs: string[]): Promise<void>;
14
15
  }
15
16
  type Config = Pick<ZeroConfig, 'app' | 'shard'>;
16
17
  /**
@@ -33,7 +34,7 @@ export declare class PusherService implements Service, Pusher {
33
34
  url: string[];
34
35
  }, lc: LogContext, clientGroupID: string);
35
36
  get pushURL(): string | undefined;
36
- initConnection(clientID: string, wsID: string, userPushURL: string | undefined): Subscription<["deleteClients", {
37
+ initConnection(clientID: string, wsID: string, userPushURL: string | undefined, userPushHeaders: Record<string, string> | undefined): Subscription<["deleteClients", {
37
38
  readonly clientIDs?: readonly string[] | undefined;
38
39
  readonly clientGroupIDs?: readonly string[] | undefined;
39
40
  }] | ["connected", {
@@ -632,8 +633,9 @@ export declare class PusherService implements Service, Pusher {
632
633
  requestID: string;
633
634
  lastMutationIDChanges: Record<string, number>;
634
635
  }]>;
635
- enqueuePush(clientID: string, push: PushBody, auth: string | undefined, httpCookie: string | undefined): Exclude<HandlerResult, StreamResult>;
636
+ enqueuePush(clientID: string, push: PushBody, auth: string | undefined, httpCookie: string | undefined, origin: string | undefined): Exclude<HandlerResult, StreamResult>;
636
637
  ackMutationResponses(upToID: MutationID): Promise<void>;
638
+ deleteClientMutations(clientIDs: string[]): Promise<void>;
637
639
  ref(): void;
638
640
  unref(): void;
639
641
  hasRefs(): boolean;
@@ -644,6 +646,7 @@ type PusherEntry = {
644
646
  push: PushBody;
645
647
  auth: string | undefined;
646
648
  httpCookie: string | undefined;
649
+ origin: string | undefined;
647
650
  clientID: string;
648
651
  };
649
652
  type PusherEntryOrStop = PusherEntry | 'stop';
@@ -1 +1 @@
1
- {"version":3,"file":"pusher.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/mutagen/pusher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pusher.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/mutagen/pusher.ts"],"names":[],"mappings":"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"}
@@ -7,8 +7,8 @@ import { PushFailed } from "../../../../zero-protocol/src/error-kind-enum.js";
7
7
  import { ZeroCache, Server } from "../../../../zero-protocol/src/error-origin-enum.js";
8
8
  import { HTTP, UnsupportedPushVersion, Internal, OutOfOrderMutation } from "../../../../zero-protocol/src/error-reason-enum.js";
9
9
  import { isProtocolError } from "../../../../zero-protocol/src/error.js";
10
- import { CLEANUP_RESULTS_MUTATION_NAME, pushResponseSchema } from "../../../../zero-protocol/src/push.js";
11
10
  import { Custom } from "../../../../zero-protocol/src/mutation-type-enum.js";
11
+ import { CLEANUP_RESULTS_MUTATION_NAME, pushResponseSchema } from "../../../../zero-protocol/src/push.js";
12
12
  import "../../config/zero-config.js";
13
13
  import { compileUrlPattern, fetchFromAPIServer } from "../../custom/fetch.js";
14
14
  import { getOrCreateCounter } from "../../observability/metrics.js";
@@ -36,6 +36,7 @@ class PusherService {
36
36
  lc,
37
37
  pushConfig.url,
38
38
  pushConfig.apiKey,
39
+ pushConfig.allowedClientHeaders,
39
40
  this.#queue
40
41
  );
41
42
  this.id = clientGroupID;
@@ -44,14 +45,19 @@ class PusherService {
44
45
  get pushURL() {
45
46
  return this.#pusher.pushURL[0];
46
47
  }
47
- initConnection(clientID, wsID, userPushURL) {
48
- return this.#pusher.initConnection(clientID, wsID, userPushURL);
48
+ initConnection(clientID, wsID, userPushURL, userPushHeaders) {
49
+ return this.#pusher.initConnection(
50
+ clientID,
51
+ wsID,
52
+ userPushURL,
53
+ userPushHeaders
54
+ );
49
55
  }
50
- enqueuePush(clientID, push, auth, httpCookie) {
56
+ enqueuePush(clientID, push, auth, httpCookie, origin) {
51
57
  if (!this.#pushConfig.forwardCookies) {
52
58
  httpCookie = void 0;
53
59
  }
54
- this.#queue.enqueue({ push, auth, clientID, httpCookie });
60
+ this.#queue.enqueue({ push, auth, clientID, httpCookie, origin });
55
61
  return {
56
62
  type: "ok"
57
63
  };
@@ -72,6 +78,7 @@ class PusherService {
72
78
  name: CLEANUP_RESULTS_MUTATION_NAME,
73
79
  args: [
74
80
  {
81
+ type: "single",
75
82
  clientGroupID: this.id,
76
83
  clientID: upToID.clientID,
77
84
  upToMutationID: upToID.id
@@ -102,6 +109,56 @@ class PusherService {
102
109
  });
103
110
  }
104
111
  }
112
+ async deleteClientMutations(clientIDs) {
113
+ if (clientIDs.length === 0) {
114
+ return;
115
+ }
116
+ const url = this.#pushConfig.url[0];
117
+ if (!url) {
118
+ return;
119
+ }
120
+ const cleanupBody = {
121
+ clientGroupID: this.id,
122
+ mutations: [
123
+ {
124
+ type: Custom,
125
+ id: 0,
126
+ // Not tracked - this is fire-and-forget
127
+ clientID: clientIDs[0],
128
+ // Use first client as sender
129
+ name: CLEANUP_RESULTS_MUTATION_NAME,
130
+ args: [
131
+ {
132
+ type: "bulk",
133
+ clientGroupID: this.id,
134
+ clientIDs
135
+ }
136
+ ],
137
+ timestamp: Date.now()
138
+ }
139
+ ],
140
+ pushVersion: 1,
141
+ timestamp: Date.now(),
142
+ requestID: `cleanup-bulk-${this.id}-${Date.now()}`
143
+ };
144
+ try {
145
+ await fetchFromAPIServer(
146
+ pushResponseSchema,
147
+ "push",
148
+ this.#lc,
149
+ url,
150
+ false,
151
+ this.#pushURLPatterns,
152
+ { appID: this.#config.app.id, shardNum: this.#config.shard.num },
153
+ { apiKey: this.#pushConfig.apiKey },
154
+ cleanupBody
155
+ );
156
+ } catch (e) {
157
+ this.#lc.warn?.("Failed to send bulk cleanup mutation", {
158
+ error: getErrorMessage(e)
159
+ });
160
+ }
161
+ }
105
162
  ref() {
106
163
  assert(!this.#isStopped, "PusherService is already stopped");
107
164
  ++this.#refCount;
@@ -133,11 +190,13 @@ class PushWorker {
133
190
  #pushURLs;
134
191
  #pushURLPatterns;
135
192
  #apiKey;
193
+ #allowedClientHeaders;
136
194
  #queue;
137
195
  #lc;
138
196
  #config;
139
197
  #clients;
140
198
  #userPushURL;
199
+ #userPushHeaders;
141
200
  #customMutations = getOrCreateCounter(
142
201
  "mutation",
143
202
  "custom",
@@ -148,11 +207,12 @@ class PushWorker {
148
207
  "pushes",
149
208
  "Number of pushes processed by the pusher"
150
209
  );
151
- constructor(config, lc, pushURL, apiKey, queue) {
210
+ constructor(config, lc, pushURL, apiKey, allowedClientHeaders, queue) {
152
211
  this.#pushURLs = pushURL;
153
212
  this.#lc = lc.withContext("component", "pusher");
154
213
  this.#pushURLPatterns = pushURL.map(compileUrlPattern);
155
214
  this.#apiKey = apiKey;
215
+ this.#allowedClientHeaders = allowedClientHeaders;
156
216
  this.#queue = queue;
157
217
  this.#config = config;
158
218
  this.#clients = /* @__PURE__ */ new Map();
@@ -164,7 +224,7 @@ class PushWorker {
164
224
  * Returns a new downstream stream if the clientID,wsID pair has not been seen before.
165
225
  * If a clientID already exists with a different wsID, that client's downstream is cancelled.
166
226
  */
167
- initConnection(clientID, wsID, userPushURL) {
227
+ initConnection(clientID, wsID, userPushURL, userPushHeaders) {
168
228
  const existing = this.#clients.get(clientID);
169
229
  if (existing && existing.wsID === wsID) {
170
230
  throw new Error("Connection was already initialized");
@@ -174,6 +234,7 @@ class PushWorker {
174
234
  }
175
235
  if (this.#userPushURL === void 0) {
176
236
  this.#userPushURL = userPushURL;
237
+ this.#userPushHeaders = userPushHeaders;
177
238
  } else {
178
239
  if (this.#userPushURL !== userPushURL) {
179
240
  this.#lc.warn?.(
@@ -339,8 +400,11 @@ class PushWorker {
339
400
  },
340
401
  {
341
402
  apiKey: this.#apiKey,
403
+ customHeaders: this.#userPushHeaders,
404
+ allowedClientHeaders: this.#allowedClientHeaders,
342
405
  token: entry.auth,
343
- cookie: entry.httpCookie
406
+ cookie: entry.httpCookie,
407
+ origin: entry.origin
344
408
  },
345
409
  entry.push
346
410
  );
@@ -416,6 +480,14 @@ function assertAreCompatiblePushes(left, right) {
416
480
  left.push.pushVersion === right.push.pushVersion,
417
481
  "pushVersion must be the same for all pushes with the same clientID"
418
482
  );
483
+ assert(
484
+ left.httpCookie === right.httpCookie,
485
+ "httpCookie must be the same for all pushes with the same clientID"
486
+ );
487
+ assert(
488
+ left.origin === right.origin,
489
+ "origin must be the same for all pushes with the same clientID"
490
+ );
419
491
  }
420
492
  export {
421
493
  PusherService,