@rocicorp/zero 1.11.0 → 1.11.2

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 (737) hide show
  1. package/out/_virtual/{_@oxc-project_runtime@0.130.0/helpers → _@oxc-project_runtime@0.154.0/helpers/esm}/usingCtx.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js +2 -1
  3. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  4. package/out/analyze-query/src/bin-analyze.js +5 -3
  5. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  6. package/out/analyze-query/src/bin-transform.js +3 -2
  7. package/out/analyze-query/src/bin-transform.js.map +1 -1
  8. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  9. package/out/ast-to-zql/src/bin.js +3 -2
  10. package/out/ast-to-zql/src/bin.js.map +1 -1
  11. package/out/ast-to-zql/src/format.js.map +1 -1
  12. package/out/datadog/src/datadog-log-sink.js +5 -4
  13. package/out/datadog/src/datadog-log-sink.js.map +1 -1
  14. package/out/otel/src/log-options.js.map +1 -1
  15. package/out/replicache/src/btree/node.js +13 -11
  16. package/out/replicache/src/btree/node.js.map +1 -1
  17. package/out/replicache/src/btree/read.js +4 -2
  18. package/out/replicache/src/btree/read.js.map +1 -1
  19. package/out/replicache/src/btree/splice.js.map +1 -1
  20. package/out/replicache/src/btree/write.js +9 -5
  21. package/out/replicache/src/btree/write.js.map +1 -1
  22. package/out/replicache/src/config.js +1 -2
  23. package/out/replicache/src/connection-loop.js.map +1 -1
  24. package/out/replicache/src/dag/chunk.js +1 -1
  25. package/out/replicache/src/dag/gc.js.map +1 -1
  26. package/out/replicache/src/dag/lazy-store.js +8 -7
  27. package/out/replicache/src/dag/lazy-store.js.map +1 -1
  28. package/out/replicache/src/dag/store-impl.js +1 -1
  29. package/out/replicache/src/dag/store-impl.js.map +1 -1
  30. package/out/replicache/src/dag/visitor.js.map +1 -1
  31. package/out/replicache/src/db/commit.js +5 -3
  32. package/out/replicache/src/db/commit.js.map +1 -1
  33. package/out/replicache/src/db/read.js +1 -1
  34. package/out/replicache/src/db/read.js.map +1 -1
  35. package/out/replicache/src/db/rebase.js.map +1 -1
  36. package/out/replicache/src/db/write.js +5 -2
  37. package/out/replicache/src/db/write.js.map +1 -1
  38. package/out/replicache/src/deleted-clients.js +3 -2
  39. package/out/replicache/src/deleted-clients.js.map +1 -1
  40. package/out/replicache/src/expo-sqlite.js +1 -2
  41. package/out/replicache/src/frozen-json.js +1 -1
  42. package/out/replicache/src/frozen-json.js.map +1 -1
  43. package/out/replicache/src/get-default-puller.js +1 -1
  44. package/out/replicache/src/hash.js +1 -1
  45. package/out/replicache/src/hash.js.map +1 -1
  46. package/out/replicache/src/impl.js +1 -2
  47. package/out/replicache/src/index-defs.js +5 -3
  48. package/out/replicache/src/index-defs.js.map +1 -1
  49. package/out/replicache/src/kv/expo-sqlite/store.js +1 -1
  50. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  51. package/out/replicache/src/kv/idb-store.js +1 -1
  52. package/out/replicache/src/kv/idb-store.js.map +1 -1
  53. package/out/replicache/src/kv/mem-fallback-store.js +1 -1
  54. package/out/replicache/src/kv/mem-fallback-store.js.map +1 -1
  55. package/out/replicache/src/kv/mem-store.js.map +1 -1
  56. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  57. package/out/replicache/src/kv/op-sqlite/types.js.map +1 -1
  58. package/out/replicache/src/kv/read-impl.js.map +1 -1
  59. package/out/replicache/src/kv/sqlite-store.js +1 -1
  60. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  61. package/out/replicache/src/kv/write-impl-base.js +4 -1
  62. package/out/replicache/src/kv/write-impl-base.js.map +1 -1
  63. package/out/replicache/src/kv/write-impl.js.map +1 -1
  64. package/out/replicache/src/log-options.js +2 -1
  65. package/out/replicache/src/log-options.js.map +1 -1
  66. package/out/replicache/src/make-idb-name.js +1 -1
  67. package/out/replicache/src/new-client-channel.js +9 -7
  68. package/out/replicache/src/new-client-channel.js.map +1 -1
  69. package/out/replicache/src/on-persist-channel.js +1 -1
  70. package/out/replicache/src/on-persist-channel.js.map +1 -1
  71. package/out/replicache/src/op-sqlite.js +1 -2
  72. package/out/replicache/src/pending-mutations.js +2 -1
  73. package/out/replicache/src/pending-mutations.js.map +1 -1
  74. package/out/replicache/src/persist/client-gc.js +2 -2
  75. package/out/replicache/src/persist/client-gc.js.map +1 -1
  76. package/out/replicache/src/persist/client-group-gc.js +2 -2
  77. package/out/replicache/src/persist/client-group-gc.js.map +1 -1
  78. package/out/replicache/src/persist/client-groups.js +1 -1
  79. package/out/replicache/src/persist/client-groups.js.map +1 -1
  80. package/out/replicache/src/persist/clients.js +1 -1
  81. package/out/replicache/src/persist/clients.js.map +1 -1
  82. package/out/replicache/src/persist/collect-idb-databases.js +5 -4
  83. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  84. package/out/replicache/src/persist/gather-mem-only-visitor.js.map +1 -1
  85. package/out/replicache/src/persist/gather-not-cached-visitor.js.map +1 -1
  86. package/out/replicache/src/persist/heartbeat.js +2 -2
  87. package/out/replicache/src/persist/heartbeat.js.map +1 -1
  88. package/out/replicache/src/persist/idb-databases-store.js +1 -1
  89. package/out/replicache/src/persist/idb-databases-store.js.map +1 -1
  90. package/out/replicache/src/persist/make-client-id.js +1 -1
  91. package/out/replicache/src/persist/make-client-id.js.map +1 -1
  92. package/out/replicache/src/persist/persist.js +3 -2
  93. package/out/replicache/src/persist/persist.js.map +1 -1
  94. package/out/replicache/src/persist/refresh.js +8 -5
  95. package/out/replicache/src/persist/refresh.js.map +1 -1
  96. package/out/replicache/src/process-scheduler.js.map +1 -1
  97. package/out/replicache/src/replicache-impl.js +3 -3
  98. package/out/replicache/src/replicache-impl.js.map +1 -1
  99. package/out/replicache/src/scan-iterator.js.map +1 -1
  100. package/out/replicache/src/scan-options.js +7 -6
  101. package/out/replicache/src/scan-options.js.map +1 -1
  102. package/out/replicache/src/sqlite.js +1 -2
  103. package/out/replicache/src/subscriptions.js +1 -1
  104. package/out/replicache/src/subscriptions.js.map +1 -1
  105. package/out/replicache/src/sync/diff.js +3 -1
  106. package/out/replicache/src/sync/diff.js.map +1 -1
  107. package/out/replicache/src/sync/ids.js.map +1 -1
  108. package/out/replicache/src/sync/patch.js +2 -4
  109. package/out/replicache/src/sync/patch.js.map +1 -1
  110. package/out/replicache/src/sync/pull.js +3 -1
  111. package/out/replicache/src/sync/pull.js.map +1 -1
  112. package/out/replicache/src/sync/push.js.map +1 -1
  113. package/out/replicache/src/sync/request-id.js +1 -1
  114. package/out/replicache/src/sync/request-id.js.map +1 -1
  115. package/out/replicache/src/transactions.js +1 -1
  116. package/out/replicache/src/transactions.js.map +1 -1
  117. package/out/shared/src/arrays.js +4 -1
  118. package/out/shared/src/arrays.js.map +1 -1
  119. package/out/shared/src/asserts.js +1 -1
  120. package/out/shared/src/bigint-json.js.map +1 -1
  121. package/out/shared/src/broadcast-channel.js +1 -1
  122. package/out/shared/src/btree-set.js +11 -8
  123. package/out/shared/src/btree-set.js.map +1 -1
  124. package/out/shared/src/cache.js.map +1 -1
  125. package/out/shared/src/config.js +1 -1
  126. package/out/shared/src/custom-key-map.js.map +1 -1
  127. package/out/shared/src/custom-key-set.js.map +1 -1
  128. package/out/shared/src/deep-clone.js +1 -1
  129. package/out/shared/src/document-visible.js.map +1 -1
  130. package/out/shared/src/dotenv.js.map +1 -1
  131. package/out/shared/src/error.js +1 -1
  132. package/out/shared/src/error.js.map +1 -1
  133. package/out/shared/src/hash.js +2 -2
  134. package/out/shared/src/hash.js.map +1 -1
  135. package/out/shared/src/iterables.js.map +1 -1
  136. package/out/shared/src/json-schema.js.map +1 -1
  137. package/out/shared/src/json.js.map +1 -1
  138. package/out/shared/src/logging-test-utils.js +1 -1
  139. package/out/shared/src/logging.js +1 -1
  140. package/out/shared/src/logging.js.map +1 -1
  141. package/out/shared/src/navigator.js +1 -1
  142. package/out/shared/src/options.js +9 -12
  143. package/out/shared/src/options.js.map +1 -1
  144. package/out/shared/src/promise-race.js.map +1 -1
  145. package/out/shared/src/queue.js.map +1 -1
  146. package/out/shared/src/record-proxy.js.map +1 -1
  147. package/out/shared/src/ring-buffer.js.map +1 -1
  148. package/out/shared/src/size-of-value.js +7 -10
  149. package/out/shared/src/size-of-value.js.map +1 -1
  150. package/out/shared/src/sorted-entries.js.map +1 -1
  151. package/out/shared/src/tdigest-schema.js.map +1 -1
  152. package/out/shared/src/tdigest.js.map +1 -1
  153. package/out/shared/src/valita.js +1 -1
  154. package/out/shared/src/valita.js.map +1 -1
  155. package/out/z2s/src/compiler.js +2 -4
  156. package/out/z2s/src/compiler.js.map +1 -1
  157. package/out/z2s/src/sql.js +1 -1
  158. package/out/z2s/src/sql.js.map +1 -1
  159. package/out/zero/package.js +212 -189
  160. package/out/zero/package.js.map +1 -1
  161. package/out/zero/src/adapters/drizzle.js +1 -2
  162. package/out/zero/src/adapters/kysely.js +1 -2
  163. package/out/zero/src/adapters/pg.js +1 -2
  164. package/out/zero/src/adapters/postgresjs.js +1 -2
  165. package/out/zero/src/adapters/prisma.js +1 -2
  166. package/out/zero/src/analyze.js +1 -2
  167. package/out/zero/src/change-protocol/v0.js +1 -2
  168. package/out/zero/src/expo-sqlite.js +1 -2
  169. package/out/zero/src/op-sqlite.js +1 -2
  170. package/out/zero/src/react-native.js.map +1 -1
  171. package/out/zero/src/sqlite.js +1 -2
  172. package/out/zero/src/zero-cache-dev.js +10 -9
  173. package/out/zero/src/zero-cache-dev.js.map +1 -1
  174. package/out/zero/src/zero-out.js +2 -1
  175. package/out/zero/src/zero-out.js.map +1 -1
  176. package/out/zero-cache/src/auth/auth.js +1 -1
  177. package/out/zero-cache/src/auth/auth.js.map +1 -1
  178. package/out/zero-cache/src/auth/jwt.js +4 -1
  179. package/out/zero-cache/src/auth/jwt.js.map +1 -1
  180. package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
  181. package/out/zero-cache/src/auth/read-authorizer.js +3 -2
  182. package/out/zero-cache/src/auth/read-authorizer.js.map +1 -1
  183. package/out/zero-cache/src/auth/write-authorizer.js +5 -16
  184. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  185. package/out/zero-cache/src/config/network.js +1 -1
  186. package/out/zero-cache/src/config/network.js.map +1 -1
  187. package/out/zero-cache/src/config/server-context.js.map +1 -1
  188. package/out/zero-cache/src/config/zero-config.js +2 -2
  189. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  190. package/out/zero-cache/src/custom/fetch.js +1 -1
  191. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  192. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  193. package/out/zero-cache/src/db/create.js.map +1 -1
  194. package/out/zero-cache/src/db/index-predicate.js.map +1 -1
  195. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  196. package/out/zero-cache/src/db/migration-lite.js +1 -1
  197. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  198. package/out/zero-cache/src/db/migration.js +5 -3
  199. package/out/zero-cache/src/db/migration.js.map +1 -1
  200. package/out/zero-cache/src/db/pg-copy-binary.js +4 -3
  201. package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -1
  202. package/out/zero-cache/src/db/pg-copy.js +0 -1
  203. package/out/zero-cache/src/db/pg-copy.js.map +1 -1
  204. package/out/zero-cache/src/db/pg-to-lite.js +1 -1
  205. package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
  206. package/out/zero-cache/src/db/run-transaction.js.map +1 -1
  207. package/out/zero-cache/src/db/specs.js +7 -5
  208. package/out/zero-cache/src/db/specs.js.map +1 -1
  209. package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
  210. package/out/zero-cache/src/db/transaction-pool.js +5 -3
  211. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  212. package/out/zero-cache/src/db/warmup.js.map +1 -1
  213. package/out/zero-cache/src/observability/events.js.map +1 -1
  214. package/out/zero-cache/src/observability/log-throttle.js.map +1 -1
  215. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  216. package/out/zero-cache/src/scripts/deploy-permissions.js +4 -2
  217. package/out/zero-cache/src/scripts/deploy-permissions.js.map +1 -1
  218. package/out/zero-cache/src/scripts/permissions.js.map +1 -1
  219. package/out/zero-cache/src/server/anonymous-otel-start.js +2 -1
  220. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  221. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  222. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  223. package/out/zero-cache/src/server/logging.js +1 -1
  224. package/out/zero-cache/src/server/logging.js.map +1 -1
  225. package/out/zero-cache/src/server/main.js.map +1 -1
  226. package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
  227. package/out/zero-cache/src/server/otel-start.js.map +1 -1
  228. package/out/zero-cache/src/server/reaper.js +2 -2
  229. package/out/zero-cache/src/server/reaper.js.map +1 -1
  230. package/out/zero-cache/src/server/replicator.js.map +1 -1
  231. package/out/zero-cache/src/server/runner/run-worker.js +2 -1
  232. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  233. package/out/zero-cache/src/server/runner/runtime.js +2 -1
  234. package/out/zero-cache/src/server/runner/runtime.js.map +1 -1
  235. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  236. package/out/zero-cache/src/server/shadow-syncer.js +1 -1
  237. package/out/zero-cache/src/server/shadow-syncer.js.map +1 -1
  238. package/out/zero-cache/src/server/slot-keeper.js.map +1 -1
  239. package/out/zero-cache/src/server/syncer-assigner.js.map +1 -1
  240. package/out/zero-cache/src/server/syncer.js.map +1 -1
  241. package/out/zero-cache/src/server/worker-dispatcher.js +1 -1
  242. package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
  243. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  244. package/out/zero-cache/src/services/analyze.js +1 -1
  245. package/out/zero-cache/src/services/analyze.js.map +1 -1
  246. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +1 -1
  247. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  248. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  249. package/out/zero-cache/src/services/change-source/common/init-cleanup.js.map +1 -1
  250. package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -1
  251. package/out/zero-cache/src/services/change-source/common/replica-schema.js +3 -3
  252. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  253. package/out/zero-cache/src/services/change-source/custom/change-source.js +1 -1
  254. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  255. package/out/zero-cache/src/services/change-source/pg/backfill-metadata.js.map +1 -1
  256. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +3 -3
  257. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  258. package/out/zero-cache/src/services/change-source/pg/change-source-init.js +1 -1
  259. package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
  260. package/out/zero-cache/src/services/change-source/pg/change-source.js +4 -6
  261. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  262. package/out/zero-cache/src/services/change-source/pg/decommission.js +2 -1
  263. package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
  264. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +5 -3
  265. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  266. package/out/zero-cache/src/services/change-source/pg/logical-replication/binary-reader.js.map +1 -1
  267. package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js.map +1 -1
  268. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +3 -2
  269. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  270. package/out/zero-cache/src/services/change-source/pg/lsn.js.map +1 -1
  271. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js +2 -2
  272. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js.map +1 -1
  273. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js +1 -1
  274. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js.map +1 -1
  275. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +3 -3
  276. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  277. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +1 -1
  278. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  279. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +13 -12
  280. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
  281. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  282. package/out/zero-cache/src/services/change-source/pg/schema/published.js +1 -1
  283. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  284. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js.map +1 -1
  285. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +9 -6
  286. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  287. package/out/zero-cache/src/services/change-source/pg/schema/validation.js +1 -1
  288. package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
  289. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +1 -1
  290. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -1
  291. package/out/zero-cache/src/services/change-source/protocol/current/control.js.map +1 -1
  292. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  293. package/out/zero-cache/src/services/change-source/protocol/current/downstream.js.map +1 -1
  294. package/out/zero-cache/src/services/change-source/protocol/current/status.js.map +1 -1
  295. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  296. package/out/zero-cache/src/services/change-source/protocol/current.js +1 -1
  297. package/out/zero-cache/src/services/change-source/protocol/mod.js +1 -2
  298. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -1
  299. package/out/zero-cache/src/services/change-streamer/backfill-state.js +1 -1
  300. package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -1
  301. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
  302. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  303. package/out/zero-cache/src/services/change-streamer/broadcast.js +1 -1
  304. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  305. package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +2 -2
  306. package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -1
  307. package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js.map +1 -1
  308. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +4 -2
  309. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  310. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +21 -21
  311. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  312. package/out/zero-cache/src/services/change-streamer/change-streamer.js +1 -1
  313. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  314. package/out/zero-cache/src/services/change-streamer/drain-tracker.js.map +1 -1
  315. package/out/zero-cache/src/services/change-streamer/forwarder.js +11 -10
  316. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  317. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js +1 -1
  318. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js.map +1 -1
  319. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js +3 -3
  320. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -1
  321. package/out/zero-cache/src/services/change-streamer/replica-poller.js +1 -1
  322. package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -1
  323. package/out/zero-cache/src/services/change-streamer/restore-admission.js +12 -1
  324. package/out/zero-cache/src/services/change-streamer/restore-admission.js.map +1 -1
  325. package/out/zero-cache/src/services/change-streamer/schema/init.js +1 -1
  326. package/out/zero-cache/src/services/change-streamer/schema/init.js.map +1 -1
  327. package/out/zero-cache/src/services/change-streamer/schema/tables.js +1 -1
  328. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  329. package/out/zero-cache/src/services/change-streamer/snapshot-message.js +1 -1
  330. package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -1
  331. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
  332. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +1 -1
  333. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
  334. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js +1 -1
  335. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js.map +1 -1
  336. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js.map +1 -1
  337. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -1
  338. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +1 -1
  339. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -1
  340. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +1 -3
  341. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -1
  342. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  343. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  344. package/out/zero-cache/src/services/change-streamer/subscribe.js +1 -1
  345. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
  346. package/out/zero-cache/src/services/change-streamer/subscriber.js +2 -2
  347. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  348. package/out/zero-cache/src/services/change-streamer/upstream-acker.js +0 -1
  349. package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -1
  350. package/out/zero-cache/src/services/heapz.js +2 -1
  351. package/out/zero-cache/src/services/heapz.js.map +1 -1
  352. package/out/zero-cache/src/services/http-service.js.map +1 -1
  353. package/out/zero-cache/src/services/life-cycle.js +1 -1
  354. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  355. package/out/zero-cache/src/services/limiter/sliding-window-limiter.js +4 -2
  356. package/out/zero-cache/src/services/limiter/sliding-window-limiter.js.map +1 -1
  357. package/out/zero-cache/src/services/litestream/commands.js +1 -1
  358. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  359. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +2 -2
  360. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -1
  361. package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -1
  362. package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
  363. package/out/zero-cache/src/services/litestream/restore-progress.js.map +1 -1
  364. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
  365. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  366. package/out/zero-cache/src/services/mutagen/pusher.js +3 -2
  367. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  368. package/out/zero-cache/src/services/plannerz.js +4 -3
  369. package/out/zero-cache/src/services/plannerz.js.map +1 -1
  370. package/out/zero-cache/src/services/profz.js +5 -3
  371. package/out/zero-cache/src/services/profz.js.map +1 -1
  372. package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
  373. package/out/zero-cache/src/services/replicator/change-log-db.js +3 -2
  374. package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
  375. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +1 -1
  376. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
  377. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  378. package/out/zero-cache/src/services/replicator/drain-handler.js.map +1 -1
  379. package/out/zero-cache/src/services/replicator/incremental-sync.js +1 -1
  380. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  381. package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
  382. package/out/zero-cache/src/services/replicator/replication-status.js +4 -3
  383. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  384. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  385. package/out/zero-cache/src/services/replicator/reporter/recorder.js +1 -1
  386. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  387. package/out/zero-cache/src/services/replicator/reporter/report-schema.js +1 -1
  388. package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -1
  389. package/out/zero-cache/src/services/replicator/schema/backfilling.js +1 -1
  390. package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
  391. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  392. package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
  393. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
  394. package/out/zero-cache/src/services/replicator/schema/replication-state.js +10 -7
  395. package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
  396. package/out/zero-cache/src/services/replicator/schema/table-metadata.js.map +1 -1
  397. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +1 -1
  398. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
  399. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js +1 -1
  400. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js.map +1 -1
  401. package/out/zero-cache/src/services/replicator/write-worker-client.js +1 -1
  402. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  403. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  404. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  405. package/out/zero-cache/src/services/runner.js.map +1 -1
  406. package/out/zero-cache/src/services/running-state.js.map +1 -1
  407. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
  408. package/out/zero-cache/src/services/statz.js +2 -1
  409. package/out/zero-cache/src/services/statz.js.map +1 -1
  410. package/out/zero-cache/src/services/view-syncer/active-users-gauge.js +2 -2
  411. package/out/zero-cache/src/services/view-syncer/active-users-gauge.js.map +1 -1
  412. package/out/zero-cache/src/services/view-syncer/advancement-metrics.js.map +1 -1
  413. package/out/zero-cache/src/services/view-syncer/batch-pacer.js.map +1 -1
  414. package/out/zero-cache/src/services/view-syncer/client-handler.js +4 -2
  415. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  416. package/out/zero-cache/src/services/view-syncer/client-schema.js.map +1 -1
  417. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  418. package/out/zero-cache/src/services/view-syncer/cvr-purger.js +2 -2
  419. package/out/zero-cache/src/services/view-syncer/cvr-purger.js.map +1 -1
  420. package/out/zero-cache/src/services/view-syncer/cvr-store.js +9 -5
  421. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  422. package/out/zero-cache/src/services/view-syncer/cvr.js +3 -2
  423. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  424. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +1 -1
  425. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -1
  426. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  427. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -1
  428. package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -1
  429. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +2 -2
  430. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -1
  431. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +5 -3
  432. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  433. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +6 -4
  434. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  435. package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -1
  436. package/out/zero-cache/src/services/view-syncer/push-cost.js.map +1 -1
  437. package/out/zero-cache/src/services/view-syncer/push-cutoff.js.map +1 -1
  438. package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
  439. package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -1
  440. package/out/zero-cache/src/services/view-syncer/query-stats.js +1 -1
  441. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -1
  442. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  443. package/out/zero-cache/src/services/view-syncer/schema/cvr.js +1 -1
  444. package/out/zero-cache/src/services/view-syncer/schema/types.js +1 -1
  445. package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
  446. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js +7 -1
  447. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js.map +1 -1
  448. package/out/zero-cache/src/services/view-syncer/snapshotter.js +3 -2
  449. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  450. package/out/zero-cache/src/services/view-syncer/ttl-clock.js.map +1 -1
  451. package/out/zero-cache/src/services/view-syncer/view-syncer.js +4 -6
  452. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  453. package/out/zero-cache/src/types/error-with-level.js.map +1 -1
  454. package/out/zero-cache/src/types/lexi-version.js.map +1 -1
  455. package/out/zero-cache/src/types/lite.js +1 -1
  456. package/out/zero-cache/src/types/lite.js.map +1 -1
  457. package/out/zero-cache/src/types/pg-data-type.js +1 -1
  458. package/out/zero-cache/src/types/pg-data-type.js.map +1 -1
  459. package/out/zero-cache/src/types/pg.js +6 -4
  460. package/out/zero-cache/src/types/pg.js.map +1 -1
  461. package/out/zero-cache/src/types/poke-chunk.js +2 -2
  462. package/out/zero-cache/src/types/poke-chunk.js.map +1 -1
  463. package/out/zero-cache/src/types/processes.js +1 -1
  464. package/out/zero-cache/src/types/processes.js.map +1 -1
  465. package/out/zero-cache/src/types/profiler.js.map +1 -1
  466. package/out/zero-cache/src/types/row-key.js.map +1 -1
  467. package/out/zero-cache/src/types/sql.js.map +1 -1
  468. package/out/zero-cache/src/types/streams.js +10 -9
  469. package/out/zero-cache/src/types/streams.js.map +1 -1
  470. package/out/zero-cache/src/types/strings.js.map +1 -1
  471. package/out/zero-cache/src/types/subscription.js.map +1 -1
  472. package/out/zero-cache/src/types/timeout.js +1 -1
  473. package/out/zero-cache/src/types/websocket-handoff.js +1 -1
  474. package/out/zero-cache/src/types/ws.js +1 -1
  475. package/out/zero-cache/src/workers/connection.js +2 -3
  476. package/out/zero-cache/src/workers/connection.js.map +1 -1
  477. package/out/zero-cache/src/workers/mutator.js.map +1 -1
  478. package/out/zero-cache/src/workers/replicator.js +5 -4
  479. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  480. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  481. package/out/zero-cache/src/workers/syncer.js +2 -2
  482. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  483. package/out/zero-client/src/client/active-clients-manager.js +1 -1
  484. package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
  485. package/out/zero-client/src/client/bindings.js +11 -11
  486. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  487. package/out/zero-client/src/client/connection.js +1 -1
  488. package/out/zero-client/src/client/connection.js.map +1 -1
  489. package/out/zero-client/src/client/context.js.map +1 -1
  490. package/out/zero-client/src/client/crud-impl.js +1 -1
  491. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  492. package/out/zero-client/src/client/crud.js +1 -1
  493. package/out/zero-client/src/client/crud.js.map +1 -1
  494. package/out/zero-client/src/client/custom.js +1 -1
  495. package/out/zero-client/src/client/custom.js.map +1 -1
  496. package/out/zero-client/src/client/delete-clients-manager.js.map +1 -1
  497. package/out/zero-client/src/client/enable-analytics.js +1 -1
  498. package/out/zero-client/src/client/enable-analytics.js.map +1 -1
  499. package/out/zero-client/src/client/error.js.map +1 -1
  500. package/out/zero-client/src/client/inspector/client-group.js.map +1 -1
  501. package/out/zero-client/src/client/inspector/client.js.map +1 -1
  502. package/out/zero-client/src/client/inspector/html-dialog-prompt.js +1 -1
  503. package/out/zero-client/src/client/inspector/html-dialog-prompt.js.map +1 -1
  504. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  505. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  506. package/out/zero-client/src/client/inspector/query.js.map +1 -1
  507. package/out/zero-client/src/client/ivm-branch.js +1 -3
  508. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  509. package/out/zero-client/src/client/keys.js +2 -1
  510. package/out/zero-client/src/client/keys.js.map +1 -1
  511. package/out/zero-client/src/client/log-options.js.map +1 -1
  512. package/out/zero-client/src/client/make-replicache-mutators.js +5 -3
  513. package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
  514. package/out/zero-client/src/client/metrics.js +3 -5
  515. package/out/zero-client/src/client/metrics.js.map +1 -1
  516. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  517. package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
  518. package/out/zero-client/src/client/options.js.map +1 -1
  519. package/out/zero-client/src/client/query-manager.js +2 -2
  520. package/out/zero-client/src/client/query-manager.js.map +1 -1
  521. package/out/zero-client/src/client/reload-error-handler.js +4 -3
  522. package/out/zero-client/src/client/reload-error-handler.js.map +1 -1
  523. package/out/zero-client/src/client/version.js +1 -1
  524. package/out/zero-client/src/client/zero-poke-handler.js +1 -1
  525. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  526. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  527. package/out/zero-client/src/client/zero.js +19 -11
  528. package/out/zero-client/src/client/zero.js.map +1 -1
  529. package/out/zero-client/src/mod.js +23 -23
  530. package/out/zero-client/src/util/nanoid.js.map +1 -1
  531. package/out/zero-permissions/src/permissions.js +3 -2
  532. package/out/zero-permissions/src/permissions.js.map +1 -1
  533. package/out/zero-pg/src/mod.js +9 -9
  534. package/out/zero-protocol/src/analyze-query-result.js +1 -1
  535. package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
  536. package/out/zero-protocol/src/application-error.js.map +1 -1
  537. package/out/zero-protocol/src/ast.js +19 -9
  538. package/out/zero-protocol/src/ast.js.map +1 -1
  539. package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
  540. package/out/zero-protocol/src/client-schema.js +1 -1
  541. package/out/zero-protocol/src/client-schema.js.map +1 -1
  542. package/out/zero-protocol/src/close-connection.js +1 -1
  543. package/out/zero-protocol/src/close-connection.js.map +1 -1
  544. package/out/zero-protocol/src/connect.js +1 -1
  545. package/out/zero-protocol/src/connect.js.map +1 -1
  546. package/out/zero-protocol/src/custom-queries.js +1 -1
  547. package/out/zero-protocol/src/custom-queries.js.map +1 -1
  548. package/out/zero-protocol/src/data.js +4 -2
  549. package/out/zero-protocol/src/data.js.map +1 -1
  550. package/out/zero-protocol/src/delete-clients.js.map +1 -1
  551. package/out/zero-protocol/src/down.js.map +1 -1
  552. package/out/zero-protocol/src/error.js.map +1 -1
  553. package/out/zero-protocol/src/inspect-down.d.ts +81 -81
  554. package/out/zero-protocol/src/inspect-down.js +1 -1
  555. package/out/zero-protocol/src/inspect-down.js.map +1 -1
  556. package/out/zero-protocol/src/inspect-up.js +1 -1
  557. package/out/zero-protocol/src/inspect-up.js.map +1 -1
  558. package/out/zero-protocol/src/mutate-server.js +1 -1
  559. package/out/zero-protocol/src/mutate-server.js.map +1 -1
  560. package/out/zero-protocol/src/mutation-id.js.map +1 -1
  561. package/out/zero-protocol/src/mutation.js +1 -1
  562. package/out/zero-protocol/src/mutation.js.map +1 -1
  563. package/out/zero-protocol/src/mutations-patch.js +1 -1
  564. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  565. package/out/zero-protocol/src/ping.js +1 -1
  566. package/out/zero-protocol/src/ping.js.map +1 -1
  567. package/out/zero-protocol/src/poke.js +1 -1
  568. package/out/zero-protocol/src/poke.js.map +1 -1
  569. package/out/zero-protocol/src/pong.js +1 -1
  570. package/out/zero-protocol/src/pong.js.map +1 -1
  571. package/out/zero-protocol/src/primary-key.js.map +1 -1
  572. package/out/zero-protocol/src/protocol-version.js +1 -0
  573. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  574. package/out/zero-protocol/src/pull.js +1 -1
  575. package/out/zero-protocol/src/pull.js.map +1 -1
  576. package/out/zero-protocol/src/push.js +1 -1
  577. package/out/zero-protocol/src/push.js.map +1 -1
  578. package/out/zero-protocol/src/queries-patch.js +1 -1
  579. package/out/zero-protocol/src/queries-patch.js.map +1 -1
  580. package/out/zero-protocol/src/query-hash-visitor.js.map +1 -1
  581. package/out/zero-protocol/src/query-hash.js +2 -2
  582. package/out/zero-protocol/src/query-server.js +1 -1
  583. package/out/zero-protocol/src/query-server.js.map +1 -1
  584. package/out/zero-protocol/src/row-patch.js.map +1 -1
  585. package/out/zero-protocol/src/up.js.map +1 -1
  586. package/out/zero-protocol/src/update-auth.js.map +1 -1
  587. package/out/zero-protocol/src/version.js.map +1 -1
  588. package/out/zero-react/src/bindings.js +11 -11
  589. package/out/zero-react/src/mod.js +5 -5
  590. package/out/zero-react/src/use-query.js +1 -1
  591. package/out/zero-react/src/use-query.js.map +1 -1
  592. package/out/zero-react/src/zero-provider.d.ts +1 -1
  593. package/out/zero-react/src/zero-provider.d.ts.map +1 -1
  594. package/out/zero-react/src/zero-provider.js +2 -1
  595. package/out/zero-react/src/zero-provider.js.map +1 -1
  596. package/out/zero-react/src/zero.js +23 -23
  597. package/out/zero-schema/src/builder/schema-builder.js +3 -2
  598. package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
  599. package/out/zero-schema/src/builder/table-builder.js +1 -1
  600. package/out/zero-schema/src/builder/table-builder.js.map +1 -1
  601. package/out/zero-schema/src/compiled-permissions.js.map +1 -1
  602. package/out/zero-schema/src/name-mapper.js +7 -5
  603. package/out/zero-schema/src/name-mapper.js.map +1 -1
  604. package/out/zero-schema/src/schema-config.js +1 -1
  605. package/out/zero-schema/src/schema-config.js.map +1 -1
  606. package/out/zero-server/src/adapters/drizzle.js.map +1 -1
  607. package/out/zero-server/src/adapters/kysely.js.map +1 -1
  608. package/out/zero-server/src/adapters/pg.js.map +1 -1
  609. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  610. package/out/zero-server/src/adapters/prisma.js.map +1 -1
  611. package/out/zero-server/src/closable-db-transaction.js.map +1 -1
  612. package/out/zero-server/src/custom.js +1 -1
  613. package/out/zero-server/src/custom.js.map +1 -1
  614. package/out/zero-server/src/mod.js +8 -8
  615. package/out/zero-server/src/process-mutations.js.map +1 -1
  616. package/out/zero-server/src/push-processor.js.map +1 -1
  617. package/out/zero-server/src/queries/process-queries.js +2 -1
  618. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  619. package/out/zero-server/src/schema.js +7 -7
  620. package/out/zero-server/src/schema.js.map +1 -1
  621. package/out/zero-server/src/zql-database.js.map +1 -1
  622. package/out/zero-solid/src/bindings.js +11 -11
  623. package/out/zero-solid/src/mod.js +5 -5
  624. package/out/zero-solid/src/solid-view.js +1 -1
  625. package/out/zero-solid/src/solid-view.js.map +1 -1
  626. package/out/zero-solid/src/use-connection-state.js +2 -1
  627. package/out/zero-solid/src/use-connection-state.js.map +1 -1
  628. package/out/zero-solid/src/use-query.js.map +1 -1
  629. package/out/zero-solid/src/use-zero-online.js +2 -1
  630. package/out/zero-solid/src/use-zero-online.js.map +1 -1
  631. package/out/zero-solid/src/use-zero.js +3 -2
  632. package/out/zero-solid/src/use-zero.js.map +1 -1
  633. package/out/zero-solid/src/zero.js +23 -23
  634. package/out/zero-types/src/name-mapper.js.map +1 -1
  635. package/out/zql/src/builder/builder.js +15 -13
  636. package/out/zql/src/builder/builder.js.map +1 -1
  637. package/out/zql/src/builder/correlated-predicate-pushdown.js.map +1 -1
  638. package/out/zql/src/builder/debug-delegate.js.map +1 -1
  639. package/out/zql/src/builder/like.js +0 -1
  640. package/out/zql/src/builder/like.js.map +1 -1
  641. package/out/zql/src/ivm/array-view.js.map +1 -1
  642. package/out/zql/src/ivm/cap.js.map +1 -1
  643. package/out/zql/src/ivm/constraint.js +1 -1
  644. package/out/zql/src/ivm/constraint.js.map +1 -1
  645. package/out/zql/src/ivm/data.js.map +1 -1
  646. package/out/zql/src/ivm/deferred-input.js.map +1 -1
  647. package/out/zql/src/ivm/exists.js +20 -18
  648. package/out/zql/src/ivm/exists.js.map +1 -1
  649. package/out/zql/src/ivm/fan-in.js.map +1 -1
  650. package/out/zql/src/ivm/fan-out.js.map +1 -1
  651. package/out/zql/src/ivm/filter-operators.js +1 -1
  652. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  653. package/out/zql/src/ivm/filter-push.js.map +1 -1
  654. package/out/zql/src/ivm/filter.js.map +1 -1
  655. package/out/zql/src/ivm/flipped-join.js +1 -3
  656. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  657. package/out/zql/src/ivm/join-index-tap.js.map +1 -1
  658. package/out/zql/src/ivm/join-index.js.map +1 -1
  659. package/out/zql/src/ivm/join-utils.js +11 -13
  660. package/out/zql/src/ivm/join-utils.js.map +1 -1
  661. package/out/zql/src/ivm/join.js.map +1 -1
  662. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  663. package/out/zql/src/ivm/memory-source.js +26 -22
  664. package/out/zql/src/ivm/memory-source.js.map +1 -1
  665. package/out/zql/src/ivm/memory-storage.js.map +1 -1
  666. package/out/zql/src/ivm/operator.js +2 -2
  667. package/out/zql/src/ivm/push-accumulated.js +0 -1
  668. package/out/zql/src/ivm/push-accumulated.js.map +1 -1
  669. package/out/zql/src/ivm/skip-yields.js.map +1 -1
  670. package/out/zql/src/ivm/skip.js.map +1 -1
  671. package/out/zql/src/ivm/take-gate.js.map +1 -1
  672. package/out/zql/src/ivm/take.js +1 -1
  673. package/out/zql/src/ivm/take.js.map +1 -1
  674. package/out/zql/src/ivm/union-fan-in.js +1 -1
  675. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  676. package/out/zql/src/ivm/union-fan-out.js.map +1 -1
  677. package/out/zql/src/ivm/view-apply-change.js +1 -1
  678. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  679. package/out/zql/src/mutate/crud.js +3 -2
  680. package/out/zql/src/mutate/crud.js.map +1 -1
  681. package/out/zql/src/planner/planner-builder.js +1 -1
  682. package/out/zql/src/planner/planner-builder.js.map +1 -1
  683. package/out/zql/src/planner/planner-connection.js.map +1 -1
  684. package/out/zql/src/planner/planner-debug.js.map +1 -1
  685. package/out/zql/src/planner/planner-fan-in.js.map +1 -1
  686. package/out/zql/src/planner/planner-fan-out.js.map +1 -1
  687. package/out/zql/src/planner/planner-filter.js.map +1 -1
  688. package/out/zql/src/planner/planner-graph.js +1 -5
  689. package/out/zql/src/planner/planner-graph.js.map +1 -1
  690. package/out/zql/src/planner/planner-join.js +1 -1
  691. package/out/zql/src/planner/planner-join.js.map +1 -1
  692. package/out/zql/src/planner/planner-source.js.map +1 -1
  693. package/out/zql/src/planner/planner-terminus.js.map +1 -1
  694. package/out/zql/src/query/create-builder.js +2 -1
  695. package/out/zql/src/query/create-builder.js.map +1 -1
  696. package/out/zql/src/query/expression.js +1 -1
  697. package/out/zql/src/query/expression.js.map +1 -1
  698. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  699. package/out/zql/src/query/named.js +2 -1
  700. package/out/zql/src/query/named.js.map +1 -1
  701. package/out/zql/src/query/query-delegate-base.js +1 -1
  702. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  703. package/out/zql/src/query/query-impl.js +3 -2
  704. package/out/zql/src/query/query-impl.js.map +1 -1
  705. package/out/zql/src/query/query-registry.js +1 -1
  706. package/out/zql/src/query/query-registry.js.map +1 -1
  707. package/out/zql/src/query/query-transitions.js +1 -1
  708. package/out/zql/src/query/query-transitions.js.map +1 -1
  709. package/out/zql/src/query/runnable-query-impl.js +3 -2
  710. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  711. package/out/zql/src/query/static-query.js.map +1 -1
  712. package/out/zql/src/query/ttl.js +8 -8
  713. package/out/zql/src/query/ttl.js.map +1 -1
  714. package/out/zql/src/query/validate-input.js.map +1 -1
  715. package/out/zqlite/src/database-storage.js +2 -2
  716. package/out/zqlite/src/database-storage.js.map +1 -1
  717. package/out/zqlite/src/db.js +2 -2
  718. package/out/zqlite/src/db.js.map +1 -1
  719. package/out/zqlite/src/explain-queries.js.map +1 -1
  720. package/out/zqlite/src/index-usage.js.map +1 -1
  721. package/out/zqlite/src/internal/sql-inline.js.map +1 -1
  722. package/out/zqlite/src/internal/sql.js.map +1 -1
  723. package/out/zqlite/src/internal/statement-cache.js +1 -1
  724. package/out/zqlite/src/internal/statement-cache.js.map +1 -1
  725. package/out/zqlite/src/mod.js +5 -5
  726. package/out/zqlite/src/pending-delta.js.map +1 -1
  727. package/out/zqlite/src/query-builder.js +1 -1
  728. package/out/zqlite/src/query-builder.js.map +1 -1
  729. package/out/zqlite/src/query-delegate.js.map +1 -1
  730. package/out/zqlite/src/resolve-scalar-subqueries.js +2 -5
  731. package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
  732. package/out/zqlite/src/sqlite-cost-model.js +3 -2
  733. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  734. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  735. package/out/zqlite/src/table-source.js +1 -1
  736. package/out/zqlite/src/table-source.js.map +1 -1
  737. package/package.json +16 -14
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","names":[],"sources":["../../../../../zero-cache/src/custom/fetch.ts"],"sourcesContent":["import {context, propagation} from '@opentelemetry/api';\nimport type {LogContext, LogLevel} from '@rocicorp/logger';\nimport 'urlpattern-polyfill';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport {type Type} from '../../../shared/src/valita.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {\n errorBodySchema,\n isProtocolError,\n type ErrorBody,\n} from '../../../zero-protocol/src/error.ts';\nimport {\n pushErrorSchema,\n type PushError,\n} from '../../../zero-protocol/src/push.ts';\nimport type {ConnectionContext} from '../services/view-syncer/connection-context-manager.ts';\nimport {ProtocolErrorWithLevel} from '../types/error-with-level.ts';\nimport {upstreamSchema, type ShardID} from '../types/shards.ts';\nimport {\n apiAttemptDuration,\n apiAttempts,\n apiInFlight,\n apiRequestDuration,\n apiRequests,\n type ApiAttemptMetricAttrs,\n type ApiAttemptResult,\n type ApiCleanupType,\n type ApiMetricBaseAttrs,\n type ApiOperation,\n type ApiRequestMetricAttrs,\n type ApiRequestResult,\n} from './metrics.ts';\n\nconst reservedParams = ['schema', 'appID'];\n\n/**\n * Compiles and validates a URLPattern from configuration.\n *\n * Patterns must be full URLs (e.g., \"https://api.example.com/endpoint\").\n * URLPattern automatically sets search and hash to wildcard ('*'),\n * which means query parameters and fragments are ignored during matching.\n *\n * @throws Error if the pattern is an invalid URLPattern\n */\nexport function compileUrlPattern(pattern: string): URLPattern {\n try {\n return new URLPattern(pattern);\n } catch (e) {\n throw new Error(\n `Invalid URLPattern in URL configuration: \"${pattern}\". Error: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n}\n\nexport const getBodyPreview = async (\n res: Response,\n lc: LogContext,\n): Promise<string | undefined> => {\n try {\n const body = await res.clone().text();\n if (body.length > 512) {\n return body.slice(0, 512) + '...';\n }\n return body;\n } catch (e) {\n lc.warn?.(\n 'failed to get body preview',\n {\n url: res.url,\n },\n e,\n );\n }\n\n return undefined;\n};\n\nconst MAX_ATTEMPTS = 4;\n\ntype ApiFailedReason =\n | typeof ErrorReason.HTTP\n | typeof ErrorReason.Parse\n | typeof ErrorReason.Internal;\n\nexport type FetchMetricsOptions = {\n operation: ApiOperation;\n cleanupType?: ApiCleanupType | undefined;\n};\n\nexport async function fetchFromAPIServer<T>(\n validator: Type<T>,\n source: 'push' | 'transform',\n lc: LogContext,\n ctx: ConnectionContext,\n shard: ShardID,\n body: ReadonlyJSONValue,\n metricsOpts: FetchMetricsOptions,\n) {\n const metricAttrs: ApiMetricBaseAttrs =\n metricsOpts.operation === 'cleanup' && metricsOpts.cleanupType !== undefined\n ? {\n operation: metricsOpts.operation,\n cleanup_type: metricsOpts.cleanupType,\n }\n : {operation: metricsOpts.operation};\n const requestStart = performance.now();\n let requestMetricAttrs: ApiRequestMetricAttrs | undefined;\n let attemptCount = 0;\n apiInFlight().add(1, metricAttrs);\n\n try {\n const fetchFromAPIServerID = randInt(1, Number.MAX_SAFE_INTEGER).toString(\n 36,\n );\n lc = lc\n .withContext('fetchFromAPIServerID', fetchFromAPIServerID)\n .withContext('source', source);\n\n const fetchConfig =\n source === 'push' ? ctx.mutateContext : ctx.queryContext;\n const url = must(\n fetchConfig.url,\n `Fetch config for ${source} is missing URL`,\n );\n const headerOptions = fetchConfig.headerOptions;\n\n lc.debug?.('fetchFromAPIServer called', {\n url,\n });\n\n if (!urlMatch(url, fetchConfig.allowedUrlPatterns ?? [])) {\n const errorBody = apiFailedBody(\n source,\n ErrorReason.Internal,\n source === 'push'\n ? `URL \"${url}\" is not allowed by the ZERO_MUTATE_URL configuration`\n : `URL \"${url}\" is not allowed by the ZERO_QUERY_URL configuration`,\n );\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'url_not_allowed',\n attemptCount,\n errorBody,\n });\n throw new ProtocolErrorWithLevel(errorBody, 'warn');\n }\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (headerOptions.apiKey) {\n headers['X-Api-Key'] = headerOptions.apiKey;\n }\n Object.assign(\n headers,\n headerOptions.customHeaders,\n headerOptions.requestHeaders,\n );\n if (ctx.auth?.raw) {\n headers['Authorization'] = `Bearer ${ctx.auth.raw}`;\n }\n if (headerOptions.cookie) {\n headers['Cookie'] = headerOptions.cookie;\n }\n if (headerOptions.origin) {\n headers['Origin'] = headerOptions.origin;\n }\n propagation.inject(context.active(), headers);\n\n const urlObj = new URL(url);\n const params = new URLSearchParams(urlObj.search);\n\n for (const reserved of reservedParams) {\n if (params.has(reserved)) {\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'config_error',\n attemptCount,\n });\n throw new Error(\n `The push URL cannot contain the reserved query param \"${reserved}\"`,\n );\n }\n }\n\n params.append('schema', upstreamSchema(shard));\n params.append('appID', shard.appID);\n\n urlObj.search = params.toString();\n\n const finalUrl = urlObj.toString();\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n attemptCount = attempt;\n lc = lc.withContext('fetchFromAPIServerAttempt', attempt);\n lc.debug?.('fetch from API server attempt');\n const sleepBeforeRetry = async () => {\n if (attempt < MAX_ATTEMPTS) {\n const delayMs = getBackoffDelayMs(attempt);\n lc.debug?.(`fetch from API server retrying in ${delayMs} ms`);\n await sleep(delayMs);\n return true;\n }\n lc.debug?.('fetch from API server reached max attempts, not retrying');\n return false;\n };\n const attemptStart = performance.now();\n try {\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const bodyPreview = await getBodyPreview(response, lc);\n lc.warn?.('fetch from API server returned non-OK status', {\n url: finalUrl,\n status: response.status,\n bodyPreview,\n });\n // Server errors can be transient, so retry if attempts remain.\n const willRetry =\n response.status >= 500 &&\n response.status < 600 &&\n attempt < MAX_ATTEMPTS;\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'http_error',\n willRetry,\n response,\n });\n if (willRetry && (await sleepBeforeRetry())) {\n continue;\n }\n\n const errorBody = apiFailedBody(\n source,\n ErrorReason.HTTP,\n `Fetch from API server returned non-OK status ${response.status}`,\n response,\n bodyPreview,\n );\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'http_error',\n attemptCount,\n response,\n errorBody,\n });\n throw new ProtocolErrorWithLevel(errorBody, 'warn');\n }\n\n try {\n const json = await response.json();\n const result = validator.parse(json, {\n mode: 'passthrough',\n });\n const apiError = apiErrorFromResult(result);\n if (apiError) {\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'api_error',\n willRetry: false,\n response,\n errorBody: apiError,\n });\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'api_error',\n attemptCount,\n response,\n errorBody: apiError,\n });\n } else {\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'success',\n willRetry: false,\n response,\n });\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'success',\n attemptCount,\n response,\n });\n }\n lc.debug?.('fetch from API server succeeded');\n return result;\n } catch (error) {\n lc.warn?.(\n 'failed to parse response',\n {\n url: finalUrl,\n },\n error,\n );\n\n const errorBody = apiFailedBody(\n source,\n ErrorReason.Parse,\n `Failed to parse response from API server: ${getErrorMessage(error)}`,\n );\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'parse_error',\n willRetry: false,\n response,\n errorBody,\n });\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'parse_error',\n attemptCount,\n response,\n errorBody,\n });\n throw new ProtocolErrorWithLevel(errorBody, 'warn', {cause: error});\n }\n } catch (error) {\n if (isProtocolError(error)) {\n throw error;\n }\n\n const isFetchFailed =\n error instanceof TypeError && error.message === 'fetch failed';\n // unexpected/unknown errors should be logged at 'error' level so they\n // are investigated\n let logLevel: LogLevel = isFetchFailed ? 'warn' : 'error';\n lc[logLevel]?.(\n 'fetch from API server threw error',\n {url: finalUrl},\n error,\n );\n\n const willRetry = isFetchFailed && attempt < MAX_ATTEMPTS;\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'fetch_error',\n willRetry,\n });\n\n if (willRetry && (await sleepBeforeRetry())) {\n continue;\n }\n\n const errorBody = apiFailedBody(\n source,\n ErrorReason.Internal,\n `Fetch from API server threw error: ${getErrorMessage(error)}`,\n );\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'fetch_error',\n attemptCount,\n errorBody,\n });\n throw new ProtocolErrorWithLevel(errorBody, logLevel, {cause: error});\n }\n }\n unreachable();\n } finally {\n const attrs =\n requestMetricAttrs ??\n apiRequestMetricAttrs(metricAttrs, {\n result: attemptCount === 0 ? 'config_error' : 'fetch_error',\n attemptCount,\n });\n apiRequests().add(1, attrs);\n apiRequestDuration().recordMs(performance.now() - requestStart, attrs);\n apiInFlight().add(-1, metricAttrs);\n }\n}\n\n/**\n * Returns true if the url matches one of the allowedUrlPatterns.\n *\n * URLPattern automatically ignores query parameters and hash fragments during matching\n * because it sets search and hash to wildcard ('*') by default.\n *\n * Example URLPattern patterns:\n * - \"https://api.example.com/endpoint\" - Exact match for a specific URL\n * - \"https://*.example.com/endpoint\" - Matches any single subdomain (e.g., \"https://api.example.com/endpoint\")\n * - \"https://*.*.example.com/endpoint\" - Matches two subdomains (e.g., \"https://api.v1.example.com/endpoint\")\n * - \"https://api.example.com/*\" - Matches any path under /\n * - \"https://api.example.com/:version/endpoint\" - Matches with named parameter (e.g., \"https://api.example.com/v1/endpoint\")\n */\nexport function urlMatch(\n url: string,\n allowedUrlPatterns: readonly URLPattern[],\n): boolean {\n for (const pattern of allowedUrlPatterns) {\n if (pattern.test(url)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Returns the delay in milliseconds for the next retry attempt using exponential backoff with jitter.\n *\n * The delay assumes the first retry is attempt 1.\n * The formula is: `min(1000, 100 * 2^(attempt - 1) + jitter)` where jitter is between 0 and 100ms.\n */\nfunction getBackoffDelayMs(attempt: number): number {\n return Math.min(1000, 100 * Math.pow(2, attempt - 1) + Math.random() * 100);\n}\n\nfunction apiFailedBody(\n source: 'push' | 'transform',\n reason: ApiFailedReason,\n message: string,\n response?: Response | undefined,\n bodyPreview?: string | undefined,\n): ErrorBody {\n if (source === 'push') {\n return reason === ErrorReason.HTTP\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason,\n status: response?.status ?? 0,\n ...(bodyPreview !== undefined ? {bodyPreview} : {}),\n message,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason,\n message,\n mutationIDs: [],\n };\n }\n\n return reason === ErrorReason.HTTP\n ? {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason,\n status: response?.status ?? 0,\n ...(bodyPreview !== undefined ? {bodyPreview} : {}),\n message,\n queryIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason,\n message,\n queryIDs: [],\n };\n}\n\ntype ApiErrorMetricBody = {\n kind: ErrorBody['kind'];\n reason?: string | undefined;\n};\n\nfunction apiErrorFromResult(result: unknown): ApiErrorMetricBody | undefined {\n const parsed = errorBodySchema.try(result, {mode: 'passthrough'});\n if (parsed.ok) {\n return parsed.value;\n }\n\n if (Array.isArray(result) && result[0] === 'transformFailed') {\n const legacyTransformFailed = errorBodySchema.try(result[1], {\n mode: 'passthrough',\n });\n return legacyTransformFailed.ok ? legacyTransformFailed.value : undefined;\n }\n\n const legacyPushError = pushErrorSchema.try(result, {mode: 'passthrough'});\n return legacyPushError.ok\n ? {\n kind: ErrorKind.PushFailed,\n reason: legacyPushErrorReason(legacyPushError.value.error),\n }\n : undefined;\n}\n\nfunction legacyPushErrorReason(error: PushError['error']): string {\n switch (error) {\n case 'http':\n return ErrorReason.HTTP;\n case 'unsupportedPushVersion':\n return ErrorReason.UnsupportedPushVersion;\n case 'unsupportedSchemaVersion':\n case 'zeroPusher':\n return ErrorReason.Internal;\n }\n}\n\ntype ApiResponseErrorMetricAttrs = Pick<\n ApiRequestMetricAttrs,\n 'http_status_code' | 'http_status_class' | 'error_kind' | 'error_reason'\n>;\n\ntype ApiRequestMetricAttrsOptions = {\n result: ApiRequestResult;\n attemptCount: number;\n response?: Response | undefined;\n errorBody?: ApiErrorMetricBody | undefined;\n};\n\ntype ApiAttemptMetricAttrsOptions = {\n attempt: number;\n result: ApiAttemptResult;\n willRetry: boolean;\n response?: Response | undefined;\n errorBody?: ApiErrorMetricBody | undefined;\n};\n\nfunction apiRequestMetricAttrs(\n baseAttrs: ApiMetricBaseAttrs,\n {result, attemptCount, response, errorBody}: ApiRequestMetricAttrsOptions,\n): ApiRequestMetricAttrs {\n return {\n ...baseAttrs,\n result,\n attempt_count: attemptCount,\n ...apiResponseErrorMetricAttrs(response, errorBody),\n };\n}\n\nfunction apiResponseErrorMetricAttrs(\n response: Response | undefined,\n errorBody: ApiErrorMetricBody | undefined,\n): ApiResponseErrorMetricAttrs {\n const attrs: ApiResponseErrorMetricAttrs = {};\n\n if (response) {\n attrs.http_status_code = response.status;\n attrs.http_status_class = `${Math.floor(response.status / 100)}xx`;\n }\n\n if (errorBody) {\n attrs.error_kind = errorBody.kind;\n if (errorBody.reason !== undefined) {\n attrs.error_reason = errorBody.reason;\n }\n }\n\n return attrs;\n}\n\nfunction recordApiAttempt(\n durationMs: number,\n baseAttrs: ApiMetricBaseAttrs,\n {\n attempt,\n result,\n willRetry,\n response,\n errorBody,\n }: ApiAttemptMetricAttrsOptions,\n) {\n const attrs: ApiAttemptMetricAttrs = {\n ...baseAttrs,\n attempt,\n result,\n will_retry: willRetry,\n ...apiResponseErrorMetricAttrs(response, errorBody),\n };\n apiAttempts().add(1, attrs);\n apiAttemptDuration().recordMs(durationMs, attrs);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAwCA,IAAM,iBAAiB,CAAC,UAAU,OAAO;;;;;;;;;;AAWzC,SAAgB,kBAAkB,SAA6B;CAC7D,IAAI;EACF,OAAO,IAAI,WAAW,OAAO;CAC/B,SAAS,GAAG;EACV,MAAM,IAAI,MACR,6CAA6C,QAAQ,YAAY,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAC5G;CACF;AACF;AAEA,IAAa,iBAAiB,OAC5B,KACA,OACgC;CAChC,IAAI;EACF,MAAM,OAAO,MAAM,IAAI,MAAM,EAAE,KAAK;EACpC,IAAI,KAAK,SAAS,KAChB,OAAO,KAAK,MAAM,GAAG,GAAG,IAAI;EAE9B,OAAO;CACT,SAAS,GAAG;EACV,GAAG,OACD,8BACA,EACE,KAAK,IAAI,IACX,GACA,CACF;CACF;AAGF;AAEA,IAAM,eAAe;AAYrB,eAAsB,mBACpB,WACA,QACA,IACA,KACA,OACA,MACA,aACA;CACA,MAAM,cACJ,YAAY,cAAc,aAAa,YAAY,gBAAgB,KAAA,IAC/D;EACE,WAAW,YAAY;EACvB,cAAc,YAAY;CAC5B,IACA,EAAC,WAAW,YAAY,UAAS;CACvC,MAAM,eAAe,YAAY,IAAI;CACrC,IAAI;CACJ,IAAI,eAAe;CACnB,YAAY,EAAE,IAAI,GAAG,WAAW;CAEhC,IAAI;EACF,MAAM,uBAAuB,QAAQ,GAAG,OAAO,gBAAgB,EAAE,SAC/D,EACF;EACA,KAAK,GACF,YAAY,wBAAwB,oBAAoB,EACxD,YAAY,UAAU,MAAM;EAE/B,MAAM,cACJ,WAAW,SAAS,IAAI,gBAAgB,IAAI;EAC9C,MAAM,MAAM,KACV,YAAY,KACZ,oBAAoB,OAAO,gBAC7B;EACA,MAAM,gBAAgB,YAAY;EAElC,GAAG,QAAQ,6BAA6B,EACtC,IACF,CAAC;EAED,IAAI,CAAC,SAAS,KAAK,YAAY,sBAAsB,CAAC,CAAC,GAAG;GACxD,MAAM,YAAY,cAChB,QACA,UACA,WAAW,SACP,QAAQ,IAAI,yDACZ,QAAQ,IAAI,qDAClB;GACA,qBAAqB,sBAAsB,aAAa;IACtD,QAAQ;IACR;IACA;GACF,CAAC;GACD,MAAM,IAAI,uBAAuB,WAAW,MAAM;EACpD;EACA,MAAM,UAAkC,EACtC,gBAAgB,mBAClB;EAEA,IAAI,cAAc,QAChB,QAAQ,eAAe,cAAc;EAEvC,OAAO,OACL,SACA,cAAc,eACd,cAAc,cAChB;EACA,IAAI,IAAI,MAAM,KACZ,QAAQ,mBAAmB,UAAU,IAAI,KAAK;EAEhD,IAAI,cAAc,QAChB,QAAQ,YAAY,cAAc;EAEpC,IAAI,cAAc,QAChB,QAAQ,YAAY,cAAc;EAEpC,YAAY,OAAO,QAAQ,OAAO,GAAG,OAAO;EAE5C,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,MAAM,SAAS,IAAI,gBAAgB,OAAO,MAAM;EAEhD,KAAK,MAAM,YAAY,gBACrB,IAAI,OAAO,IAAI,QAAQ,GAAG;GACxB,qBAAqB,sBAAsB,aAAa;IACtD,QAAQ;IACR;GACF,CAAC;GACD,MAAM,IAAI,MACR,yDAAyD,SAAS,EACpE;EACF;EAGF,OAAO,OAAO,UAAU,eAAe,KAAK,CAAC;EAC7C,OAAO,OAAO,SAAS,MAAM,KAAK;EAElC,OAAO,SAAS,OAAO,SAAS;EAEhC,MAAM,WAAW,OAAO,SAAS;EAEjC,KAAK,IAAI,UAAU,GAAG,WAAW,cAAc,WAAW;GACxD,eAAe;GACf,KAAK,GAAG,YAAY,6BAA6B,OAAO;GACxD,GAAG,QAAQ,+BAA+B;GAC1C,MAAM,mBAAmB,YAAY;IACnC,IAAI,UAAU,cAAc;KAC1B,MAAM,UAAU,kBAAkB,OAAO;KACzC,GAAG,QAAQ,qCAAqC,QAAQ,IAAI;KAC5D,MAAM,MAAM,OAAO;KACnB,OAAO;IACT;IACA,GAAG,QAAQ,0DAA0D;IACrE,OAAO;GACT;GACA,MAAM,eAAe,YAAY,IAAI;GACrC,IAAI;IACF,MAAM,WAAW,MAAM,MAAM,UAAU;KACrC,QAAQ;KACR;KACA,MAAM,KAAK,UAAU,IAAI;IAC3B,CAAC;IAED,IAAI,CAAC,SAAS,IAAI;KAChB,MAAM,cAAc,MAAM,eAAe,UAAU,EAAE;KACrD,GAAG,OAAO,gDAAgD;MACxD,KAAK;MACL,QAAQ,SAAS;MACjB;KACF,CAAC;KAED,MAAM,YACJ,SAAS,UAAU,OACnB,SAAS,SAAS,OAClB,UAAU;KACZ,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;MAC9D;MACA,QAAQ;MACR;MACA;KACF,CAAC;KACD,IAAI,aAAc,MAAM,iBAAiB,GACvC;KAGF,MAAM,YAAY,cAChB,QACA,MACA,gDAAgD,SAAS,UACzD,UACA,WACF;KACA,qBAAqB,sBAAsB,aAAa;MACtD,QAAQ;MACR;MACA;MACA;KACF,CAAC;KACD,MAAM,IAAI,uBAAuB,WAAW,MAAM;IACpD;IAEA,IAAI;KACF,MAAM,OAAO,MAAM,SAAS,KAAK;KACjC,MAAM,SAAS,UAAU,MAAM,MAAM,EACnC,MAAM,cACR,CAAC;KACD,MAAM,WAAW,mBAAmB,MAAM;KAC1C,IAAI,UAAU;MACZ,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;OAC9D;OACA,QAAQ;OACR,WAAW;OACX;OACA,WAAW;MACb,CAAC;MACD,qBAAqB,sBAAsB,aAAa;OACtD,QAAQ;OACR;OACA;OACA,WAAW;MACb,CAAC;KACH,OAAO;MACL,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;OAC9D;OACA,QAAQ;OACR,WAAW;OACX;MACF,CAAC;MACD,qBAAqB,sBAAsB,aAAa;OACtD,QAAQ;OACR;OACA;MACF,CAAC;KACH;KACA,GAAG,QAAQ,iCAAiC;KAC5C,OAAO;IACT,SAAS,OAAO;KACd,GAAG,OACD,4BACA,EACE,KAAK,SACP,GACA,KACF;KAEA,MAAM,YAAY,cAChB,QACA,OACA,6CAA6C,gBAAgB,KAAK,GACpE;KACA,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;MAC9D;MACA,QAAQ;MACR,WAAW;MACX;MACA;KACF,CAAC;KACD,qBAAqB,sBAAsB,aAAa;MACtD,QAAQ;MACR;MACA;MACA;KACF,CAAC;KACD,MAAM,IAAI,uBAAuB,WAAW,QAAQ,EAAC,OAAO,MAAK,CAAC;IACpE;GACF,SAAS,OAAO;IACd,IAAI,gBAAgB,KAAK,GACvB,MAAM;IAGR,MAAM,gBACJ,iBAAiB,aAAa,MAAM,YAAY;IAGlD,IAAI,WAAqB,gBAAgB,SAAS;IAClD,GAAG,YACD,qCACA,EAAC,KAAK,SAAQ,GACd,KACF;IAEA,MAAM,YAAY,iBAAiB,UAAU;IAC7C,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;KAC9D;KACA,QAAQ;KACR;IACF,CAAC;IAED,IAAI,aAAc,MAAM,iBAAiB,GACvC;IAGF,MAAM,YAAY,cAChB,QACA,UACA,sCAAsC,gBAAgB,KAAK,GAC7D;IACA,qBAAqB,sBAAsB,aAAa;KACtD,QAAQ;KACR;KACA;IACF,CAAC;IACD,MAAM,IAAI,uBAAuB,WAAW,UAAU,EAAC,OAAO,MAAK,CAAC;GACtE;EACF;EACA,YAAY;CACd,UAAU;EACR,MAAM,QACJ,sBACA,sBAAsB,aAAa;GACjC,QAAQ,iBAAiB,IAAI,iBAAiB;GAC9C;EACF,CAAC;EACH,YAAY,EAAE,IAAI,GAAG,KAAK;EAC1B,mBAAmB,EAAE,SAAS,YAAY,IAAI,IAAI,cAAc,KAAK;EACrE,YAAY,EAAE,IAAI,IAAI,WAAW;CACnC;AACF;;;;;;;;;;;;;;AAeA,SAAgB,SACd,KACA,oBACS;CACT,KAAK,MAAM,WAAW,oBACpB,IAAI,QAAQ,KAAK,GAAG,GAClB,OAAO;CAGX,OAAO;AACT;;;;;;;AAQA,SAAS,kBAAkB,SAAyB;CAClD,OAAO,KAAK,IAAI,KAAM,MAAM,KAAK,IAAI,GAAG,UAAU,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG;AAC5E;AAEA,SAAS,cACP,QACA,QACA,SACA,UACA,aACW;CACX,IAAI,WAAW,QACb,OAAO,WAAW,SACd;EACE,MAAM;EACN,QAAQ;EACR;EACA,QAAQ,UAAU,UAAU;EAC5B,GAAI,gBAAgB,KAAA,IAAY,EAAC,YAAW,IAAI,CAAC;EACjD;EACA,aAAa,CAAC;CAChB,IACA;EACE,MAAM;EACN,QAAQ;EACR;EACA;EACA,aAAa,CAAC;CAChB;CAGN,OAAO,WAAW,SACd;EACE,MAAM;EACN,QAAQ;EACR;EACA,QAAQ,UAAU,UAAU;EAC5B,GAAI,gBAAgB,KAAA,IAAY,EAAC,YAAW,IAAI,CAAC;EACjD;EACA,UAAU,CAAC;CACb,IACA;EACE,MAAM;EACN,QAAQ;EACR;EACA;EACA,UAAU,CAAC;CACb;AACN;AAOA,SAAS,mBAAmB,QAAiD;CAC3E,MAAM,SAAS,gBAAgB,IAAI,QAAQ,EAAC,MAAM,cAAa,CAAC;CAChE,IAAI,OAAO,IACT,OAAO,OAAO;CAGhB,IAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,mBAAmB;EAC5D,MAAM,wBAAwB,gBAAgB,IAAI,OAAO,IAAI,EAC3D,MAAM,cACR,CAAC;EACD,OAAO,sBAAsB,KAAK,sBAAsB,QAAQ,KAAA;CAClE;CAEA,MAAM,kBAAkB,gBAAgB,IAAI,QAAQ,EAAC,MAAM,cAAa,CAAC;CACzE,OAAO,gBAAgB,KACnB;EACE,MAAM;EACN,QAAQ,sBAAsB,gBAAgB,MAAM,KAAK;CAC3D,IACA,KAAA;AACN;AAEA,SAAS,sBAAsB,OAAmC;CAChE,QAAQ,OAAR;EACE,KAAK,QACH,OAAO;EACT,KAAK,0BACH,OAAO;EACT,KAAK;EACL,KAAK,cACH,OAAO;CACX;AACF;AAsBA,SAAS,sBACP,WACA,EAAC,QAAQ,cAAc,UAAU,aACV;CACvB,OAAO;EACL,GAAG;EACH;EACA,eAAe;EACf,GAAG,4BAA4B,UAAU,SAAS;CACpD;AACF;AAEA,SAAS,4BACP,UACA,WAC6B;CAC7B,MAAM,QAAqC,CAAC;CAE5C,IAAI,UAAU;EACZ,MAAM,mBAAmB,SAAS;EAClC,MAAM,oBAAoB,GAAG,KAAK,MAAM,SAAS,SAAS,GAAG,EAAE;CACjE;CAEA,IAAI,WAAW;EACb,MAAM,aAAa,UAAU;EAC7B,IAAI,UAAU,WAAW,KAAA,GACvB,MAAM,eAAe,UAAU;CAEnC;CAEA,OAAO;AACT;AAEA,SAAS,iBACP,YACA,WACA,EACE,SACA,QACA,WACA,UACA,aAEF;CACA,MAAM,QAA+B;EACnC,GAAG;EACH;EACA;EACA,YAAY;EACZ,GAAG,4BAA4B,UAAU,SAAS;CACpD;CACA,YAAY,EAAE,IAAI,GAAG,KAAK;CAC1B,mBAAmB,EAAE,SAAS,YAAY,KAAK;AACjD"}
1
+ {"version":3,"file":"fetch.js","names":["ErrorReason.Internal","ErrorReason.HTTP","ErrorReason.Parse","ErrorKind.PushFailed","ErrorOrigin.ZeroCache","ErrorKind.TransformFailed","ErrorReason.UnsupportedPushVersion"],"sources":["../../../../../zero-cache/src/custom/fetch.ts"],"sourcesContent":["import {context, propagation} from '@opentelemetry/api';\nimport type {LogContext, LogLevel} from '@rocicorp/logger';\nimport 'urlpattern-polyfill';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport {type Type} from '../../../shared/src/valita.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {\n errorBodySchema,\n isProtocolError,\n type ErrorBody,\n} from '../../../zero-protocol/src/error.ts';\nimport {\n pushErrorSchema,\n type PushError,\n} from '../../../zero-protocol/src/push.ts';\nimport type {ConnectionContext} from '../services/view-syncer/connection-context-manager.ts';\nimport {ProtocolErrorWithLevel} from '../types/error-with-level.ts';\nimport {upstreamSchema, type ShardID} from '../types/shards.ts';\nimport {\n apiAttemptDuration,\n apiAttempts,\n apiInFlight,\n apiRequestDuration,\n apiRequests,\n type ApiAttemptMetricAttrs,\n type ApiAttemptResult,\n type ApiCleanupType,\n type ApiMetricBaseAttrs,\n type ApiOperation,\n type ApiRequestMetricAttrs,\n type ApiRequestResult,\n} from './metrics.ts';\n\nconst reservedParams = ['schema', 'appID'];\n\n/**\n * Compiles and validates a URLPattern from configuration.\n *\n * Patterns must be full URLs (e.g., \"https://api.example.com/endpoint\").\n * URLPattern automatically sets search and hash to wildcard ('*'),\n * which means query parameters and fragments are ignored during matching.\n *\n * @throws Error if the pattern is an invalid URLPattern\n */\nexport function compileUrlPattern(pattern: string): URLPattern {\n try {\n return new URLPattern(pattern);\n } catch (e) {\n throw new Error(\n `Invalid URLPattern in URL configuration: \"${pattern}\". Error: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n}\n\nexport const getBodyPreview = async (\n res: Response,\n lc: LogContext,\n): Promise<string | undefined> => {\n try {\n const body = await res.clone().text();\n if (body.length > 512) {\n return body.slice(0, 512) + '...';\n }\n return body;\n } catch (e) {\n lc.warn?.(\n 'failed to get body preview',\n {\n url: res.url,\n },\n e,\n );\n }\n\n return undefined;\n};\n\nconst MAX_ATTEMPTS = 4;\n\ntype ApiFailedReason =\n | typeof ErrorReason.HTTP\n | typeof ErrorReason.Parse\n | typeof ErrorReason.Internal;\n\nexport type FetchMetricsOptions = {\n operation: ApiOperation;\n cleanupType?: ApiCleanupType | undefined;\n};\n\nexport async function fetchFromAPIServer<T>(\n validator: Type<T>,\n source: 'push' | 'transform',\n lc: LogContext,\n ctx: ConnectionContext,\n shard: ShardID,\n body: ReadonlyJSONValue,\n metricsOpts: FetchMetricsOptions,\n) {\n const metricAttrs: ApiMetricBaseAttrs =\n metricsOpts.operation === 'cleanup' && metricsOpts.cleanupType !== undefined\n ? {\n operation: metricsOpts.operation,\n cleanup_type: metricsOpts.cleanupType,\n }\n : {operation: metricsOpts.operation};\n const requestStart = performance.now();\n let requestMetricAttrs: ApiRequestMetricAttrs | undefined;\n let attemptCount = 0;\n apiInFlight().add(1, metricAttrs);\n\n try {\n const fetchFromAPIServerID = randInt(1, Number.MAX_SAFE_INTEGER).toString(\n 36,\n );\n lc = lc\n .withContext('fetchFromAPIServerID', fetchFromAPIServerID)\n .withContext('source', source);\n\n const fetchConfig =\n source === 'push' ? ctx.mutateContext : ctx.queryContext;\n const url = must(\n fetchConfig.url,\n `Fetch config for ${source} is missing URL`,\n );\n const headerOptions = fetchConfig.headerOptions;\n\n lc.debug?.('fetchFromAPIServer called', {\n url,\n });\n\n if (!urlMatch(url, fetchConfig.allowedUrlPatterns ?? [])) {\n const errorBody = apiFailedBody(\n source,\n ErrorReason.Internal,\n source === 'push'\n ? `URL \"${url}\" is not allowed by the ZERO_MUTATE_URL configuration`\n : `URL \"${url}\" is not allowed by the ZERO_QUERY_URL configuration`,\n );\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'url_not_allowed',\n attemptCount,\n errorBody,\n });\n throw new ProtocolErrorWithLevel(errorBody, 'warn');\n }\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (headerOptions.apiKey) {\n headers['X-Api-Key'] = headerOptions.apiKey;\n }\n Object.assign(\n headers,\n headerOptions.customHeaders,\n headerOptions.requestHeaders,\n );\n if (ctx.auth?.raw) {\n headers['Authorization'] = `Bearer ${ctx.auth.raw}`;\n }\n if (headerOptions.cookie) {\n headers['Cookie'] = headerOptions.cookie;\n }\n if (headerOptions.origin) {\n headers['Origin'] = headerOptions.origin;\n }\n propagation.inject(context.active(), headers);\n\n const urlObj = new URL(url);\n const params = new URLSearchParams(urlObj.search);\n\n for (const reserved of reservedParams) {\n if (params.has(reserved)) {\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'config_error',\n attemptCount,\n });\n throw new Error(\n `The push URL cannot contain the reserved query param \"${reserved}\"`,\n );\n }\n }\n\n params.append('schema', upstreamSchema(shard));\n params.append('appID', shard.appID);\n\n urlObj.search = params.toString();\n\n const finalUrl = urlObj.toString();\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n attemptCount = attempt;\n lc = lc.withContext('fetchFromAPIServerAttempt', attempt);\n lc.debug?.('fetch from API server attempt');\n const sleepBeforeRetry = async () => {\n if (attempt < MAX_ATTEMPTS) {\n const delayMs = getBackoffDelayMs(attempt);\n lc.debug?.(`fetch from API server retrying in ${delayMs} ms`);\n await sleep(delayMs);\n return true;\n }\n lc.debug?.('fetch from API server reached max attempts, not retrying');\n return false;\n };\n const attemptStart = performance.now();\n try {\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const bodyPreview = await getBodyPreview(response, lc);\n lc.warn?.('fetch from API server returned non-OK status', {\n url: finalUrl,\n status: response.status,\n bodyPreview,\n });\n // Server errors can be transient, so retry if attempts remain.\n const willRetry =\n response.status >= 500 &&\n response.status < 600 &&\n attempt < MAX_ATTEMPTS;\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'http_error',\n willRetry,\n response,\n });\n if (willRetry && (await sleepBeforeRetry())) {\n continue;\n }\n\n const errorBody = apiFailedBody(\n source,\n ErrorReason.HTTP,\n `Fetch from API server returned non-OK status ${response.status}`,\n response,\n bodyPreview,\n );\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'http_error',\n attemptCount,\n response,\n errorBody,\n });\n throw new ProtocolErrorWithLevel(errorBody, 'warn');\n }\n\n try {\n const json = await response.json();\n const result = validator.parse(json, {\n mode: 'passthrough',\n });\n const apiError = apiErrorFromResult(result);\n if (apiError) {\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'api_error',\n willRetry: false,\n response,\n errorBody: apiError,\n });\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'api_error',\n attemptCount,\n response,\n errorBody: apiError,\n });\n } else {\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'success',\n willRetry: false,\n response,\n });\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'success',\n attemptCount,\n response,\n });\n }\n lc.debug?.('fetch from API server succeeded');\n return result;\n } catch (error) {\n lc.warn?.(\n 'failed to parse response',\n {\n url: finalUrl,\n },\n error,\n );\n\n const errorBody = apiFailedBody(\n source,\n ErrorReason.Parse,\n `Failed to parse response from API server: ${getErrorMessage(error)}`,\n );\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'parse_error',\n willRetry: false,\n response,\n errorBody,\n });\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'parse_error',\n attemptCount,\n response,\n errorBody,\n });\n throw new ProtocolErrorWithLevel(errorBody, 'warn', {cause: error});\n }\n } catch (error) {\n if (isProtocolError(error)) {\n throw error;\n }\n\n const isFetchFailed =\n error instanceof TypeError && error.message === 'fetch failed';\n // unexpected/unknown errors should be logged at 'error' level so they\n // are investigated\n let logLevel: LogLevel = isFetchFailed ? 'warn' : 'error';\n lc[logLevel]?.(\n 'fetch from API server threw error',\n {url: finalUrl},\n error,\n );\n\n const willRetry = isFetchFailed && attempt < MAX_ATTEMPTS;\n recordApiAttempt(performance.now() - attemptStart, metricAttrs, {\n attempt,\n result: 'fetch_error',\n willRetry,\n });\n\n if (willRetry && (await sleepBeforeRetry())) {\n continue;\n }\n\n const errorBody = apiFailedBody(\n source,\n ErrorReason.Internal,\n `Fetch from API server threw error: ${getErrorMessage(error)}`,\n );\n requestMetricAttrs = apiRequestMetricAttrs(metricAttrs, {\n result: 'fetch_error',\n attemptCount,\n errorBody,\n });\n throw new ProtocolErrorWithLevel(errorBody, logLevel, {cause: error});\n }\n }\n unreachable();\n } finally {\n const attrs =\n requestMetricAttrs ??\n apiRequestMetricAttrs(metricAttrs, {\n result: attemptCount === 0 ? 'config_error' : 'fetch_error',\n attemptCount,\n });\n apiRequests().add(1, attrs);\n apiRequestDuration().recordMs(performance.now() - requestStart, attrs);\n apiInFlight().add(-1, metricAttrs);\n }\n}\n\n/**\n * Returns true if the url matches one of the allowedUrlPatterns.\n *\n * URLPattern automatically ignores query parameters and hash fragments during matching\n * because it sets search and hash to wildcard ('*') by default.\n *\n * Example URLPattern patterns:\n * - \"https://api.example.com/endpoint\" - Exact match for a specific URL\n * - \"https://*.example.com/endpoint\" - Matches any single subdomain (e.g., \"https://api.example.com/endpoint\")\n * - \"https://*.*.example.com/endpoint\" - Matches two subdomains (e.g., \"https://api.v1.example.com/endpoint\")\n * - \"https://api.example.com/*\" - Matches any path under /\n * - \"https://api.example.com/:version/endpoint\" - Matches with named parameter (e.g., \"https://api.example.com/v1/endpoint\")\n */\nexport function urlMatch(\n url: string,\n allowedUrlPatterns: readonly URLPattern[],\n): boolean {\n for (const pattern of allowedUrlPatterns) {\n if (pattern.test(url)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Returns the delay in milliseconds for the next retry attempt using exponential backoff with jitter.\n *\n * The delay assumes the first retry is attempt 1.\n * The formula is: `min(1000, 100 * 2^(attempt - 1) + jitter)` where jitter is between 0 and 100ms.\n */\nfunction getBackoffDelayMs(attempt: number): number {\n return Math.min(1000, 100 * Math.pow(2, attempt - 1) + Math.random() * 100);\n}\n\nfunction apiFailedBody(\n source: 'push' | 'transform',\n reason: ApiFailedReason,\n message: string,\n response?: Response | undefined,\n bodyPreview?: string | undefined,\n): ErrorBody {\n if (source === 'push') {\n return reason === ErrorReason.HTTP\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason,\n status: response?.status ?? 0,\n ...(bodyPreview !== undefined ? {bodyPreview} : {}),\n message,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason,\n message,\n mutationIDs: [],\n };\n }\n\n return reason === ErrorReason.HTTP\n ? {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason,\n status: response?.status ?? 0,\n ...(bodyPreview !== undefined ? {bodyPreview} : {}),\n message,\n queryIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason,\n message,\n queryIDs: [],\n };\n}\n\ntype ApiErrorMetricBody = {\n kind: ErrorBody['kind'];\n reason?: string | undefined;\n};\n\nfunction apiErrorFromResult(result: unknown): ApiErrorMetricBody | undefined {\n const parsed = errorBodySchema.try(result, {mode: 'passthrough'});\n if (parsed.ok) {\n return parsed.value;\n }\n\n if (Array.isArray(result) && result[0] === 'transformFailed') {\n const legacyTransformFailed = errorBodySchema.try(result[1], {\n mode: 'passthrough',\n });\n return legacyTransformFailed.ok ? legacyTransformFailed.value : undefined;\n }\n\n const legacyPushError = pushErrorSchema.try(result, {mode: 'passthrough'});\n return legacyPushError.ok\n ? {\n kind: ErrorKind.PushFailed,\n reason: legacyPushErrorReason(legacyPushError.value.error),\n }\n : undefined;\n}\n\nfunction legacyPushErrorReason(error: PushError['error']): string {\n switch (error) {\n case 'http':\n return ErrorReason.HTTP;\n case 'unsupportedPushVersion':\n return ErrorReason.UnsupportedPushVersion;\n case 'unsupportedSchemaVersion':\n case 'zeroPusher':\n return ErrorReason.Internal;\n }\n}\n\ntype ApiResponseErrorMetricAttrs = Pick<\n ApiRequestMetricAttrs,\n 'http_status_code' | 'http_status_class' | 'error_kind' | 'error_reason'\n>;\n\ntype ApiRequestMetricAttrsOptions = {\n result: ApiRequestResult;\n attemptCount: number;\n response?: Response | undefined;\n errorBody?: ApiErrorMetricBody | undefined;\n};\n\ntype ApiAttemptMetricAttrsOptions = {\n attempt: number;\n result: ApiAttemptResult;\n willRetry: boolean;\n response?: Response | undefined;\n errorBody?: ApiErrorMetricBody | undefined;\n};\n\nfunction apiRequestMetricAttrs(\n baseAttrs: ApiMetricBaseAttrs,\n {result, attemptCount, response, errorBody}: ApiRequestMetricAttrsOptions,\n): ApiRequestMetricAttrs {\n return {\n ...baseAttrs,\n result,\n attempt_count: attemptCount,\n ...apiResponseErrorMetricAttrs(response, errorBody),\n };\n}\n\nfunction apiResponseErrorMetricAttrs(\n response: Response | undefined,\n errorBody: ApiErrorMetricBody | undefined,\n): ApiResponseErrorMetricAttrs {\n const attrs: ApiResponseErrorMetricAttrs = {};\n\n if (response) {\n attrs.http_status_code = response.status;\n attrs.http_status_class = `${Math.floor(response.status / 100)}xx`;\n }\n\n if (errorBody) {\n attrs.error_kind = errorBody.kind;\n if (errorBody.reason !== undefined) {\n attrs.error_reason = errorBody.reason;\n }\n }\n\n return attrs;\n}\n\nfunction recordApiAttempt(\n durationMs: number,\n baseAttrs: ApiMetricBaseAttrs,\n {\n attempt,\n result,\n willRetry,\n response,\n errorBody,\n }: ApiAttemptMetricAttrsOptions,\n) {\n const attrs: ApiAttemptMetricAttrs = {\n ...baseAttrs,\n attempt,\n result,\n will_retry: willRetry,\n ...apiResponseErrorMetricAttrs(response, errorBody),\n };\n apiAttempts().add(1, attrs);\n apiAttemptDuration().recordMs(durationMs, attrs);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAwCA,IAAM,iBAAiB,CAAC,UAAU,OAAO;;;;;;;;;;AAWzC,SAAgB,kBAAkB,SAA6B;CAC7D,IAAI;EACF,OAAO,IAAI,WAAW,OAAO;CAC/B,SAAS,GAAG;EACV,MAAM,IAAI,MACR,6CAA6C,QAAQ,YAAY,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAC5G;CACF;AACF;AAEA,IAAa,iBAAiB,OAC5B,KACA,OACgC;CAChC,IAAI;EACF,MAAM,OAAO,MAAM,IAAI,MAAM,CAAC,CAAC,KAAK;EACpC,IAAI,KAAK,SAAS,KAChB,OAAO,KAAK,MAAM,GAAG,GAAG,IAAI;EAE9B,OAAO;CACT,SAAS,GAAG;EACV,GAAG,OACD,8BACA,EACE,KAAK,IAAI,IACX,GACA,CACF;CACF;AAGF;AAEA,IAAM,eAAe;AAYrB,eAAsB,mBACpB,WACA,QACA,IACA,KACA,OACA,MACA,aACA;CACA,MAAM,cACJ,YAAY,cAAc,aAAa,YAAY,gBAAgB,KAAA,IAC/D;EACE,WAAW,YAAY;EACvB,cAAc,YAAY;CAC5B,IACA,EAAC,WAAW,YAAY,UAAS;CACvC,MAAM,eAAe,YAAY,IAAI;CACrC,IAAI;CACJ,IAAI,eAAe;CACnB,YAAY,CAAC,CAAC,IAAI,GAAG,WAAW;CAEhC,IAAI;EACF,MAAM,uBAAuB,QAAQ,GAAG,OAAO,gBAAgB,CAAC,CAAC,SAC/D,EACF;EACA,KAAK,GACF,YAAY,wBAAwB,oBAAoB,CAAC,CACzD,YAAY,UAAU,MAAM;EAE/B,MAAM,cACJ,WAAW,SAAS,IAAI,gBAAgB,IAAI;EAC9C,MAAM,MAAM,KACV,YAAY,KACZ,oBAAoB,OAAO,gBAC7B;EACA,MAAM,gBAAgB,YAAY;EAElC,GAAG,QAAQ,6BAA6B,EACtC,IACF,CAAC;EAED,IAAI,CAAC,SAAS,KAAK,YAAY,sBAAsB,CAAC,CAAC,GAAG;GACxD,MAAM,YAAY,cAChB,QACAA,UACA,WAAW,SACP,QAAQ,IAAI,yDACZ,QAAQ,IAAI,qDAClB;GACA,qBAAqB,sBAAsB,aAAa;IACtD,QAAQ;IACR;IACA;GACF,CAAC;GACD,MAAM,IAAI,uBAAuB,WAAW,MAAM;EACpD;EACA,MAAM,UAAkC,EACtC,gBAAgB,mBAClB;EAEA,IAAI,cAAc,QAChB,QAAQ,eAAe,cAAc;EAEvC,OAAO,OACL,SACA,cAAc,eACd,cAAc,cAChB;EACA,IAAI,IAAI,MAAM,KACZ,QAAQ,mBAAmB,UAAU,IAAI,KAAK;EAEhD,IAAI,cAAc,QAChB,QAAQ,YAAY,cAAc;EAEpC,IAAI,cAAc,QAChB,QAAQ,YAAY,cAAc;EAEpC,YAAY,OAAO,QAAQ,OAAO,GAAG,OAAO;EAE5C,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,MAAM,SAAS,IAAI,gBAAgB,OAAO,MAAM;EAEhD,KAAK,MAAM,YAAY,gBACrB,IAAI,OAAO,IAAI,QAAQ,GAAG;GACxB,qBAAqB,sBAAsB,aAAa;IACtD,QAAQ;IACR;GACF,CAAC;GACD,MAAM,IAAI,MACR,yDAAyD,SAAS,EACpE;EACF;EAGF,OAAO,OAAO,UAAU,eAAe,KAAK,CAAC;EAC7C,OAAO,OAAO,SAAS,MAAM,KAAK;EAElC,OAAO,SAAS,OAAO,SAAS;EAEhC,MAAM,WAAW,OAAO,SAAS;EAEjC,KAAK,IAAI,UAAU,GAAG,WAAW,cAAc,WAAW;GACxD,eAAe;GACf,KAAK,GAAG,YAAY,6BAA6B,OAAO;GACxD,GAAG,QAAQ,+BAA+B;GAC1C,MAAM,mBAAmB,YAAY;IACnC,IAAI,UAAU,cAAc;KAC1B,MAAM,UAAU,kBAAkB,OAAO;KACzC,GAAG,QAAQ,qCAAqC,QAAQ,IAAI;KAC5D,MAAM,MAAM,OAAO;KACnB,OAAO;IACT;IACA,GAAG,QAAQ,0DAA0D;IACrE,OAAO;GACT;GACA,MAAM,eAAe,YAAY,IAAI;GACrC,IAAI;IACF,MAAM,WAAW,MAAM,MAAM,UAAU;KACrC,QAAQ;KACR;KACA,MAAM,KAAK,UAAU,IAAI;IAC3B,CAAC;IAED,IAAI,CAAC,SAAS,IAAI;KAChB,MAAM,cAAc,MAAM,eAAe,UAAU,EAAE;KACrD,GAAG,OAAO,gDAAgD;MACxD,KAAK;MACL,QAAQ,SAAS;MACjB;KACF,CAAC;KAED,MAAM,YACJ,SAAS,UAAU,OACnB,SAAS,SAAS,OAClB,UAAU;KACZ,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;MAC9D;MACA,QAAQ;MACR;MACA;KACF,CAAC;KACD,IAAI,aAAc,MAAM,iBAAiB,GACvC;KAGF,MAAM,YAAY,cAChB,QACAC,MACA,gDAAgD,SAAS,UACzD,UACA,WACF;KACA,qBAAqB,sBAAsB,aAAa;MACtD,QAAQ;MACR;MACA;MACA;KACF,CAAC;KACD,MAAM,IAAI,uBAAuB,WAAW,MAAM;IACpD;IAEA,IAAI;KACF,MAAM,OAAO,MAAM,SAAS,KAAK;KACjC,MAAM,SAAS,UAAU,MAAM,MAAM,EACnC,MAAM,cACR,CAAC;KACD,MAAM,WAAW,mBAAmB,MAAM;KAC1C,IAAI,UAAU;MACZ,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;OAC9D;OACA,QAAQ;OACR,WAAW;OACX;OACA,WAAW;MACb,CAAC;MACD,qBAAqB,sBAAsB,aAAa;OACtD,QAAQ;OACR;OACA;OACA,WAAW;MACb,CAAC;KACH,OAAO;MACL,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;OAC9D;OACA,QAAQ;OACR,WAAW;OACX;MACF,CAAC;MACD,qBAAqB,sBAAsB,aAAa;OACtD,QAAQ;OACR;OACA;MACF,CAAC;KACH;KACA,GAAG,QAAQ,iCAAiC;KAC5C,OAAO;IACT,SAAS,OAAO;KACd,GAAG,OACD,4BACA,EACE,KAAK,SACP,GACA,KACF;KAEA,MAAM,YAAY,cAChB,QACAC,OACA,6CAA6C,gBAAgB,KAAK,GACpE;KACA,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;MAC9D;MACA,QAAQ;MACR,WAAW;MACX;MACA;KACF,CAAC;KACD,qBAAqB,sBAAsB,aAAa;MACtD,QAAQ;MACR;MACA;MACA;KACF,CAAC;KACD,MAAM,IAAI,uBAAuB,WAAW,QAAQ,EAAC,OAAO,MAAK,CAAC;IACpE;GACF,SAAS,OAAO;IACd,IAAI,gBAAgB,KAAK,GACvB,MAAM;IAGR,MAAM,gBACJ,iBAAiB,aAAa,MAAM,YAAY;IAGlD,IAAI,WAAqB,gBAAgB,SAAS;IAClD,GAAG,SAAS,GACV,qCACA,EAAC,KAAK,SAAQ,GACd,KACF;IAEA,MAAM,YAAY,iBAAiB,UAAU;IAC7C,iBAAiB,YAAY,IAAI,IAAI,cAAc,aAAa;KAC9D;KACA,QAAQ;KACR;IACF,CAAC;IAED,IAAI,aAAc,MAAM,iBAAiB,GACvC;IAGF,MAAM,YAAY,cAChB,QACAF,UACA,sCAAsC,gBAAgB,KAAK,GAC7D;IACA,qBAAqB,sBAAsB,aAAa;KACtD,QAAQ;KACR;KACA;IACF,CAAC;IACD,MAAM,IAAI,uBAAuB,WAAW,UAAU,EAAC,OAAO,MAAK,CAAC;GACtE;EACF;EACA,YAAY;CACd,UAAU;EACR,MAAM,QACJ,sBACA,sBAAsB,aAAa;GACjC,QAAQ,iBAAiB,IAAI,iBAAiB;GAC9C;EACF,CAAC;EACH,YAAY,CAAC,CAAC,IAAI,GAAG,KAAK;EAC1B,mBAAmB,CAAC,CAAC,SAAS,YAAY,IAAI,IAAI,cAAc,KAAK;EACrE,YAAY,CAAC,CAAC,IAAI,IAAI,WAAW;CACnC;AACF;;;;;;;;;;;;;;AAeA,SAAgB,SACd,KACA,oBACS;CACT,KAAK,MAAM,WAAW,oBACpB,IAAI,QAAQ,KAAK,GAAG,GAClB,OAAO;CAGX,OAAO;AACT;;;;;;;AAQA,SAAS,kBAAkB,SAAyB;CAClD,OAAO,KAAK,IAAI,KAAM,MAAM,KAAK,IAAI,GAAG,UAAU,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG;AAC5E;AAEA,SAAS,cACP,QACA,QACA,SACA,UACA,aACW;CACX,IAAI,WAAW,QACb,OAAO,WAAW,SACd;EACE,MAAMG;EACN,QAAQC;EACR;EACA,QAAQ,UAAU,UAAU;EAC5B,GAAI,gBAAgB,KAAA,IAAY,EAAC,YAAW,IAAI,CAAC;EACjD;EACA,aAAa,CAAC;CAChB,IACA;EACE,MAAMD;EACN,QAAQC;EACR;EACA;EACA,aAAa,CAAC;CAChB;CAGN,OAAO,WAAW,SACd;EACE,MAAMC;EACN,QAAQD;EACR;EACA,QAAQ,UAAU,UAAU;EAC5B,GAAI,gBAAgB,KAAA,IAAY,EAAC,YAAW,IAAI,CAAC;EACjD;EACA,UAAU,CAAC;CACb,IACA;EACE,MAAMC;EACN,QAAQD;EACR;EACA;EACA,UAAU,CAAC;CACb;AACN;AAOA,SAAS,mBAAmB,QAAiD;CAC3E,MAAM,SAAS,gBAAgB,IAAI,QAAQ,EAAC,MAAM,cAAa,CAAC;CAChE,IAAI,OAAO,IACT,OAAO,OAAO;CAGhB,IAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,mBAAmB;EAC5D,MAAM,wBAAwB,gBAAgB,IAAI,OAAO,IAAI,EAC3D,MAAM,cACR,CAAC;EACD,OAAO,sBAAsB,KAAK,sBAAsB,QAAQ,KAAA;CAClE;CAEA,MAAM,kBAAkB,gBAAgB,IAAI,QAAQ,EAAC,MAAM,cAAa,CAAC;CACzE,OAAO,gBAAgB,KACnB;EACE,MAAMD;EACN,QAAQ,sBAAsB,gBAAgB,MAAM,KAAK;CAC3D,IACA,KAAA;AACN;AAEA,SAAS,sBAAsB,OAAmC;CAChE,QAAQ,OAAR;EACE,KAAK,QACH,OAAOF;EACT,KAAK,0BACH,OAAOK;EACT,KAAK;EACL,KAAK,cACH,OAAON;CACX;AACF;AAsBA,SAAS,sBACP,WACA,EAAC,QAAQ,cAAc,UAAU,aACV;CACvB,OAAO;EACL,GAAG;EACH;EACA,eAAe;EACf,GAAG,4BAA4B,UAAU,SAAS;CACpD;AACF;AAEA,SAAS,4BACP,UACA,WAC6B;CAC7B,MAAM,QAAqC,CAAC;CAE5C,IAAI,UAAU;EACZ,MAAM,mBAAmB,SAAS;EAClC,MAAM,oBAAoB,GAAG,KAAK,MAAM,SAAS,SAAS,GAAG,EAAE;CACjE;CAEA,IAAI,WAAW;EACb,MAAM,aAAa,UAAU;EAC7B,IAAI,UAAU,WAAW,KAAA,GACvB,MAAM,eAAe,UAAU;CAEnC;CAEA,OAAO;AACT;AAEA,SAAS,iBACP,YACA,WACA,EACE,SACA,QACA,WACA,UACA,aAEF;CACA,MAAM,QAA+B;EACnC,GAAG;EACH;EACA;EACA,YAAY;EACZ,GAAG,4BAA4B,UAAU,SAAS;CACpD;CACA,YAAY,CAAC,CAAC,IAAI,GAAG,KAAK;CAC1B,mBAAmB,CAAC,CAAC,SAAS,YAAY,KAAK;AACjD"}
@@ -1 +1 @@
1
- {"version":3,"file":"transform-query.js","names":["#shard","#cache","#lc","#requestTransform"],"sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"sourcesContent":["import {trace} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport {startAsyncSpan} from '../../../otel/src/span.ts';\nimport {TimedCache} from '../../../shared/src/cache.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport {sortedEntries} from '../../../shared/src/sorted-entries.ts';\nimport {\n type ErroredQuery,\n type TransformRequestBody,\n type TransformRequestMessage,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {\n isProtocolError,\n type TransformFailedBody,\n} from '../../../zero-protocol/src/error.ts';\nimport {hashOfAST} from '../../../zero-protocol/src/query-hash.ts';\nimport {\n queryResponseSchema,\n type QueryResponseBody,\n} from '../../../zero-protocol/src/query-server.ts';\nimport type {TransformedAndHashed} from '../auth/read-authorizer.ts';\nimport {fetchFromAPIServer} from '../custom/fetch.ts';\nimport type {\n ConnectionContext,\n ConnectionValidation,\n} from '../services/view-syncer/connection-context-manager.ts';\nimport type {CustomQueryRecord} from '../services/view-syncer/schema/types.ts';\nimport type {ShardID} from '../types/shards.ts';\n\nconst tracer = trace.getTracer('custom-query-transformer');\n\nexport type TransformResponse =\n | {\n kind: 'QueryResponse';\n queries: QueryResponseBody;\n validation: ConnectionValidation;\n }\n | TransformFailedBody;\n\nexport type HashedTransformResponse =\n | {\n kind: 'failed';\n result: TransformFailedBody;\n }\n | ({\n kind: 'success';\n result: (TransformedAndHashed | ErroredQuery)[];\n } & (\n | {\n cached: true;\n }\n | {\n cached: false;\n validation: ConnectionValidation;\n }\n ));\n\n/**\n * Transforms a custom query by calling the user's API server.\n * Caches the transformed queries for 5 seconds to avoid unnecessary API calls.\n *\n * Error responses are not cached as the user may want to retry the query\n * and the error may be transient.\n *\n * The TTL was chosen to be 5 seconds since custom query requests come with\n * a token which itself may have a short TTL (e.g., 10 seconds).\n *\n * Token expiration isn't expected to be exact so this 5 second\n * caching shouldn't cause unexpected behavior. E.g., many JWT libraries\n * implement leeway for expiration checks: https://github.com/panva/jose/blob/main/docs/jwt/verify/interfaces/JWTVerifyOptions.md#clocktolerance\n *\n * The ViewSyncer will call the API server 3-4 times with the exact same queries\n * if we do not cache requests.\n *\n * Caching is safe here because the cache key encodes the effective request\n * identity used for `/query`: auth, userID, forwarded cookies, origin,\n * custom headers, target URL, and the query hash itself.\n */\nexport class CustomQueryTransformer {\n readonly #shard: ShardID;\n readonly #cache: TimedCache<TransformedAndHashed>;\n readonly #lc: LogContext;\n\n constructor(lc: LogContext, shard: ShardID) {\n this.#shard = shard;\n this.#lc = lc;\n this.#cache = new TimedCache(5000); // 5 seconds cache TTL\n }\n\n /**\n * Stops the cache's periodic cleanup so the transformer can be garbage\n * collected. Idempotent, and the transformer remains safe to use after\n * destroy (it just no longer caches).\n */\n destroy(): void {\n this.#cache.destroy();\n }\n\n /**\n * Forces the empty `/query` validation request used by auth maintenance.\n *\n * This stays separate from `transform()` because `transform([], ...)`\n * short-circuits locally and never hits the API server, while validation\n * still needs to make the request so auth failures are surfaced.\n * Successful validation is intentionally opaque because callers only care\n * whether the request succeeded or failed.\n */\n async validate(ctx: ConnectionContext): Promise<TransformResponse> {\n const response = await this.#requestTransform(ctx, [], 'validate');\n\n return response;\n }\n\n async transform(\n ctx: ConnectionContext,\n queries: Iterable<CustomQueryRecord>,\n ): Promise<HashedTransformResponse> {\n const request: TransformRequestBody = [];\n const cachedResponses: TransformedAndHashed[] = [];\n\n // split queries into cached and uncached\n for (const query of queries) {\n const cacheKey = getCacheKey(ctx, query.id);\n const cached = this.#cache.get(cacheKey);\n if (cached) {\n cachedResponses.push(cached);\n } else {\n request.push({\n id: query.id,\n name: query.name,\n args: query.args,\n });\n }\n }\n\n let cached = true;\n\n if (request.length === 0) {\n return {\n kind: 'success',\n result: cachedResponses,\n cached: true,\n };\n } else {\n // we are hitting the server with at least one uncached query\n cached = false;\n }\n\n const response = await this.#requestTransform(ctx, request, 'transform');\n if (response.kind === 'TransformFailed') {\n return {\n kind: 'failed',\n result: response,\n };\n }\n\n const newResponses = response.queries.map(transformed => {\n if ('error' in transformed) {\n return transformed;\n }\n return {\n id: transformed.id,\n transformedAst: transformed.ast,\n transformationHash: hashOfAST(transformed.ast),\n } satisfies TransformedAndHashed;\n });\n\n for (const transformed of newResponses) {\n if ('error' in transformed) {\n // do not cache error responses\n continue;\n }\n const cacheKey = getCacheKey(ctx, transformed.id);\n this.#cache.set(cacheKey, transformed);\n }\n\n return {\n kind: 'success',\n result: [...newResponses, ...cachedResponses],\n validation: response.validation,\n cached,\n };\n }\n\n async #requestTransform(\n ctx: ConnectionContext,\n request: TransformRequestBody,\n operation: 'validate' | 'transform',\n ): Promise<TransformResponse> {\n const queryIDs = request.map(({id}) => id);\n\n try {\n const transformResponse = await startAsyncSpan(\n tracer,\n 'customQueryTransformer.fetchFromAPIServer',\n () =>\n fetchFromAPIServer(\n queryResponseSchema,\n 'transform',\n this.#lc,\n ctx,\n this.#shard,\n ['transform', request] satisfies TransformRequestMessage,\n {operation: operation === 'validate' ? 'validate_auth' : 'query'},\n ),\n );\n\n if ('kind' in transformResponse) {\n return transformResponse.kind === 'QueryResponse'\n ? {\n kind: 'QueryResponse',\n queries: transformResponse.queries,\n validation:\n transformResponse.userID !== undefined\n ? {\n kind: 'server-validated',\n validatedUserID: transformResponse.userID,\n }\n : {kind: 'client-fallback'},\n }\n : transformResponse;\n }\n\n if (transformResponse[0] === 'transformed') {\n // Legacy query response, so we use client fallback\n return {\n kind: 'QueryResponse',\n validation: {kind: 'client-fallback'},\n queries: transformResponse[1],\n };\n }\n\n return transformResponse[1];\n } catch (e) {\n if (\n isProtocolError(e) &&\n e.errorBody.kind === ErrorKind.TransformFailed\n ) {\n return {\n ...e.errorBody,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Failed to ${operation} queries: ${getErrorMessage(e)}`,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n }\n}\n\nfunction getCacheKey(ctx: ConnectionContext, queryID: string) {\n // For custom queries, queryID is a hash of the name + args.\n // The apiKey is static for a given transformer instance.\n return JSON.stringify({\n queryID,\n token: ctx.auth?.raw,\n cookie: ctx.queryContext.headerOptions.cookie,\n origin: ctx.queryContext.headerOptions.origin,\n userID: ctx.user,\n url: ctx.queryContext.url,\n customHeaders: normalizedHeaders(\n ctx.queryContext.headerOptions.customHeaders,\n ),\n requestHeaders: normalizedHeaders(\n ctx.queryContext.headerOptions.requestHeaders,\n ),\n });\n}\n\nfunction normalizedHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n) {\n if (!headers) {\n return undefined;\n }\n\n const forwardedHeaders = sortedEntries(headers);\n\n return forwardedHeaders.length === 0\n ? undefined\n : JSON.stringify(forwardedHeaders);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transform-query.js","names":["ErrorKind.TransformFailed","ErrorOrigin.ZeroCache","ErrorReason.Internal"],"sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"sourcesContent":["import {trace} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport {startAsyncSpan} from '../../../otel/src/span.ts';\nimport {TimedCache} from '../../../shared/src/cache.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport {sortedEntries} from '../../../shared/src/sorted-entries.ts';\nimport {\n type ErroredQuery,\n type TransformRequestBody,\n type TransformRequestMessage,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {\n isProtocolError,\n type TransformFailedBody,\n} from '../../../zero-protocol/src/error.ts';\nimport {hashOfAST} from '../../../zero-protocol/src/query-hash.ts';\nimport {\n queryResponseSchema,\n type QueryResponseBody,\n} from '../../../zero-protocol/src/query-server.ts';\nimport type {TransformedAndHashed} from '../auth/read-authorizer.ts';\nimport {fetchFromAPIServer} from '../custom/fetch.ts';\nimport type {\n ConnectionContext,\n ConnectionValidation,\n} from '../services/view-syncer/connection-context-manager.ts';\nimport type {CustomQueryRecord} from '../services/view-syncer/schema/types.ts';\nimport type {ShardID} from '../types/shards.ts';\n\nconst tracer = trace.getTracer('custom-query-transformer');\n\nexport type TransformResponse =\n | {\n kind: 'QueryResponse';\n queries: QueryResponseBody;\n validation: ConnectionValidation;\n }\n | TransformFailedBody;\n\nexport type HashedTransformResponse =\n | {\n kind: 'failed';\n result: TransformFailedBody;\n }\n | ({\n kind: 'success';\n result: (TransformedAndHashed | ErroredQuery)[];\n } & (\n | {\n cached: true;\n }\n | {\n cached: false;\n validation: ConnectionValidation;\n }\n ));\n\n/**\n * Transforms a custom query by calling the user's API server.\n * Caches the transformed queries for 5 seconds to avoid unnecessary API calls.\n *\n * Error responses are not cached as the user may want to retry the query\n * and the error may be transient.\n *\n * The TTL was chosen to be 5 seconds since custom query requests come with\n * a token which itself may have a short TTL (e.g., 10 seconds).\n *\n * Token expiration isn't expected to be exact so this 5 second\n * caching shouldn't cause unexpected behavior. E.g., many JWT libraries\n * implement leeway for expiration checks: https://github.com/panva/jose/blob/main/docs/jwt/verify/interfaces/JWTVerifyOptions.md#clocktolerance\n *\n * The ViewSyncer will call the API server 3-4 times with the exact same queries\n * if we do not cache requests.\n *\n * Caching is safe here because the cache key encodes the effective request\n * identity used for `/query`: auth, userID, forwarded cookies, origin,\n * custom headers, target URL, and the query hash itself.\n */\nexport class CustomQueryTransformer {\n readonly #shard: ShardID;\n readonly #cache: TimedCache<TransformedAndHashed>;\n readonly #lc: LogContext;\n\n constructor(lc: LogContext, shard: ShardID) {\n this.#shard = shard;\n this.#lc = lc;\n this.#cache = new TimedCache(5000); // 5 seconds cache TTL\n }\n\n /**\n * Stops the cache's periodic cleanup so the transformer can be garbage\n * collected. Idempotent, and the transformer remains safe to use after\n * destroy (it just no longer caches).\n */\n destroy(): void {\n this.#cache.destroy();\n }\n\n /**\n * Forces the empty `/query` validation request used by auth maintenance.\n *\n * This stays separate from `transform()` because `transform([], ...)`\n * short-circuits locally and never hits the API server, while validation\n * still needs to make the request so auth failures are surfaced.\n * Successful validation is intentionally opaque because callers only care\n * whether the request succeeded or failed.\n */\n async validate(ctx: ConnectionContext): Promise<TransformResponse> {\n const response = await this.#requestTransform(ctx, [], 'validate');\n\n return response;\n }\n\n async transform(\n ctx: ConnectionContext,\n queries: Iterable<CustomQueryRecord>,\n ): Promise<HashedTransformResponse> {\n const request: TransformRequestBody = [];\n const cachedResponses: TransformedAndHashed[] = [];\n\n // split queries into cached and uncached\n for (const query of queries) {\n const cacheKey = getCacheKey(ctx, query.id);\n const cached = this.#cache.get(cacheKey);\n if (cached) {\n cachedResponses.push(cached);\n } else {\n request.push({\n id: query.id,\n name: query.name,\n args: query.args,\n });\n }\n }\n\n let cached = true;\n\n if (request.length === 0) {\n return {\n kind: 'success',\n result: cachedResponses,\n cached: true,\n };\n } else {\n // we are hitting the server with at least one uncached query\n cached = false;\n }\n\n const response = await this.#requestTransform(ctx, request, 'transform');\n if (response.kind === 'TransformFailed') {\n return {\n kind: 'failed',\n result: response,\n };\n }\n\n const newResponses = response.queries.map(transformed => {\n if ('error' in transformed) {\n return transformed;\n }\n return {\n id: transformed.id,\n transformedAst: transformed.ast,\n transformationHash: hashOfAST(transformed.ast),\n } satisfies TransformedAndHashed;\n });\n\n for (const transformed of newResponses) {\n if ('error' in transformed) {\n // do not cache error responses\n continue;\n }\n const cacheKey = getCacheKey(ctx, transformed.id);\n this.#cache.set(cacheKey, transformed);\n }\n\n return {\n kind: 'success',\n result: [...newResponses, ...cachedResponses],\n validation: response.validation,\n cached,\n };\n }\n\n async #requestTransform(\n ctx: ConnectionContext,\n request: TransformRequestBody,\n operation: 'validate' | 'transform',\n ): Promise<TransformResponse> {\n const queryIDs = request.map(({id}) => id);\n\n try {\n const transformResponse = await startAsyncSpan(\n tracer,\n 'customQueryTransformer.fetchFromAPIServer',\n () =>\n fetchFromAPIServer(\n queryResponseSchema,\n 'transform',\n this.#lc,\n ctx,\n this.#shard,\n ['transform', request] satisfies TransformRequestMessage,\n {operation: operation === 'validate' ? 'validate_auth' : 'query'},\n ),\n );\n\n if ('kind' in transformResponse) {\n return transformResponse.kind === 'QueryResponse'\n ? {\n kind: 'QueryResponse',\n queries: transformResponse.queries,\n validation:\n transformResponse.userID !== undefined\n ? {\n kind: 'server-validated',\n validatedUserID: transformResponse.userID,\n }\n : {kind: 'client-fallback'},\n }\n : transformResponse;\n }\n\n if (transformResponse[0] === 'transformed') {\n // Legacy query response, so we use client fallback\n return {\n kind: 'QueryResponse',\n validation: {kind: 'client-fallback'},\n queries: transformResponse[1],\n };\n }\n\n return transformResponse[1];\n } catch (e) {\n if (\n isProtocolError(e) &&\n e.errorBody.kind === ErrorKind.TransformFailed\n ) {\n return {\n ...e.errorBody,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Failed to ${operation} queries: ${getErrorMessage(e)}`,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n }\n}\n\nfunction getCacheKey(ctx: ConnectionContext, queryID: string) {\n // For custom queries, queryID is a hash of the name + args.\n // The apiKey is static for a given transformer instance.\n return JSON.stringify({\n queryID,\n token: ctx.auth?.raw,\n cookie: ctx.queryContext.headerOptions.cookie,\n origin: ctx.queryContext.headerOptions.origin,\n userID: ctx.user,\n url: ctx.queryContext.url,\n customHeaders: normalizedHeaders(\n ctx.queryContext.headerOptions.customHeaders,\n ),\n requestHeaders: normalizedHeaders(\n ctx.queryContext.headerOptions.requestHeaders,\n ),\n });\n}\n\nfunction normalizedHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n) {\n if (!headers) {\n return undefined;\n }\n\n const forwardedHeaders = sortedEntries(headers);\n\n return forwardedHeaders.length === 0\n ? undefined\n : JSON.stringify(forwardedHeaders);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"create.js","names":[],"sources":["../../../../../zero-cache/src/db/create.ts"],"sourcesContent":["import {assertValidLiteColumnSpec} from '../types/lite.ts';\nimport {id, idList} from '../types/sql.ts';\nimport {liteIndexPredicateSQL} from './index-predicate.ts';\nimport type {ColumnSpec, LiteIndexSpec, LiteTableSpec} from './specs.ts';\n\n/**\n * `spec` always comes from `mapPostgresToLiteColumn` so we only need to support\n * the output of that function.\n */\nexport function liteColumnDef(spec: ColumnSpec) {\n assertValidLiteColumnSpec(spec);\n\n let def = id(spec.dataType);\n\n if (spec.characterMaximumLength) {\n def += `(${spec.characterMaximumLength})`;\n }\n if (spec.notNull) {\n def += ' NOT NULL';\n }\n if (spec.dflt) {\n def += ` DEFAULT ${spec.dflt}`;\n }\n return def;\n}\n\n/**\n * Constructs a `CREATE TABLE` statement for a {@link TableSpec}.\n */\nexport function createLiteTableStatement(spec: LiteTableSpec): string {\n const defs = Object.entries(spec.columns)\n .sort(([_a, {pos: a}], [_b, {pos: b}]) => a - b)\n .map(([name, columnSpec]) => `${id(name)} ${liteColumnDef(columnSpec)}`);\n if (spec.primaryKey) {\n defs.push(`PRIMARY KEY (${idList(spec.primaryKey)})`);\n }\n\n const createStmt = `CREATE TABLE ${id(spec.name)} (`;\n return [createStmt, defs.join(',\\n'), ');'].join('\\n');\n}\n\nexport function createLiteIndexStatement(index: LiteIndexSpec): string {\n const columns = Object.entries(index.columns)\n .map(([name, dir]) => `${id(name)} ${dir}`)\n .join(',');\n // Partial indexes are never unique on the replica. Their uniqueness is\n // not needed (a partial index cannot serve as a row key), and a UNIQUE\n // partial index would turn any divergence between PostgreSQL's and\n // SQLite's evaluation of the predicate into a replication failure.\n const unique = index.unique && !index.predicate ? 'UNIQUE' : '';\n const predicate = index.predicate\n ? ` WHERE ${liteIndexPredicateSQL(index.predicate)}`\n : '';\n return `CREATE ${unique} INDEX ${id(index.name)} ON ${id(\n index.tableName,\n )} (${columns})${predicate};`;\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,cAAc,MAAkB;CAC9C,0BAA0B,IAAI;CAE9B,IAAI,MAAM,GAAG,KAAK,QAAQ;CAE1B,IAAI,KAAK,wBACP,OAAO,IAAI,KAAK,uBAAuB;CAEzC,IAAI,KAAK,SACP,OAAO;CAET,IAAI,KAAK,MACP,OAAO,YAAY,KAAK;CAE1B,OAAO;AACT;;;;AAKA,SAAgB,yBAAyB,MAA6B;CACpE,MAAM,OAAO,OAAO,QAAQ,KAAK,OAAO,EACrC,MAAM,CAAC,IAAI,EAAC,KAAK,MAAK,CAAC,IAAI,EAAC,KAAK,SAAQ,IAAI,CAAC,EAC9C,KAAK,CAAC,MAAM,gBAAgB,GAAG,GAAG,IAAI,EAAE,GAAG,cAAc,UAAU,GAAG;CACzE,IAAI,KAAK,YACP,KAAK,KAAK,gBAAgB,OAAO,KAAK,UAAU,EAAE,EAAE;CAItD,OAAO;EAAC,gBAD2B,GAAG,KAAK,IAAI,EAAE;EAC7B,KAAK,KAAK,KAAK;EAAG;CAAI,EAAE,KAAK,IAAI;AACvD;AAEA,SAAgB,yBAAyB,OAA8B;CACrE,MAAM,UAAU,OAAO,QAAQ,MAAM,OAAO,EACzC,KAAK,CAAC,MAAM,SAAS,GAAG,GAAG,IAAI,EAAE,GAAG,KAAK,EACzC,KAAK,GAAG;CAKX,MAAM,SAAS,MAAM,UAAU,CAAC,MAAM,YAAY,WAAW;CAC7D,MAAM,YAAY,MAAM,YACpB,UAAU,sBAAsB,MAAM,SAAS,MAC/C;CACJ,OAAO,UAAU,OAAO,SAAS,GAAG,MAAM,IAAI,EAAE,MAAM,GACpD,MAAM,SACR,EAAE,IAAI,QAAQ,GAAG,UAAU;AAC7B"}
1
+ {"version":3,"file":"create.js","names":[],"sources":["../../../../../zero-cache/src/db/create.ts"],"sourcesContent":["import {assertValidLiteColumnSpec} from '../types/lite.ts';\nimport {id, idList} from '../types/sql.ts';\nimport {liteIndexPredicateSQL} from './index-predicate.ts';\nimport type {ColumnSpec, LiteIndexSpec, LiteTableSpec} from './specs.ts';\n\n/**\n * `spec` always comes from `mapPostgresToLiteColumn` so we only need to support\n * the output of that function.\n */\nexport function liteColumnDef(spec: ColumnSpec) {\n assertValidLiteColumnSpec(spec);\n\n let def = id(spec.dataType);\n\n if (spec.characterMaximumLength) {\n def += `(${spec.characterMaximumLength})`;\n }\n if (spec.notNull) {\n def += ' NOT NULL';\n }\n if (spec.dflt) {\n def += ` DEFAULT ${spec.dflt}`;\n }\n return def;\n}\n\n/**\n * Constructs a `CREATE TABLE` statement for a {@link TableSpec}.\n */\nexport function createLiteTableStatement(spec: LiteTableSpec): string {\n const defs = Object.entries(spec.columns)\n .sort(([_a, {pos: a}], [_b, {pos: b}]) => a - b)\n .map(([name, columnSpec]) => `${id(name)} ${liteColumnDef(columnSpec)}`);\n if (spec.primaryKey) {\n defs.push(`PRIMARY KEY (${idList(spec.primaryKey)})`);\n }\n\n const createStmt = `CREATE TABLE ${id(spec.name)} (`;\n return [createStmt, defs.join(',\\n'), ');'].join('\\n');\n}\n\nexport function createLiteIndexStatement(index: LiteIndexSpec): string {\n const columns = Object.entries(index.columns)\n .map(([name, dir]) => `${id(name)} ${dir}`)\n .join(',');\n // Partial indexes are never unique on the replica. Their uniqueness is\n // not needed (a partial index cannot serve as a row key), and a UNIQUE\n // partial index would turn any divergence between PostgreSQL's and\n // SQLite's evaluation of the predicate into a replication failure.\n const unique = index.unique && !index.predicate ? 'UNIQUE' : '';\n const predicate = index.predicate\n ? ` WHERE ${liteIndexPredicateSQL(index.predicate)}`\n : '';\n return `CREATE ${unique} INDEX ${id(index.name)} ON ${id(\n index.tableName,\n )} (${columns})${predicate};`;\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,cAAc,MAAkB;CAC9C,0BAA0B,IAAI;CAE9B,IAAI,MAAM,GAAG,KAAK,QAAQ;CAE1B,IAAI,KAAK,wBACP,OAAO,IAAI,KAAK,uBAAuB;CAEzC,IAAI,KAAK,SACP,OAAO;CAET,IAAI,KAAK,MACP,OAAO,YAAY,KAAK;CAE1B,OAAO;AACT;;;;AAKA,SAAgB,yBAAyB,MAA6B;CACpE,MAAM,OAAO,OAAO,QAAQ,KAAK,OAAO,CAAC,CACtC,MAAM,CAAC,IAAI,EAAC,KAAK,MAAK,CAAC,IAAI,EAAC,KAAK,SAAQ,IAAI,CAAC,CAAC,CAC/C,KAAK,CAAC,MAAM,gBAAgB,GAAG,GAAG,IAAI,EAAE,GAAG,cAAc,UAAU,GAAG;CACzE,IAAI,KAAK,YACP,KAAK,KAAK,gBAAgB,OAAO,KAAK,UAAU,EAAE,EAAE;CAItD,OAAO;EAAC,gBAD2B,GAAG,KAAK,IAAI,EAAE;EAC7B,KAAK,KAAK,KAAK;EAAG;CAAI,CAAC,CAAC,KAAK,IAAI;AACvD;AAEA,SAAgB,yBAAyB,OAA8B;CACrE,MAAM,UAAU,OAAO,QAAQ,MAAM,OAAO,CAAC,CAC1C,KAAK,CAAC,MAAM,SAAS,GAAG,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC,CAC1C,KAAK,GAAG;CAKX,MAAM,SAAS,MAAM,UAAU,CAAC,MAAM,YAAY,WAAW;CAC7D,MAAM,YAAY,MAAM,YACpB,UAAU,sBAAsB,MAAM,SAAS,MAC/C;CACJ,OAAO,UAAU,OAAO,SAAS,GAAG,MAAM,IAAI,EAAE,MAAM,GACpD,MAAM,SACR,EAAE,IAAI,QAAQ,GAAG,UAAU;AAC7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index-predicate.js","names":[],"sources":["../../../../../zero-cache/src/db/index-predicate.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {id, lit} from '../types/sql.ts';\nimport type {IndexPredicate, IndexPredicateValue} from './specs.ts';\n\nconst CANONICAL_INTEGER_RE = /^-?(?:0|[1-9]\\d*)$/;\n\nexport function liteIndexPredicateSQL(predicate: IndexPredicate): string {\n switch (predicate.type) {\n case 'and':\n case 'or':\n assert(predicate.conditions.length > 0, 'empty index predicate');\n return `(${predicate.conditions\n .map(liteIndexPredicateSQL)\n .join(` ${predicate.type.toUpperCase()} `)})`;\n case 'not':\n return `(NOT ${liteIndexPredicateSQL(predicate.condition)})`;\n case 'null-test':\n return `${id(predicate.column)} ${predicate.op}`;\n case 'comparison':\n return `${id(predicate.column)} ${predicate.op} ${predicateValueSQL(\n predicate.value,\n )}`;\n }\n}\n\nfunction predicateValueSQL(value: IndexPredicateValue): string {\n switch (value.type) {\n case 'boolean':\n return value.value ? '1' : '0';\n case 'integer':\n // The PostgreSQL translator only constructs canonical decimal integers.\n assert(\n CANONICAL_INTEGER_RE.test(value.value),\n 'invalid integer predicate',\n );\n return value.value;\n case 'string':\n return lit(value.value);\n }\n}\n\nexport function mapIndexPredicateColumns(\n predicate: IndexPredicate,\n map: (column: string) => string,\n): IndexPredicate {\n switch (predicate.type) {\n case 'and':\n case 'or':\n return {\n ...predicate,\n conditions: predicate.conditions.map(condition =>\n mapIndexPredicateColumns(condition, map),\n ),\n };\n case 'not':\n return {\n ...predicate,\n condition: mapIndexPredicateColumns(predicate.condition, map),\n };\n case 'null-test':\n case 'comparison':\n return {...predicate, column: map(predicate.column)};\n }\n}\n"],"mappings":";;;AAIA,IAAM,uBAAuB;AAE7B,SAAgB,sBAAsB,WAAmC;CACvE,QAAQ,UAAU,MAAlB;EACE,KAAK;EACL,KAAK;GACH,OAAO,UAAU,WAAW,SAAS,GAAG,uBAAuB;GAC/D,OAAO,IAAI,UAAU,WAClB,IAAI,qBAAqB,EACzB,KAAK,IAAI,UAAU,KAAK,YAAY,EAAE,EAAE,EAAE;EAC/C,KAAK,OACH,OAAO,QAAQ,sBAAsB,UAAU,SAAS,EAAE;EAC5D,KAAK,aACH,OAAO,GAAG,GAAG,UAAU,MAAM,EAAE,GAAG,UAAU;EAC9C,KAAK,cACH,OAAO,GAAG,GAAG,UAAU,MAAM,EAAE,GAAG,UAAU,GAAG,GAAG,kBAChD,UAAU,KACZ;CACJ;AACF;AAEA,SAAS,kBAAkB,OAAoC;CAC7D,QAAQ,MAAM,MAAd;EACE,KAAK,WACH,OAAO,MAAM,QAAQ,MAAM;EAC7B,KAAK;GAEH,OACE,qBAAqB,KAAK,MAAM,KAAK,GACrC,2BACF;GACA,OAAO,MAAM;EACf,KAAK,UACH,OAAO,IAAI,MAAM,KAAK;CAC1B;AACF;AAEA,SAAgB,yBACd,WACA,KACgB;CAChB,QAAQ,UAAU,MAAlB;EACE,KAAK;EACL,KAAK,MACH,OAAO;GACL,GAAG;GACH,YAAY,UAAU,WAAW,KAAI,cACnC,yBAAyB,WAAW,GAAG,CACzC;EACF;EACF,KAAK,OACH,OAAO;GACL,GAAG;GACH,WAAW,yBAAyB,UAAU,WAAW,GAAG;EAC9D;EACF,KAAK;EACL,KAAK,cACH,OAAO;GAAC,GAAG;GAAW,QAAQ,IAAI,UAAU,MAAM;EAAC;CACvD;AACF"}
1
+ {"version":3,"file":"index-predicate.js","names":[],"sources":["../../../../../zero-cache/src/db/index-predicate.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {id, lit} from '../types/sql.ts';\nimport type {IndexPredicate, IndexPredicateValue} from './specs.ts';\n\nconst CANONICAL_INTEGER_RE = /^-?(?:0|[1-9]\\d*)$/;\n\nexport function liteIndexPredicateSQL(predicate: IndexPredicate): string {\n switch (predicate.type) {\n case 'and':\n case 'or':\n assert(predicate.conditions.length > 0, 'empty index predicate');\n return `(${predicate.conditions\n .map(liteIndexPredicateSQL)\n .join(` ${predicate.type.toUpperCase()} `)})`;\n case 'not':\n return `(NOT ${liteIndexPredicateSQL(predicate.condition)})`;\n case 'null-test':\n return `${id(predicate.column)} ${predicate.op}`;\n case 'comparison':\n return `${id(predicate.column)} ${predicate.op} ${predicateValueSQL(\n predicate.value,\n )}`;\n }\n}\n\nfunction predicateValueSQL(value: IndexPredicateValue): string {\n switch (value.type) {\n case 'boolean':\n return value.value ? '1' : '0';\n case 'integer':\n // The PostgreSQL translator only constructs canonical decimal integers.\n assert(\n CANONICAL_INTEGER_RE.test(value.value),\n 'invalid integer predicate',\n );\n return value.value;\n case 'string':\n return lit(value.value);\n }\n}\n\nexport function mapIndexPredicateColumns(\n predicate: IndexPredicate,\n map: (column: string) => string,\n): IndexPredicate {\n switch (predicate.type) {\n case 'and':\n case 'or':\n return {\n ...predicate,\n conditions: predicate.conditions.map(condition =>\n mapIndexPredicateColumns(condition, map),\n ),\n };\n case 'not':\n return {\n ...predicate,\n condition: mapIndexPredicateColumns(predicate.condition, map),\n };\n case 'null-test':\n case 'comparison':\n return {...predicate, column: map(predicate.column)};\n }\n}\n"],"mappings":";;;AAIA,IAAM,uBAAuB;AAE7B,SAAgB,sBAAsB,WAAmC;CACvE,QAAQ,UAAU,MAAlB;EACE,KAAK;EACL,KAAK;GACH,OAAO,UAAU,WAAW,SAAS,GAAG,uBAAuB;GAC/D,OAAO,IAAI,UAAU,WAClB,IAAI,qBAAqB,CAAC,CAC1B,KAAK,IAAI,UAAU,KAAK,YAAY,EAAE,EAAE,EAAE;EAC/C,KAAK,OACH,OAAO,QAAQ,sBAAsB,UAAU,SAAS,EAAE;EAC5D,KAAK,aACH,OAAO,GAAG,GAAG,UAAU,MAAM,EAAE,GAAG,UAAU;EAC9C,KAAK,cACH,OAAO,GAAG,GAAG,UAAU,MAAM,EAAE,GAAG,UAAU,GAAG,GAAG,kBAChD,UAAU,KACZ;CACJ;AACF;AAEA,SAAS,kBAAkB,OAAoC;CAC7D,QAAQ,MAAM,MAAd;EACE,KAAK,WACH,OAAO,MAAM,QAAQ,MAAM;EAC7B,KAAK;GAEH,OACE,qBAAqB,KAAK,MAAM,KAAK,GACrC,2BACF;GACA,OAAO,MAAM;EACf,KAAK,UACH,OAAO,IAAI,MAAM,KAAK;CAC1B;AACF;AAEA,SAAgB,yBACd,WACA,KACgB;CAChB,QAAQ,UAAU,MAAlB;EACE,KAAK;EACL,KAAK,MACH,OAAO;GACL,GAAG;GACH,YAAY,UAAU,WAAW,KAAI,cACnC,yBAAyB,WAAW,GAAG,CACzC;EACF;EACF,KAAK,OACH,OAAO;GACL,GAAG;GACH,WAAW,yBAAyB,UAAU,WAAW,GAAG;EAC9D;EACF,KAAK;EACL,KAAK,cACH,OAAO;GAAC,GAAG;GAAW,QAAQ,IAAI,UAAU,MAAM;EAAC;CACvD;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"lite-tables.js","names":[],"sources":["../../../../../zero-cache/src/db/lite-tables.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {newArray} from '../../../shared/src/arrays.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {difference} from '../../../shared/src/set-utils.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {primaryKeySchema} from '../../../zero-protocol/src/primary-key.ts';\nimport type {Database} from '../../../zqlite/src/db.ts';\nimport {\n ColumnMetadataStore,\n metadataToLiteTypeString,\n} from '../services/replicator/schema/column-metadata.ts';\nimport {TableMetadataTracker} from '../services/replicator/schema/table-metadata.ts';\nimport {\n isArray,\n isEnum,\n liteTypeToZqlValueType,\n mapLiteDataTypeToZqlSchemaValue,\n nullableUpstream,\n} from '../types/lite.ts';\nimport * as PostgresTypeClass from './postgres-type-class-enum.ts';\nimport type {\n LiteAndZqlSpec,\n LiteIndexSpec,\n LiteTableSpec,\n MutableLiteIndexSpec,\n MutableLiteTableSpec,\n} from './specs.ts';\n\ntype ColumnInfo = {\n table: string;\n name: string;\n type: string;\n notNull: number;\n dflt: string | null;\n keyPos: number;\n};\n\nexport type LiteTableSpecWithReplicationStatus = LiteTableSpec & {\n readonly backfilling?: string[];\n readonly minRowVersion?: string | null;\n};\n\ntype MutableLiteTableSpecWithReplicationStatus = MutableLiteTableSpec & {\n backfilling: string[];\n minRowVersion: string | null;\n};\n\nexport function listTables(\n db: Database,\n useColumnMetadata = true,\n useTableMetadata = true,\n): LiteTableSpecWithReplicationStatus[] {\n const columns = db\n .prepare(\n `\n SELECT\n m.name as \"table\",\n p.name as name,\n p.type as type,\n p.\"notnull\" as \"notNull\",\n p.dflt_value as \"dflt\",\n p.pk as keyPos\n FROM sqlite_master as m\n LEFT JOIN pragma_table_info(m.name) as p\n WHERE m.type = 'table'\n AND m.name NOT LIKE 'sqlite_%'\n AND m.name NOT LIKE '_zero.%'\n AND m.name NOT LIKE '_litestream_%'\n `,\n )\n .all() as ColumnInfo[];\n\n const minRowVersions = useTableMetadata\n ? new TableMetadataTracker(db).getMinRowVersions()\n : new Map();\n const tables: LiteTableSpecWithReplicationStatus[] = [];\n let table: MutableLiteTableSpecWithReplicationStatus | undefined;\n\n columns.forEach(col => {\n if (col.table !== table?.name) {\n // New table\n table = {\n name: col.table,\n columns: {},\n backfilling: [],\n minRowVersion: minRowVersions.get(col.table) ?? null,\n };\n tables.push(table);\n }\n\n // Try to read from metadata table first, fall back to SQLite column type\n let dataType: string;\n let elemPgTypeClass:\n | typeof PostgresTypeClass.Base\n | typeof PostgresTypeClass.Enum\n | null;\n\n const metadata = useColumnMetadata\n ? ColumnMetadataStore.getInstance(db)?.getColumn(col.table, col.name)\n : undefined;\n if (metadata) {\n // Read from metadata table and convert to pipe notation\n dataType = metadataToLiteTypeString(metadata);\n elemPgTypeClass = metadata.isArray\n ? metadata.isEnum\n ? PostgresTypeClass.Enum\n : PostgresTypeClass.Base\n : null;\n } else {\n // Fall back to reading from SQLite column type (pipe notation)\n dataType = col.type;\n elemPgTypeClass = isArray(col.type)\n ? isEnum(col.type)\n ? PostgresTypeClass.Enum\n : PostgresTypeClass.Base\n : null;\n }\n\n table.columns[col.name] = {\n pos: Object.keys(table.columns).length + 1,\n dataType,\n characterMaximumLength: null,\n notNull: col.notNull !== 0,\n dflt: col.dflt,\n elemPgTypeClass,\n };\n if (metadata?.isBackfilling) {\n table.backfilling.push(col.name);\n }\n if (col.keyPos) {\n table.primaryKey ??= [];\n while (table.primaryKey.length < col.keyPos) {\n table.primaryKey.push('');\n }\n table.primaryKey[col.keyPos - 1] = col.name;\n }\n });\n\n return tables;\n}\n\n/**\n * An index as it exists on the replica. SQLite retains only the DDL text of a\n * partial index, not a structured predicate, so replica indexes report a\n * `partial` flag in place of the {@link LiteIndexSpec.predicate}.\n */\nexport type ReplicaIndexSpec = Omit<LiteIndexSpec, 'predicate'> & {\n readonly partial?: boolean | undefined;\n};\n\ntype MutableReplicaIndexSpec = Omit<MutableLiteIndexSpec, 'predicate'> & {\n partial?: boolean | undefined;\n};\n\nexport function listIndexes(db: Database): ReplicaIndexSpec[] {\n const indexes = db\n .prepare(\n `SELECT \n idx.name as indexName, \n idx.tbl_name as tableName, \n info.\"unique\" as \"unique\",\n info.partial as partial,\n col.name as column,\n CASE WHEN col.desc = 0 THEN 'ASC' ELSE 'DESC' END as dir\n FROM sqlite_master as idx\n JOIN pragma_index_list(idx.tbl_name) AS info ON info.name = idx.name\n JOIN pragma_index_xinfo(idx.name) as col\n WHERE idx.type = 'index' AND \n col.key = 1 AND\n idx.tbl_name NOT LIKE '_zero.%'\n ORDER BY idx.name, col.seqno ASC`,\n )\n .all() as {\n indexName: string;\n tableName: string;\n unique: number;\n partial: number;\n column: string;\n dir: 'ASC' | 'DESC';\n }[];\n\n const ret: MutableReplicaIndexSpec[] = [];\n for (const {\n indexName: name,\n tableName,\n unique,\n partial,\n column,\n dir,\n } of indexes) {\n if (ret.at(-1)?.name === name) {\n // Aggregate multiple column names into the array.\n must(ret.at(-1)).columns[column] = dir;\n } else {\n ret.push({\n tableName,\n name,\n columns: {[column]: dir},\n unique: unique !== 0,\n ...(partial === 0 ? {} : {partial: true}),\n });\n }\n }\n\n return ret;\n}\n\nexport type ZqlSpecOptions = {\n /**\n * Controls whether to include backfilling columns in the computed\n * LiteAndZqlSpec. In general, backfilling columns should be include\n * in \"replication\" logic (copying data from upstream to the replica),\n * and excluded from \"sync\" logic (sending data from the replica to\n * clients).\n */\n includeBackfillingColumns: boolean;\n};\n\n/**\n * Computes a TableSpec \"view\" of the replicated data that is\n * suitable for processing / consumption for the client. This\n * includes:\n * * excluding tables without a PRIMARY KEY or UNIQUE INDEX\n * * excluding columns with types that are not supported by ZQL\n * * choosing columns to use as the primary key amongst those\n * in unique indexes\n *\n * @param tableSpecs an optional map to reset and populate\n * @param fullTables an optional map to receive the full table specs,\n * which may include tables and columns that are not synced to\n * the client because they lack a primary key or are of unsupported\n * data types.\n */\nexport function computeZqlSpecs(\n lc: LogContext,\n replica: Database,\n opts: ZqlSpecOptions,\n tableSpecs: Map<string, LiteAndZqlSpec> = new Map(),\n fullTables?: Map<string, LiteTableSpec>,\n): Map<string, LiteAndZqlSpec> {\n return computeZqlSpecsFromLiteSpecs(\n listTables(replica),\n listIndexes(replica),\n opts,\n tableSpecs,\n fullTables,\n lc,\n );\n}\n\nfunction isPartialIndex(idx: LiteIndexSpec | ReplicaIndexSpec): boolean {\n return (\n ('predicate' in idx && idx.predicate !== undefined) ||\n ('partial' in idx && idx.partial === true)\n );\n}\n\nexport function computeZqlSpecsFromLiteSpecs(\n tables: LiteTableSpecWithReplicationStatus[],\n indexes: readonly (LiteIndexSpec | ReplicaIndexSpec)[],\n {includeBackfillingColumns}: ZqlSpecOptions,\n tableSpecs: Map<string, LiteAndZqlSpec> = new Map(),\n fullTables?: Map<string, LiteTableSpec>,\n lc?: LogContext,\n): Map<string, LiteAndZqlSpec> {\n tableSpecs.clear();\n fullTables?.clear();\n\n const uniqueIndexColumns = new Map<string, string[][]>();\n // A partial unique index only constrains the rows matching its predicate,\n // so it cannot serve as a row key.\n for (const {tableName, columns} of indexes.filter(\n idx => idx.unique && !isPartialIndex(idx),\n )) {\n getOrInsertComputed(uniqueIndexColumns, tableName, newArray).push(\n Object.keys(columns),\n );\n }\n\n tables.forEach(fullTable => {\n fullTables?.set(fullTable.name, fullTable);\n\n const backfilling = new Set(fullTable.backfilling);\n // Only include columns that:\n // - have a defined ZQL Value\n // - aren't backfilling if `includeBackfillingColumns` is false\n const visibleColumns = Object.entries(fullTable.columns).filter(\n ([col, {dataType}]) =>\n liteTypeToZqlValueType(dataType) &&\n (includeBackfillingColumns || !backfilling.has(col)),\n );\n const notNullColumns = new Set(\n visibleColumns\n .filter(\n ([col, {dataType}]) =>\n !nullableUpstream(dataType) || fullTable.primaryKey?.includes(col),\n )\n .map(([col]) => col),\n );\n\n const uniqueKeys = uniqueIndexColumns.get(fullTable.name) ?? [];\n // Examine all column combinations that can serve as a primary key,\n // i.e. excluding indexes over nullable or unsynced columns.\n const keys = uniqueKeys.filter(\n key => difference(new Set(key), notNullColumns).size === 0,\n );\n if (keys.length === 0) {\n // Only include tables with a row key.\n //\n // Note that this will automatically exclude tables that are being\n // backfilled (when includeBackfillingColumns is `false`) since candidate\n // keys only include visible columns.\n lc?.debug?.(\n `not syncing table ${fullTable.name} because it ` +\n (backfilling.size ? 'is being backfilled' : 'has no primary key'),\n );\n return;\n }\n // Pick the \"best\" (i.e. shortest) key for default IVM operations.\n const primaryKey = keys.sort(keyCmp)[0];\n\n const tableSpec = {\n ...fullTable,\n columns: Object.fromEntries(visibleColumns),\n primaryKey: v.parse(primaryKey, primaryKeySchema),\n uniqueKeys: uniqueKeys.map(key => v.parse(key, primaryKeySchema)),\n allPotentialPrimaryKeys: keys.map(key => v.parse(key, primaryKeySchema)),\n minRowVersion: fullTable.minRowVersion ?? null,\n };\n\n tableSpecs.set(tableSpec.name, {\n tableSpec,\n zqlSpec: Object.fromEntries(\n Object.entries(tableSpec.columns).map(([name, {dataType}]) => [\n name,\n mapLiteDataTypeToZqlSchemaValue(dataType),\n ]),\n ),\n });\n });\n return tableSpecs;\n}\n\nexport function mustGetTableSpec(\n tableSpecs: Map<string, LiteAndZqlSpec>,\n tableName: string,\n): LiteAndZqlSpec {\n const tableSpec = tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(\n `table '${tableName}' is not one of: ${[...tableSpecs.keys()]\n .filter(t => !t.includes('.') && !t.startsWith('_litestream_'))\n .sort()}. ` +\n `Check the spelling and ensure that the table has a primary key.`,\n );\n }\n return tableSpec;\n}\n\n// Deterministic comparator for favoring shorter row keys.\nfunction keyCmp(a: string[], b: string[]) {\n if (a.length !== b.length) {\n return a.length - b.length; // Fewer columns are better.\n }\n for (let i = 0; i < a.length; i++) {\n if (a[i] < b[i]) {\n return -1;\n }\n if (a[i] > b[i]) {\n return 1;\n }\n }\n return 0;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lite-tables.js","names":["v.parse"],"sources":["../../../../../zero-cache/src/db/lite-tables.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {newArray} from '../../../shared/src/arrays.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {difference} from '../../../shared/src/set-utils.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {primaryKeySchema} from '../../../zero-protocol/src/primary-key.ts';\nimport type {Database} from '../../../zqlite/src/db.ts';\nimport {\n ColumnMetadataStore,\n metadataToLiteTypeString,\n} from '../services/replicator/schema/column-metadata.ts';\nimport {TableMetadataTracker} from '../services/replicator/schema/table-metadata.ts';\nimport {\n isArray,\n isEnum,\n liteTypeToZqlValueType,\n mapLiteDataTypeToZqlSchemaValue,\n nullableUpstream,\n} from '../types/lite.ts';\nimport * as PostgresTypeClass from './postgres-type-class-enum.ts';\nimport type {\n LiteAndZqlSpec,\n LiteIndexSpec,\n LiteTableSpec,\n MutableLiteIndexSpec,\n MutableLiteTableSpec,\n} from './specs.ts';\n\ntype ColumnInfo = {\n table: string;\n name: string;\n type: string;\n notNull: number;\n dflt: string | null;\n keyPos: number;\n};\n\nexport type LiteTableSpecWithReplicationStatus = LiteTableSpec & {\n readonly backfilling?: string[];\n readonly minRowVersion?: string | null;\n};\n\ntype MutableLiteTableSpecWithReplicationStatus = MutableLiteTableSpec & {\n backfilling: string[];\n minRowVersion: string | null;\n};\n\nexport function listTables(\n db: Database,\n useColumnMetadata = true,\n useTableMetadata = true,\n): LiteTableSpecWithReplicationStatus[] {\n const columns = db\n .prepare(\n `\n SELECT\n m.name as \"table\",\n p.name as name,\n p.type as type,\n p.\"notnull\" as \"notNull\",\n p.dflt_value as \"dflt\",\n p.pk as keyPos\n FROM sqlite_master as m\n LEFT JOIN pragma_table_info(m.name) as p\n WHERE m.type = 'table'\n AND m.name NOT LIKE 'sqlite_%'\n AND m.name NOT LIKE '_zero.%'\n AND m.name NOT LIKE '_litestream_%'\n `,\n )\n .all() as ColumnInfo[];\n\n const minRowVersions = useTableMetadata\n ? new TableMetadataTracker(db).getMinRowVersions()\n : new Map();\n const tables: LiteTableSpecWithReplicationStatus[] = [];\n let table: MutableLiteTableSpecWithReplicationStatus | undefined;\n\n columns.forEach(col => {\n if (col.table !== table?.name) {\n // New table\n table = {\n name: col.table,\n columns: {},\n backfilling: [],\n minRowVersion: minRowVersions.get(col.table) ?? null,\n };\n tables.push(table);\n }\n\n // Try to read from metadata table first, fall back to SQLite column type\n let dataType: string;\n let elemPgTypeClass:\n | typeof PostgresTypeClass.Base\n | typeof PostgresTypeClass.Enum\n | null;\n\n const metadata = useColumnMetadata\n ? ColumnMetadataStore.getInstance(db)?.getColumn(col.table, col.name)\n : undefined;\n if (metadata) {\n // Read from metadata table and convert to pipe notation\n dataType = metadataToLiteTypeString(metadata);\n elemPgTypeClass = metadata.isArray\n ? metadata.isEnum\n ? PostgresTypeClass.Enum\n : PostgresTypeClass.Base\n : null;\n } else {\n // Fall back to reading from SQLite column type (pipe notation)\n dataType = col.type;\n elemPgTypeClass = isArray(col.type)\n ? isEnum(col.type)\n ? PostgresTypeClass.Enum\n : PostgresTypeClass.Base\n : null;\n }\n\n table.columns[col.name] = {\n pos: Object.keys(table.columns).length + 1,\n dataType,\n characterMaximumLength: null,\n notNull: col.notNull !== 0,\n dflt: col.dflt,\n elemPgTypeClass,\n };\n if (metadata?.isBackfilling) {\n table.backfilling.push(col.name);\n }\n if (col.keyPos) {\n table.primaryKey ??= [];\n while (table.primaryKey.length < col.keyPos) {\n table.primaryKey.push('');\n }\n table.primaryKey[col.keyPos - 1] = col.name;\n }\n });\n\n return tables;\n}\n\n/**\n * An index as it exists on the replica. SQLite retains only the DDL text of a\n * partial index, not a structured predicate, so replica indexes report a\n * `partial` flag in place of the {@link LiteIndexSpec.predicate}.\n */\nexport type ReplicaIndexSpec = Omit<LiteIndexSpec, 'predicate'> & {\n readonly partial?: boolean | undefined;\n};\n\ntype MutableReplicaIndexSpec = Omit<MutableLiteIndexSpec, 'predicate'> & {\n partial?: boolean | undefined;\n};\n\nexport function listIndexes(db: Database): ReplicaIndexSpec[] {\n const indexes = db\n .prepare(\n `SELECT \n idx.name as indexName, \n idx.tbl_name as tableName, \n info.\"unique\" as \"unique\",\n info.partial as partial,\n col.name as column,\n CASE WHEN col.desc = 0 THEN 'ASC' ELSE 'DESC' END as dir\n FROM sqlite_master as idx\n JOIN pragma_index_list(idx.tbl_name) AS info ON info.name = idx.name\n JOIN pragma_index_xinfo(idx.name) as col\n WHERE idx.type = 'index' AND \n col.key = 1 AND\n idx.tbl_name NOT LIKE '_zero.%'\n ORDER BY idx.name, col.seqno ASC`,\n )\n .all() as {\n indexName: string;\n tableName: string;\n unique: number;\n partial: number;\n column: string;\n dir: 'ASC' | 'DESC';\n }[];\n\n const ret: MutableReplicaIndexSpec[] = [];\n for (const {\n indexName: name,\n tableName,\n unique,\n partial,\n column,\n dir,\n } of indexes) {\n if (ret.at(-1)?.name === name) {\n // Aggregate multiple column names into the array.\n must(ret.at(-1)).columns[column] = dir;\n } else {\n ret.push({\n tableName,\n name,\n columns: {[column]: dir},\n unique: unique !== 0,\n ...(partial === 0 ? {} : {partial: true}),\n });\n }\n }\n\n return ret;\n}\n\nexport type ZqlSpecOptions = {\n /**\n * Controls whether to include backfilling columns in the computed\n * LiteAndZqlSpec. In general, backfilling columns should be include\n * in \"replication\" logic (copying data from upstream to the replica),\n * and excluded from \"sync\" logic (sending data from the replica to\n * clients).\n */\n includeBackfillingColumns: boolean;\n};\n\n/**\n * Computes a TableSpec \"view\" of the replicated data that is\n * suitable for processing / consumption for the client. This\n * includes:\n * * excluding tables without a PRIMARY KEY or UNIQUE INDEX\n * * excluding columns with types that are not supported by ZQL\n * * choosing columns to use as the primary key amongst those\n * in unique indexes\n *\n * @param tableSpecs an optional map to reset and populate\n * @param fullTables an optional map to receive the full table specs,\n * which may include tables and columns that are not synced to\n * the client because they lack a primary key or are of unsupported\n * data types.\n */\nexport function computeZqlSpecs(\n lc: LogContext,\n replica: Database,\n opts: ZqlSpecOptions,\n tableSpecs: Map<string, LiteAndZqlSpec> = new Map(),\n fullTables?: Map<string, LiteTableSpec>,\n): Map<string, LiteAndZqlSpec> {\n return computeZqlSpecsFromLiteSpecs(\n listTables(replica),\n listIndexes(replica),\n opts,\n tableSpecs,\n fullTables,\n lc,\n );\n}\n\nfunction isPartialIndex(idx: LiteIndexSpec | ReplicaIndexSpec): boolean {\n return (\n ('predicate' in idx && idx.predicate !== undefined) ||\n ('partial' in idx && idx.partial === true)\n );\n}\n\nexport function computeZqlSpecsFromLiteSpecs(\n tables: LiteTableSpecWithReplicationStatus[],\n indexes: readonly (LiteIndexSpec | ReplicaIndexSpec)[],\n {includeBackfillingColumns}: ZqlSpecOptions,\n tableSpecs: Map<string, LiteAndZqlSpec> = new Map(),\n fullTables?: Map<string, LiteTableSpec>,\n lc?: LogContext,\n): Map<string, LiteAndZqlSpec> {\n tableSpecs.clear();\n fullTables?.clear();\n\n const uniqueIndexColumns = new Map<string, string[][]>();\n // A partial unique index only constrains the rows matching its predicate,\n // so it cannot serve as a row key.\n for (const {tableName, columns} of indexes.filter(\n idx => idx.unique && !isPartialIndex(idx),\n )) {\n getOrInsertComputed(uniqueIndexColumns, tableName, newArray).push(\n Object.keys(columns),\n );\n }\n\n tables.forEach(fullTable => {\n fullTables?.set(fullTable.name, fullTable);\n\n const backfilling = new Set(fullTable.backfilling);\n // Only include columns that:\n // - have a defined ZQL Value\n // - aren't backfilling if `includeBackfillingColumns` is false\n const visibleColumns = Object.entries(fullTable.columns).filter(\n ([col, {dataType}]) =>\n liteTypeToZqlValueType(dataType) &&\n (includeBackfillingColumns || !backfilling.has(col)),\n );\n const notNullColumns = new Set(\n visibleColumns\n .filter(\n ([col, {dataType}]) =>\n !nullableUpstream(dataType) || fullTable.primaryKey?.includes(col),\n )\n .map(([col]) => col),\n );\n\n const uniqueKeys = uniqueIndexColumns.get(fullTable.name) ?? [];\n // Examine all column combinations that can serve as a primary key,\n // i.e. excluding indexes over nullable or unsynced columns.\n const keys = uniqueKeys.filter(\n key => difference(new Set(key), notNullColumns).size === 0,\n );\n if (keys.length === 0) {\n // Only include tables with a row key.\n //\n // Note that this will automatically exclude tables that are being\n // backfilled (when includeBackfillingColumns is `false`) since candidate\n // keys only include visible columns.\n lc?.debug?.(\n `not syncing table ${fullTable.name} because it ` +\n (backfilling.size ? 'is being backfilled' : 'has no primary key'),\n );\n return;\n }\n // Pick the \"best\" (i.e. shortest) key for default IVM operations.\n const primaryKey = keys.sort(keyCmp)[0];\n\n const tableSpec = {\n ...fullTable,\n columns: Object.fromEntries(visibleColumns),\n primaryKey: v.parse(primaryKey, primaryKeySchema),\n uniqueKeys: uniqueKeys.map(key => v.parse(key, primaryKeySchema)),\n allPotentialPrimaryKeys: keys.map(key => v.parse(key, primaryKeySchema)),\n minRowVersion: fullTable.minRowVersion ?? null,\n };\n\n tableSpecs.set(tableSpec.name, {\n tableSpec,\n zqlSpec: Object.fromEntries(\n Object.entries(tableSpec.columns).map(([name, {dataType}]) => [\n name,\n mapLiteDataTypeToZqlSchemaValue(dataType),\n ]),\n ),\n });\n });\n return tableSpecs;\n}\n\nexport function mustGetTableSpec(\n tableSpecs: Map<string, LiteAndZqlSpec>,\n tableName: string,\n): LiteAndZqlSpec {\n const tableSpec = tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(\n `table '${tableName}' is not one of: ${[...tableSpecs.keys()]\n .filter(t => !t.includes('.') && !t.startsWith('_litestream_'))\n .sort()}. ` +\n `Check the spelling and ensure that the table has a primary key.`,\n );\n }\n return tableSpec;\n}\n\n// Deterministic comparator for favoring shorter row keys.\nfunction keyCmp(a: string[], b: string[]) {\n if (a.length !== b.length) {\n return a.length - b.length; // Fewer columns are better.\n }\n for (let i = 0; i < a.length; i++) {\n if (a[i] < b[i]) {\n return -1;\n }\n if (a[i] > b[i]) {\n return 1;\n }\n }\n return 0;\n}\n"],"mappings":"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"}
@@ -168,6 +168,6 @@ async function runTransaction(log, db, tx) {
168
168
  }
169
169
  }
170
170
  //#endregion
171
- export { runSchemaMigrations };
171
+ export { getVersionHistory, runSchemaMigrations };
172
172
 
173
173
  //# sourceMappingURL=migration-lite.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migration-lite.js","names":[],"sources":["../../../../../zero-cache/src/db/migration-lite.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {AbortError} from '../../../shared/src/abort-error.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {Database as Db} from '../../../zqlite/src/db.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\n\ntype Operations = (log: LogContext, tx: Db) => Promise<void> | void;\n\n/**\n * Encapsulates the logic for setting up or upgrading to a new schema. After the\n * Migration code successfully completes, {@link runSchemaMigrations}\n * will update the schema version and commit the transaction.\n */\nexport type Migration = {\n /**\n * Perform database operations that create or alter table structure. This is\n * called at most once during lifetime of the application. If a `migrateData()`\n * operation is defined, that will be performed after `migrateSchema()` succeeds.\n */\n migrateSchema?: Operations;\n\n /**\n * Perform database operations to migrate data to the new schema. This is\n * called after `migrateSchema()` (if defined), and may be called again\n * to re-migrate data after the server was rolled back to an earlier version,\n * and rolled forward again.\n *\n * Consequently, the logic in `migrateData()` must be idempotent.\n */\n migrateData?: Operations;\n\n /**\n * Sets the `minSafeVersion` to the specified value, prohibiting running\n * any earlier code versions.\n */\n minSafeVersion?: number;\n};\n\n/**\n * Mapping of incremental migrations to move from the previous old code\n * version to next one. Versions must be non-zero.\n *\n * The schema resulting from performing incremental migrations should be\n * equivalent to that of the `setupMigration` on a blank database.\n *\n * The highest destinationVersion of this map denotes the current\n * \"code version\", and is also used as the destination version when\n * running the initial setup migration on a blank database.\n */\nexport type IncrementalMigrationMap = {\n [destinationVersion: number]: Migration;\n};\n\n/**\n * Ensures that the schema is compatible with the current code, updating and\n * migrating the schema if necessary.\n */\nexport async function runSchemaMigrations(\n log: LogContext,\n debugName: string,\n dbPath: string,\n setupMigration: Migration,\n incrementalMigrationMap: IncrementalMigrationMap,\n): Promise<void> {\n const start = Date.now();\n log = log.withContext(\n 'initSchema',\n randInt(0, Number.MAX_SAFE_INTEGER).toString(36),\n );\n const db = new Database(log, dbPath);\n\n try {\n const versionMigrations = sorted(incrementalMigrationMap);\n assert(\n versionMigrations.length,\n `Must specify a at least one version migration`,\n );\n assert(\n versionMigrations[0][0] > 0,\n `Versions must be non-zero positive numbers`,\n );\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const codeVersion = versionMigrations.at(-1)![0];\n log.info?.(\n `Checking schema for compatibility with ${debugName} at schema v${codeVersion}`,\n );\n\n let versions = await runTransaction(log, db, tx => {\n const versions = getVersionHistory(tx);\n if (codeVersion < versions.minSafeVersion) {\n throw new Error(\n `Cannot run ${debugName} at schema v${codeVersion} because rollback limit is v${versions.minSafeVersion}`,\n );\n }\n\n if (versions.dataVersion > codeVersion) {\n log.info?.(\n `Data is at v${versions.dataVersion}. Resetting to v${codeVersion}`,\n );\n return updateVersionHistory(log, tx, versions, codeVersion);\n }\n return versions;\n });\n if (versions.dataVersion < codeVersion) {\n // Enables journal_mode = OFF, and lifts the guards against writing while\n // iterating a query, which migrations may rely on.\n db.unsafeMode(true);\n db.pragma('foreign_keys = OFF');\n if (versions.dataVersion === 0) {\n // The setup migration (i.e. initial sync) populates a new database,\n // which nothing else has open, as fast as possible.\n db.pragma('locking_mode = EXCLUSIVE');\n db.pragma('journal_mode = OFF');\n db.pragma('synchronous = OFF');\n } else {\n // Incremental migrations are small, and run in the database's journal\n // mode, which keeps each migration atomic and its WAL intact: resuming\n // a litestream backup requires continuing from the WAL, whose salt a\n // WAL restart would change. Disable automatic checkpoints to avoid WAL\n // restarts from schema updates.\n db.pragma('wal_autocheckpoint = 0');\n }\n // Unfortunately, AUTO_VACUUM is not compatible with BEGIN CONCURRENT,\n // so it is not an option for the replica file.\n // https://sqlite.org/forum/forumpost/25f183416a\n // db.pragma('auto_vacuum = INCREMENTAL');\n\n const migrations =\n versions.dataVersion === 0\n ? // For the empty database v0, only run the setup migration.\n ([[codeVersion, setupMigration]] as const)\n : versionMigrations;\n\n for (const [dest, migration] of migrations) {\n if (versions.dataVersion < dest) {\n log.info?.(\n `Migrating schema from v${versions.dataVersion} to v${dest}`,\n );\n void log.flush(); // Flush logs before each migration to help debug crash-y migrations.\n\n versions = await runTransaction(log, db, async tx => {\n // Fetch meta from within the transaction to make the migration atomic.\n let versions = getVersionHistory(tx);\n if (versions.dataVersion < dest) {\n versions = await runMigration(log, tx, versions, dest, migration);\n assert(\n versions.dataVersion === dest,\n () =>\n `Migration did not reach target version: expected ${dest}, got ${versions.dataVersion}`,\n );\n }\n return versions;\n });\n }\n }\n\n // Note: This used to be ANALYZE but that takes a long time for large\n // dbs. The SQLite docs generally recommend optimize instead.\n //\n // https://sqlite.org/pragma.html#pragma_analysis_limit\n //\n // Beginning with SQLite version 3.46.0 (2024-05-23), the recommended\n // way of running ANALYZE is with the PRAGMA optimize command.\n // The PRAGMA optimize will automatically set a reasonable, temporary\n // analysis limit that ensures that the PRAGMA optimize command will\n // finish quickly even on enormous databases. Applications that use\n // the PRAGMA optimize instead of running ANALYZE directly do not need\n // to set an analysis limit.\n const start = performance.now();\n db.pragma('optimize');\n const elapsed = performance.now() - start;\n log.info?.(`OPTIMIZE completed (${elapsed.toFixed(2)}ms)`);\n }\n\n // Do not run `quick_check` here. It scans the full database and can\n // dominate startup time for large replicas or slower disks.\n db.pragma('synchronous = NORMAL');\n db.unsafeMode(false);\n\n assert(\n versions.dataVersion === codeVersion,\n () =>\n `Final dataVersion (${versions.dataVersion}) does not match codeVersion (${codeVersion})`,\n );\n log.info?.(\n `Running ${debugName} at schema v${codeVersion} (${\n Date.now() - start\n } ms)`,\n );\n } catch (e) {\n if (e instanceof AbortError) {\n // AbortErrors (e.g. AutoResetSignal) are not failures. They are the\n // expected mechanism for signaling that the replica needs a reset (e.g.\n // corruption or an incompatible schema version) so that the caller can\n // resync anew. Logging them at `error` would trip error-based alerting\n // for a normal, self-healing path.\n log.warn?.('Replica requires reset; resync will follow', e);\n } else {\n log.error?.('Error in ensureSchemaMigrated', e);\n }\n throw e;\n } finally {\n db.close();\n void log.flush(); // Flush the logs but do not block server progress on it.\n }\n}\n\nfunction sorted(\n incrementalMigrationMap: IncrementalMigrationMap,\n): [number, Migration][] {\n const versionMigrations: [number, Migration][] = [];\n for (const [v, m] of Object.entries(incrementalMigrationMap)) {\n versionMigrations.push([Number(v), m]);\n }\n return versionMigrations.sort(([a], [b]) => a - b);\n}\n\n// Exposed for tests.\nexport const versionHistory = v.object({\n /**\n * The `schemaVersion` is highest code version that has ever been run\n * on the database, and is used to delineate the structure of the tables\n * in the database. A schemaVersion only moves forward; rolling back to\n * an earlier (safe) code version does not revert schema changes that\n * have already been applied.\n */\n schemaVersion: v.number(),\n\n /**\n * The data version is the code version of the latest server that ran.\n * Note that this may be less than the schemaVersion in the case that\n * a server is rolled back to an earlier version after a schema change.\n * In such a case, data (but not schema), may need to be re-migrated\n * when rolling forward again.\n */\n dataVersion: v.number(),\n\n /**\n * The minimum code version that is safe to run. This is used when\n * a schema migration is not backwards compatible with an older version\n * of the code.\n */\n minSafeVersion: v.number(),\n});\n\n// Exposed for tests.\nexport type VersionHistory = v.Infer<typeof versionHistory>;\n\n// Exposed for tests\nexport function getVersionHistory(db: Db): VersionHistory {\n // Note: The `lock` column transparently ensures that at most one row exists.\n db.prepare(\n `\n CREATE TABLE IF NOT EXISTS \"_zero.versionHistory\" (\n dataVersion INTEGER NOT NULL,\n schemaVersion INTEGER NOT NULL,\n minSafeVersion INTEGER NOT NULL,\n\n lock INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n `,\n ).run();\n const result = db\n .prepare(\n 'SELECT dataVersion, schemaVersion, minSafeVersion FROM \"_zero.versionHistory\"',\n )\n .get() as VersionHistory;\n return result ?? {dataVersion: 0, schemaVersion: 0, minSafeVersion: 0};\n}\n\nfunction updateVersionHistory(\n log: LogContext,\n db: Db,\n prev: VersionHistory,\n newVersion: number,\n minSafeVersion?: number,\n): VersionHistory {\n assert(newVersion > 0, 'newVersion must be positive');\n const meta = {\n ...prev,\n dataVersion: newVersion,\n // The schemaVersion never moves backwards.\n schemaVersion: Math.max(newVersion, prev.schemaVersion),\n minSafeVersion: getMinSafeVersion(log, prev, minSafeVersion),\n } satisfies VersionHistory;\n\n db.prepare(\n `\n INSERT INTO \"_zero.versionHistory\" (dataVersion, schemaVersion, minSafeVersion, lock)\n VALUES (@dataVersion, @schemaVersion, @minSafeVersion, 1)\n ON CONFLICT (lock) DO UPDATE\n SET dataVersion=@dataVersion,\n schemaVersion=@schemaVersion,\n minSafeVersion=@minSafeVersion\n `,\n ).run(meta);\n\n return meta;\n}\n\nasync function runMigration(\n log: LogContext,\n tx: Db,\n versions: VersionHistory,\n destinationVersion: number,\n migration: Migration,\n): Promise<VersionHistory> {\n if (versions.schemaVersion < destinationVersion) {\n await migration.migrateSchema?.(log, tx);\n }\n if (versions.dataVersion < destinationVersion) {\n await migration.migrateData?.(log, tx);\n }\n return updateVersionHistory(\n log,\n tx,\n versions,\n destinationVersion,\n migration.minSafeVersion,\n );\n}\n\n/**\n * Bumps the rollback limit [[toAtLeast]] the specified version.\n * Leaves the rollback limit unchanged if it is equal or greater.\n */\nfunction getMinSafeVersion(\n log: LogContext,\n current: VersionHistory,\n proposedSafeVersion?: number,\n): number {\n if (proposedSafeVersion === undefined) {\n return current.minSafeVersion;\n }\n if (current.minSafeVersion >= proposedSafeVersion) {\n // The rollback limit must never move backwards.\n log.debug?.(\n `rollback limit is already at ${current.minSafeVersion}, ` +\n `don't need to bump to ${proposedSafeVersion}`,\n );\n return current.minSafeVersion;\n }\n log.info?.(\n `bumping rollback limit from ${current.minSafeVersion} to ${proposedSafeVersion}`,\n );\n return proposedSafeVersion;\n}\n\n// Note: We use a custom transaction wrapper (instead of db.begin(...)) in order\n// to support async operations within the transaction.\nasync function runTransaction<T>(\n log: LogContext,\n db: Db,\n tx: (db: Db) => Promise<T> | T,\n): Promise<T> {\n db.prepare('BEGIN EXCLUSIVE').run();\n try {\n const result = await tx(db);\n db.prepare('COMMIT').run();\n return result;\n } catch (e) {\n if (e instanceof AbortError) {\n // AbortErrors (e.g. AutoResetSignal) are an expected, self-healing\n // control-flow signal, not a failure. Log at `warn` so the rollback is\n // still visible without tripping error-based alerting.\n log.warn?.('Aborted transaction for reset signal', e);\n } else {\n log.error?.('Aborted transaction due to error', e);\n }\n try {\n db.prepare('ROLLBACK').run();\n } catch (rollbackError) {\n log.error?.('Unable to rollback transaction', rollbackError);\n const combinedError = new Error(\n `Transaction failed and rollback also failed: operation error = ${String(\n e,\n )}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = e;\n throw combinedError;\n }\n throw e;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"migration-lite.js","names":["v"],"sources":["../../../../../zero-cache/src/db/migration-lite.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {AbortError} from '../../../shared/src/abort-error.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {Database as Db} from '../../../zqlite/src/db.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\n\ntype Operations = (log: LogContext, tx: Db) => Promise<void> | void;\n\n/**\n * Encapsulates the logic for setting up or upgrading to a new schema. After the\n * Migration code successfully completes, {@link runSchemaMigrations}\n * will update the schema version and commit the transaction.\n */\nexport type Migration = {\n /**\n * Perform database operations that create or alter table structure. This is\n * called at most once during lifetime of the application. If a `migrateData()`\n * operation is defined, that will be performed after `migrateSchema()` succeeds.\n */\n migrateSchema?: Operations;\n\n /**\n * Perform database operations to migrate data to the new schema. This is\n * called after `migrateSchema()` (if defined), and may be called again\n * to re-migrate data after the server was rolled back to an earlier version,\n * and rolled forward again.\n *\n * Consequently, the logic in `migrateData()` must be idempotent.\n */\n migrateData?: Operations;\n\n /**\n * Sets the `minSafeVersion` to the specified value, prohibiting running\n * any earlier code versions.\n */\n minSafeVersion?: number;\n};\n\n/**\n * Mapping of incremental migrations to move from the previous old code\n * version to next one. Versions must be non-zero.\n *\n * The schema resulting from performing incremental migrations should be\n * equivalent to that of the `setupMigration` on a blank database.\n *\n * The highest destinationVersion of this map denotes the current\n * \"code version\", and is also used as the destination version when\n * running the initial setup migration on a blank database.\n */\nexport type IncrementalMigrationMap = {\n [destinationVersion: number]: Migration;\n};\n\n/**\n * Ensures that the schema is compatible with the current code, updating and\n * migrating the schema if necessary.\n */\nexport async function runSchemaMigrations(\n log: LogContext,\n debugName: string,\n dbPath: string,\n setupMigration: Migration,\n incrementalMigrationMap: IncrementalMigrationMap,\n): Promise<void> {\n const start = Date.now();\n log = log.withContext(\n 'initSchema',\n randInt(0, Number.MAX_SAFE_INTEGER).toString(36),\n );\n const db = new Database(log, dbPath);\n\n try {\n const versionMigrations = sorted(incrementalMigrationMap);\n assert(\n versionMigrations.length,\n `Must specify a at least one version migration`,\n );\n assert(\n versionMigrations[0][0] > 0,\n `Versions must be non-zero positive numbers`,\n );\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const codeVersion = versionMigrations.at(-1)![0];\n log.info?.(\n `Checking schema for compatibility with ${debugName} at schema v${codeVersion}`,\n );\n\n let versions = await runTransaction(log, db, tx => {\n const versions = getVersionHistory(tx);\n if (codeVersion < versions.minSafeVersion) {\n throw new Error(\n `Cannot run ${debugName} at schema v${codeVersion} because rollback limit is v${versions.minSafeVersion}`,\n );\n }\n\n if (versions.dataVersion > codeVersion) {\n log.info?.(\n `Data is at v${versions.dataVersion}. Resetting to v${codeVersion}`,\n );\n return updateVersionHistory(log, tx, versions, codeVersion);\n }\n return versions;\n });\n if (versions.dataVersion < codeVersion) {\n // Enables journal_mode = OFF, and lifts the guards against writing while\n // iterating a query, which migrations may rely on.\n db.unsafeMode(true);\n db.pragma('foreign_keys = OFF');\n if (versions.dataVersion === 0) {\n // The setup migration (i.e. initial sync) populates a new database,\n // which nothing else has open, as fast as possible.\n db.pragma('locking_mode = EXCLUSIVE');\n db.pragma('journal_mode = OFF');\n db.pragma('synchronous = OFF');\n } else {\n // Incremental migrations are small, and run in the database's journal\n // mode, which keeps each migration atomic and its WAL intact: resuming\n // a litestream backup requires continuing from the WAL, whose salt a\n // WAL restart would change. Disable automatic checkpoints to avoid WAL\n // restarts from schema updates.\n db.pragma('wal_autocheckpoint = 0');\n }\n // Unfortunately, AUTO_VACUUM is not compatible with BEGIN CONCURRENT,\n // so it is not an option for the replica file.\n // https://sqlite.org/forum/forumpost/25f183416a\n // db.pragma('auto_vacuum = INCREMENTAL');\n\n const migrations =\n versions.dataVersion === 0\n ? // For the empty database v0, only run the setup migration.\n ([[codeVersion, setupMigration]] as const)\n : versionMigrations;\n\n for (const [dest, migration] of migrations) {\n if (versions.dataVersion < dest) {\n log.info?.(\n `Migrating schema from v${versions.dataVersion} to v${dest}`,\n );\n void log.flush(); // Flush logs before each migration to help debug crash-y migrations.\n\n versions = await runTransaction(log, db, async tx => {\n // Fetch meta from within the transaction to make the migration atomic.\n let versions = getVersionHistory(tx);\n if (versions.dataVersion < dest) {\n versions = await runMigration(log, tx, versions, dest, migration);\n assert(\n versions.dataVersion === dest,\n () =>\n `Migration did not reach target version: expected ${dest}, got ${versions.dataVersion}`,\n );\n }\n return versions;\n });\n }\n }\n\n // Note: This used to be ANALYZE but that takes a long time for large\n // dbs. The SQLite docs generally recommend optimize instead.\n //\n // https://sqlite.org/pragma.html#pragma_analysis_limit\n //\n // Beginning with SQLite version 3.46.0 (2024-05-23), the recommended\n // way of running ANALYZE is with the PRAGMA optimize command.\n // The PRAGMA optimize will automatically set a reasonable, temporary\n // analysis limit that ensures that the PRAGMA optimize command will\n // finish quickly even on enormous databases. Applications that use\n // the PRAGMA optimize instead of running ANALYZE directly do not need\n // to set an analysis limit.\n const start = performance.now();\n db.pragma('optimize');\n const elapsed = performance.now() - start;\n log.info?.(`OPTIMIZE completed (${elapsed.toFixed(2)}ms)`);\n }\n\n // Do not run `quick_check` here. It scans the full database and can\n // dominate startup time for large replicas or slower disks.\n db.pragma('synchronous = NORMAL');\n db.unsafeMode(false);\n\n assert(\n versions.dataVersion === codeVersion,\n () =>\n `Final dataVersion (${versions.dataVersion}) does not match codeVersion (${codeVersion})`,\n );\n log.info?.(\n `Running ${debugName} at schema v${codeVersion} (${\n Date.now() - start\n } ms)`,\n );\n } catch (e) {\n if (e instanceof AbortError) {\n // AbortErrors (e.g. AutoResetSignal) are not failures. They are the\n // expected mechanism for signaling that the replica needs a reset (e.g.\n // corruption or an incompatible schema version) so that the caller can\n // resync anew. Logging them at `error` would trip error-based alerting\n // for a normal, self-healing path.\n log.warn?.('Replica requires reset; resync will follow', e);\n } else {\n log.error?.('Error in ensureSchemaMigrated', e);\n }\n throw e;\n } finally {\n db.close();\n void log.flush(); // Flush the logs but do not block server progress on it.\n }\n}\n\nfunction sorted(\n incrementalMigrationMap: IncrementalMigrationMap,\n): [number, Migration][] {\n const versionMigrations: [number, Migration][] = [];\n for (const [v, m] of Object.entries(incrementalMigrationMap)) {\n versionMigrations.push([Number(v), m]);\n }\n return versionMigrations.sort(([a], [b]) => a - b);\n}\n\n// Exposed for tests.\nexport const versionHistory = v.object({\n /**\n * The `schemaVersion` is highest code version that has ever been run\n * on the database, and is used to delineate the structure of the tables\n * in the database. A schemaVersion only moves forward; rolling back to\n * an earlier (safe) code version does not revert schema changes that\n * have already been applied.\n */\n schemaVersion: v.number(),\n\n /**\n * The data version is the code version of the latest server that ran.\n * Note that this may be less than the schemaVersion in the case that\n * a server is rolled back to an earlier version after a schema change.\n * In such a case, data (but not schema), may need to be re-migrated\n * when rolling forward again.\n */\n dataVersion: v.number(),\n\n /**\n * The minimum code version that is safe to run. This is used when\n * a schema migration is not backwards compatible with an older version\n * of the code.\n */\n minSafeVersion: v.number(),\n});\n\n// Exposed for tests.\nexport type VersionHistory = v.Infer<typeof versionHistory>;\n\n// Exposed for tests\nexport function getVersionHistory(db: Db): VersionHistory {\n // Note: The `lock` column transparently ensures that at most one row exists.\n db.prepare(\n `\n CREATE TABLE IF NOT EXISTS \"_zero.versionHistory\" (\n dataVersion INTEGER NOT NULL,\n schemaVersion INTEGER NOT NULL,\n minSafeVersion INTEGER NOT NULL,\n\n lock INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n `,\n ).run();\n const result = db\n .prepare(\n 'SELECT dataVersion, schemaVersion, minSafeVersion FROM \"_zero.versionHistory\"',\n )\n .get() as VersionHistory;\n return result ?? {dataVersion: 0, schemaVersion: 0, minSafeVersion: 0};\n}\n\nfunction updateVersionHistory(\n log: LogContext,\n db: Db,\n prev: VersionHistory,\n newVersion: number,\n minSafeVersion?: number,\n): VersionHistory {\n assert(newVersion > 0, 'newVersion must be positive');\n const meta = {\n ...prev,\n dataVersion: newVersion,\n // The schemaVersion never moves backwards.\n schemaVersion: Math.max(newVersion, prev.schemaVersion),\n minSafeVersion: getMinSafeVersion(log, prev, minSafeVersion),\n } satisfies VersionHistory;\n\n db.prepare(\n `\n INSERT INTO \"_zero.versionHistory\" (dataVersion, schemaVersion, minSafeVersion, lock)\n VALUES (@dataVersion, @schemaVersion, @minSafeVersion, 1)\n ON CONFLICT (lock) DO UPDATE\n SET dataVersion=@dataVersion,\n schemaVersion=@schemaVersion,\n minSafeVersion=@minSafeVersion\n `,\n ).run(meta);\n\n return meta;\n}\n\nasync function runMigration(\n log: LogContext,\n tx: Db,\n versions: VersionHistory,\n destinationVersion: number,\n migration: Migration,\n): Promise<VersionHistory> {\n if (versions.schemaVersion < destinationVersion) {\n await migration.migrateSchema?.(log, tx);\n }\n if (versions.dataVersion < destinationVersion) {\n await migration.migrateData?.(log, tx);\n }\n return updateVersionHistory(\n log,\n tx,\n versions,\n destinationVersion,\n migration.minSafeVersion,\n );\n}\n\n/**\n * Bumps the rollback limit [[toAtLeast]] the specified version.\n * Leaves the rollback limit unchanged if it is equal or greater.\n */\nfunction getMinSafeVersion(\n log: LogContext,\n current: VersionHistory,\n proposedSafeVersion?: number,\n): number {\n if (proposedSafeVersion === undefined) {\n return current.minSafeVersion;\n }\n if (current.minSafeVersion >= proposedSafeVersion) {\n // The rollback limit must never move backwards.\n log.debug?.(\n `rollback limit is already at ${current.minSafeVersion}, ` +\n `don't need to bump to ${proposedSafeVersion}`,\n );\n return current.minSafeVersion;\n }\n log.info?.(\n `bumping rollback limit from ${current.minSafeVersion} to ${proposedSafeVersion}`,\n );\n return proposedSafeVersion;\n}\n\n// Note: We use a custom transaction wrapper (instead of db.begin(...)) in order\n// to support async operations within the transaction.\nasync function runTransaction<T>(\n log: LogContext,\n db: Db,\n tx: (db: Db) => Promise<T> | T,\n): Promise<T> {\n db.prepare('BEGIN EXCLUSIVE').run();\n try {\n const result = await tx(db);\n db.prepare('COMMIT').run();\n return result;\n } catch (e) {\n if (e instanceof AbortError) {\n // AbortErrors (e.g. AutoResetSignal) are an expected, self-healing\n // control-flow signal, not a failure. Log at `warn` so the rollback is\n // still visible without tripping error-based alerting.\n log.warn?.('Aborted transaction for reset signal', e);\n } else {\n log.error?.('Aborted transaction due to error', e);\n }\n try {\n db.prepare('ROLLBACK').run();\n } catch (rollbackError) {\n log.error?.('Unable to rollback transaction', rollbackError);\n const combinedError = new Error(\n `Transaction failed and rollback also failed: operation error = ${String(\n e,\n )}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = e;\n throw combinedError;\n }\n throw e;\n }\n}\n"],"mappings":"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"}
@@ -92,8 +92,10 @@ async function getVersionHistory(sql, schemaName, create = false) {
92
92
  if ((await sql`
93
93
  SELECT nspname, relname FROM pg_class
94
94
  JOIN pg_namespace ON relnamespace = pg_namespace.oid
95
- WHERE nspname = ${schemaName} AND relname = ${"versionHistory"}`).length === 0) if (create) await createVersionHistoryTable(sql, schemaName);
96
- else return null;
95
+ WHERE nspname = ${schemaName} AND relname = ${"versionHistory"}`).length === 0) {
96
+ if (create) await createVersionHistoryTable(sql, schemaName);
97
+ else return null;
98
+ }
97
99
  const rows = await sql`
98
100
  SELECT "dataVersion", "schemaVersion", "minSafeVersion"
99
101
  FROM ${sql(schemaName)}."versionHistory"`;
@@ -136,6 +138,6 @@ function getMinSafeVersion(log, current, proposedSafeVersion) {
136
138
  return proposedSafeVersion;
137
139
  }
138
140
  //#endregion
139
- export { runSchemaMigrations };
141
+ export { createVersionHistoryTable, getVersionHistory, runSchemaMigrations, versionHistory };
140
142
 
141
143
  //# sourceMappingURL=migration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migration.js","names":[],"sources":["../../../../../zero-cache/src/db/migration.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type postgres from 'postgres';\nimport {AbortError} from '../../../shared/src/abort-error.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport {runTx} from './run-transaction.ts';\n\ntype Operations = (log: LogContext, tx: PostgresTransaction) => Promise<void>;\n\n/**\n * Encapsulates the logic for setting up or upgrading to a new schema. After the\n * Migration code successfully completes, {@link runSchemaMigrations}\n * will update the schema version and commit the transaction.\n */\nexport type Migration = {\n /**\n * Perform database operations that create or alter table structure. This is\n * called at most once during lifetime of the application. If a `migrateData()`\n * operation is defined, that will be performed after `migrateSchema()` succeeds.\n */\n migrateSchema?: Operations;\n\n /**\n * Perform database operations to migrate data to the new schema. This is\n * called after `migrateSchema()` (if defined), and may be called again\n * to re-migrate data after the server was rolled back to an earlier version,\n * and rolled forward again.\n *\n * Consequently, the logic in `migrateData()` must be idempotent.\n */\n migrateData?: Operations;\n\n /**\n * Sets the `minSafeVersion` to the specified value, prohibiting running\n * any earlier code versions.\n */\n minSafeVersion?: number;\n};\n\n/**\n * Mapping of incremental migrations to move from the previous old code\n * version to next one. Versions must be non-zero.\n *\n * The schema resulting from performing incremental migrations should be\n * equivalent to that of the `setupMigration` on a blank database.\n *\n * The highest destinationVersion of this map denotes the current\n * \"code version\", and is also used as the destination version when\n * running the initial setup migration on a blank database.\n */\nexport type IncrementalMigrationMap = {\n [destinationVersion: number]: Migration;\n};\n\n/**\n * Ensures that the schema is compatible with the current code, updating and\n * migrating the schema if necessary.\n */\nexport async function runSchemaMigrations(\n log: LogContext,\n debugName: string,\n schemaName: string,\n db: PostgresDB,\n setupMigration: Migration,\n incrementalMigrationMap: IncrementalMigrationMap,\n): Promise<void> {\n log = log.withContext('initSchema', schemaName);\n\n const versionMigrations = sorted(incrementalMigrationMap);\n assert(\n versionMigrations.length,\n `Must specify at least one version migration`,\n );\n assert(\n versionMigrations[0][0] > 0,\n `Versions must be non-zero positive numbers`,\n );\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const codeVersion = versionMigrations.at(-1)![0];\n\n log.info?.(\n `Checking schema for compatibility with ${debugName} at schema v${codeVersion}`,\n );\n\n try {\n await runTx(db, async tx => {\n // Acquire advisory lock to prevent concurrent migrations from racing.\n // This can happen during rolling deployments when multiple pods start\n // up simultaneously. The lock auto-releases when the transaction ends.\n const lockName = `migrate-schema:${schemaName}`;\n await tx`SELECT pg_advisory_xact_lock(hashtext(${lockName}))`;\n\n let versions = await ensureVersionHistory(tx, schemaName);\n\n if (codeVersion < versions.minSafeVersion) {\n throw new Error(\n `Cannot run ${debugName} at schema v${codeVersion} because rollback limit is v${versions.minSafeVersion}`,\n );\n }\n\n if (versions.dataVersion > codeVersion) {\n log.info?.(\n `Data is at v${versions.dataVersion}. Resetting to v${codeVersion}`,\n );\n await updateVersionHistory(log, tx, schemaName, versions, codeVersion);\n return;\n }\n\n if (versions.dataVersion === codeVersion) {\n return;\n }\n\n const migrations =\n versions.dataVersion === 0\n ? // For an empty database (v0), only run the setup migration.\n ([[codeVersion, setupMigration]] as const)\n : versionMigrations;\n\n for (const [dest, migration] of migrations) {\n if (versions.dataVersion < dest) {\n log.info?.(\n `Migrating schema from v${versions.dataVersion} to v${dest}`,\n );\n void log.flush();\n versions = await runMigration(\n log,\n schemaName,\n tx,\n versions,\n dest,\n migration,\n );\n }\n }\n });\n\n log.info?.(`Running ${debugName} at schema v${codeVersion}`);\n } catch (e) {\n if (e instanceof AbortError) {\n // AbortErrors (e.g. AutoResetSignal) are not failures. They are the\n // expected mechanism for signaling that the schema is not initialized\n // (or otherwise needs a reset) so that the caller can backtrack and\n // resync. Logging them at `error` would trip error-based alerting for a\n // normal, self-healing path.\n log.warn?.('Schema requires reset; resync will follow', e);\n } else {\n log.error?.('Error in ensureSchemaMigrated', e);\n }\n throw e;\n } finally {\n void log.flush();\n }\n}\n\nfunction sorted(\n incrementalMigrationMap: IncrementalMigrationMap,\n): [number, Migration][] {\n const versionMigrations: [number, Migration][] = [];\n for (const [v, m] of Object.entries(incrementalMigrationMap)) {\n versionMigrations.push([Number(v), m]);\n }\n return versionMigrations.sort(([a], [b]) => a - b);\n}\n\n// Exposed for tests.\nexport const versionHistory = v.object({\n /**\n * The `schemaVersion` is highest code version that has ever been run\n * on the database, and is used to delineate the structure of the tables\n * in the database. A schemaVersion only moves forward; rolling back to\n * an earlier (safe) code version does not revert schema changes that\n * have already been applied.\n */\n schemaVersion: v.number(),\n\n /**\n * The data version is the code version of the latest server that ran.\n * Note that this may be less than the schemaVersion in the case that\n * a server is rolled back to an earlier version after a schema change.\n * In such a case, data (but not schema), may need to be re-migrated\n * when rolling forward again.\n */\n dataVersion: v.number(),\n\n /**\n * The minimum code version that is safe to run. This is used when\n * a schema migration is not backwards compatible with an older version\n * of the code.\n */\n minSafeVersion: v.number(),\n});\n\n// Exposed for tests.\nexport type VersionHistory = v.Infer<typeof versionHistory>;\n\n// Exposed for tests.\nexport async function createVersionHistoryTable(\n sql: postgres.Sql,\n schemaName: string,\n) {\n // Note: The `lock` column transparently ensures that at most one row exists.\n await sql`\n CREATE SCHEMA IF NOT EXISTS ${sql(schemaName)};\n CREATE TABLE IF NOT EXISTS ${sql(schemaName)}.\"versionHistory\" (\n \"dataVersion\" int NOT NULL,\n \"schemaVersion\" int NOT NULL,\n \"minSafeVersion\" int NOT NULL,\n\n lock char(1) NOT NULL CONSTRAINT DF_schema_meta_lock DEFAULT 'v',\n CONSTRAINT PK_schema_meta_lock PRIMARY KEY (lock),\n CONSTRAINT CK_schema_meta_lock CHECK (lock='v')\n );`.simple();\n}\n\nasync function ensureVersionHistory(\n sql: postgres.Sql,\n schemaName: string,\n): Promise<VersionHistory> {\n return must(await getVersionHistory(sql, schemaName, true));\n}\n\nexport async function getVersionHistory(\n sql: postgres.Sql,\n schemaName: string,\n create = false,\n): Promise<VersionHistory | null> {\n const exists = await sql`\n SELECT nspname, relname FROM pg_class\n JOIN pg_namespace ON relnamespace = pg_namespace.oid\n WHERE nspname = ${schemaName} AND relname = ${'versionHistory'}`;\n\n if (exists.length === 0) {\n if (create) {\n await createVersionHistoryTable(sql, schemaName);\n } else {\n return null;\n }\n }\n const rows = await sql`\n SELECT \"dataVersion\", \"schemaVersion\", \"minSafeVersion\"\n FROM ${sql(schemaName)}.\"versionHistory\"`;\n\n if (rows.length === 0) {\n return create\n ? {schemaVersion: 0, dataVersion: 0, minSafeVersion: 0}\n : null;\n }\n return v.parse(rows[0], versionHistory);\n}\n\nasync function updateVersionHistory(\n log: LogContext,\n sql: postgres.Sql,\n schemaName: string,\n prev: VersionHistory,\n newVersion: number,\n minSafeVersion?: number,\n): Promise<VersionHistory> {\n assert(newVersion > 0, 'newVersion must be positive');\n const versions = {\n dataVersion: newVersion,\n // The schemaVersion never moves backwards.\n schemaVersion: Math.max(newVersion, prev.schemaVersion),\n minSafeVersion: getMinSafeVersion(log, prev, minSafeVersion),\n } satisfies VersionHistory;\n\n await sql`\n INSERT INTO ${sql(schemaName)}.\"versionHistory\" ${sql(versions)}\n ON CONFLICT (lock) DO UPDATE SET ${sql(versions)}\n `;\n return versions;\n}\n\nasync function runMigration(\n log: LogContext,\n schemaName: string,\n tx: PostgresTransaction,\n versions: VersionHistory,\n destinationVersion: number,\n migration: Migration,\n): Promise<VersionHistory> {\n if (versions.schemaVersion < destinationVersion) {\n await migration.migrateSchema?.(log, tx);\n }\n if (versions.dataVersion < destinationVersion) {\n await migration.migrateData?.(log, tx);\n }\n return updateVersionHistory(\n log,\n tx,\n schemaName,\n versions,\n destinationVersion,\n migration.minSafeVersion,\n );\n}\n\n/**\n * Bumps the rollback limit [[toAtLeast]] the specified version.\n * Leaves the rollback limit unchanged if it is equal or greater.\n */\nfunction getMinSafeVersion(\n log: LogContext,\n current: VersionHistory,\n proposedSafeVersion?: number,\n): number {\n if (proposedSafeVersion === undefined) {\n return current.minSafeVersion;\n }\n if (current.minSafeVersion >= proposedSafeVersion) {\n // The rollback limit must never move backwards.\n log.debug?.(\n `rollback limit is already at ${current.minSafeVersion}, ` +\n `don't need to bump to ${proposedSafeVersion}`,\n );\n return current.minSafeVersion;\n }\n log.info?.(\n `bumping rollback limit from ${current.minSafeVersion} to ${proposedSafeVersion}`,\n );\n return proposedSafeVersion;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"migration.js","names":["v","v.parse"],"sources":["../../../../../zero-cache/src/db/migration.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type postgres from 'postgres';\nimport {AbortError} from '../../../shared/src/abort-error.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport {runTx} from './run-transaction.ts';\n\ntype Operations = (log: LogContext, tx: PostgresTransaction) => Promise<void>;\n\n/**\n * Encapsulates the logic for setting up or upgrading to a new schema. After the\n * Migration code successfully completes, {@link runSchemaMigrations}\n * will update the schema version and commit the transaction.\n */\nexport type Migration = {\n /**\n * Perform database operations that create or alter table structure. This is\n * called at most once during lifetime of the application. If a `migrateData()`\n * operation is defined, that will be performed after `migrateSchema()` succeeds.\n */\n migrateSchema?: Operations;\n\n /**\n * Perform database operations to migrate data to the new schema. This is\n * called after `migrateSchema()` (if defined), and may be called again\n * to re-migrate data after the server was rolled back to an earlier version,\n * and rolled forward again.\n *\n * Consequently, the logic in `migrateData()` must be idempotent.\n */\n migrateData?: Operations;\n\n /**\n * Sets the `minSafeVersion` to the specified value, prohibiting running\n * any earlier code versions.\n */\n minSafeVersion?: number;\n};\n\n/**\n * Mapping of incremental migrations to move from the previous old code\n * version to next one. Versions must be non-zero.\n *\n * The schema resulting from performing incremental migrations should be\n * equivalent to that of the `setupMigration` on a blank database.\n *\n * The highest destinationVersion of this map denotes the current\n * \"code version\", and is also used as the destination version when\n * running the initial setup migration on a blank database.\n */\nexport type IncrementalMigrationMap = {\n [destinationVersion: number]: Migration;\n};\n\n/**\n * Ensures that the schema is compatible with the current code, updating and\n * migrating the schema if necessary.\n */\nexport async function runSchemaMigrations(\n log: LogContext,\n debugName: string,\n schemaName: string,\n db: PostgresDB,\n setupMigration: Migration,\n incrementalMigrationMap: IncrementalMigrationMap,\n): Promise<void> {\n log = log.withContext('initSchema', schemaName);\n\n const versionMigrations = sorted(incrementalMigrationMap);\n assert(\n versionMigrations.length,\n `Must specify at least one version migration`,\n );\n assert(\n versionMigrations[0][0] > 0,\n `Versions must be non-zero positive numbers`,\n );\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const codeVersion = versionMigrations.at(-1)![0];\n\n log.info?.(\n `Checking schema for compatibility with ${debugName} at schema v${codeVersion}`,\n );\n\n try {\n await runTx(db, async tx => {\n // Acquire advisory lock to prevent concurrent migrations from racing.\n // This can happen during rolling deployments when multiple pods start\n // up simultaneously. The lock auto-releases when the transaction ends.\n const lockName = `migrate-schema:${schemaName}`;\n await tx`SELECT pg_advisory_xact_lock(hashtext(${lockName}))`;\n\n let versions = await ensureVersionHistory(tx, schemaName);\n\n if (codeVersion < versions.minSafeVersion) {\n throw new Error(\n `Cannot run ${debugName} at schema v${codeVersion} because rollback limit is v${versions.minSafeVersion}`,\n );\n }\n\n if (versions.dataVersion > codeVersion) {\n log.info?.(\n `Data is at v${versions.dataVersion}. Resetting to v${codeVersion}`,\n );\n await updateVersionHistory(log, tx, schemaName, versions, codeVersion);\n return;\n }\n\n if (versions.dataVersion === codeVersion) {\n return;\n }\n\n const migrations =\n versions.dataVersion === 0\n ? // For an empty database (v0), only run the setup migration.\n ([[codeVersion, setupMigration]] as const)\n : versionMigrations;\n\n for (const [dest, migration] of migrations) {\n if (versions.dataVersion < dest) {\n log.info?.(\n `Migrating schema from v${versions.dataVersion} to v${dest}`,\n );\n void log.flush();\n versions = await runMigration(\n log,\n schemaName,\n tx,\n versions,\n dest,\n migration,\n );\n }\n }\n });\n\n log.info?.(`Running ${debugName} at schema v${codeVersion}`);\n } catch (e) {\n if (e instanceof AbortError) {\n // AbortErrors (e.g. AutoResetSignal) are not failures. They are the\n // expected mechanism for signaling that the schema is not initialized\n // (or otherwise needs a reset) so that the caller can backtrack and\n // resync. Logging them at `error` would trip error-based alerting for a\n // normal, self-healing path.\n log.warn?.('Schema requires reset; resync will follow', e);\n } else {\n log.error?.('Error in ensureSchemaMigrated', e);\n }\n throw e;\n } finally {\n void log.flush();\n }\n}\n\nfunction sorted(\n incrementalMigrationMap: IncrementalMigrationMap,\n): [number, Migration][] {\n const versionMigrations: [number, Migration][] = [];\n for (const [v, m] of Object.entries(incrementalMigrationMap)) {\n versionMigrations.push([Number(v), m]);\n }\n return versionMigrations.sort(([a], [b]) => a - b);\n}\n\n// Exposed for tests.\nexport const versionHistory = v.object({\n /**\n * The `schemaVersion` is highest code version that has ever been run\n * on the database, and is used to delineate the structure of the tables\n * in the database. A schemaVersion only moves forward; rolling back to\n * an earlier (safe) code version does not revert schema changes that\n * have already been applied.\n */\n schemaVersion: v.number(),\n\n /**\n * The data version is the code version of the latest server that ran.\n * Note that this may be less than the schemaVersion in the case that\n * a server is rolled back to an earlier version after a schema change.\n * In such a case, data (but not schema), may need to be re-migrated\n * when rolling forward again.\n */\n dataVersion: v.number(),\n\n /**\n * The minimum code version that is safe to run. This is used when\n * a schema migration is not backwards compatible with an older version\n * of the code.\n */\n minSafeVersion: v.number(),\n});\n\n// Exposed for tests.\nexport type VersionHistory = v.Infer<typeof versionHistory>;\n\n// Exposed for tests.\nexport async function createVersionHistoryTable(\n sql: postgres.Sql,\n schemaName: string,\n) {\n // Note: The `lock` column transparently ensures that at most one row exists.\n await sql`\n CREATE SCHEMA IF NOT EXISTS ${sql(schemaName)};\n CREATE TABLE IF NOT EXISTS ${sql(schemaName)}.\"versionHistory\" (\n \"dataVersion\" int NOT NULL,\n \"schemaVersion\" int NOT NULL,\n \"minSafeVersion\" int NOT NULL,\n\n lock char(1) NOT NULL CONSTRAINT DF_schema_meta_lock DEFAULT 'v',\n CONSTRAINT PK_schema_meta_lock PRIMARY KEY (lock),\n CONSTRAINT CK_schema_meta_lock CHECK (lock='v')\n );`.simple();\n}\n\nasync function ensureVersionHistory(\n sql: postgres.Sql,\n schemaName: string,\n): Promise<VersionHistory> {\n return must(await getVersionHistory(sql, schemaName, true));\n}\n\nexport async function getVersionHistory(\n sql: postgres.Sql,\n schemaName: string,\n create = false,\n): Promise<VersionHistory | null> {\n const exists = await sql`\n SELECT nspname, relname FROM pg_class\n JOIN pg_namespace ON relnamespace = pg_namespace.oid\n WHERE nspname = ${schemaName} AND relname = ${'versionHistory'}`;\n\n if (exists.length === 0) {\n if (create) {\n await createVersionHistoryTable(sql, schemaName);\n } else {\n return null;\n }\n }\n const rows = await sql`\n SELECT \"dataVersion\", \"schemaVersion\", \"minSafeVersion\"\n FROM ${sql(schemaName)}.\"versionHistory\"`;\n\n if (rows.length === 0) {\n return create\n ? {schemaVersion: 0, dataVersion: 0, minSafeVersion: 0}\n : null;\n }\n return v.parse(rows[0], versionHistory);\n}\n\nasync function updateVersionHistory(\n log: LogContext,\n sql: postgres.Sql,\n schemaName: string,\n prev: VersionHistory,\n newVersion: number,\n minSafeVersion?: number,\n): Promise<VersionHistory> {\n assert(newVersion > 0, 'newVersion must be positive');\n const versions = {\n dataVersion: newVersion,\n // The schemaVersion never moves backwards.\n schemaVersion: Math.max(newVersion, prev.schemaVersion),\n minSafeVersion: getMinSafeVersion(log, prev, minSafeVersion),\n } satisfies VersionHistory;\n\n await sql`\n INSERT INTO ${sql(schemaName)}.\"versionHistory\" ${sql(versions)}\n ON CONFLICT (lock) DO UPDATE SET ${sql(versions)}\n `;\n return versions;\n}\n\nasync function runMigration(\n log: LogContext,\n schemaName: string,\n tx: PostgresTransaction,\n versions: VersionHistory,\n destinationVersion: number,\n migration: Migration,\n): Promise<VersionHistory> {\n if (versions.schemaVersion < destinationVersion) {\n await migration.migrateSchema?.(log, tx);\n }\n if (versions.dataVersion < destinationVersion) {\n await migration.migrateData?.(log, tx);\n }\n return updateVersionHistory(\n log,\n tx,\n schemaName,\n versions,\n destinationVersion,\n migration.minSafeVersion,\n );\n}\n\n/**\n * Bumps the rollback limit [[toAtLeast]] the specified version.\n * Leaves the rollback limit unchanged if it is equal or greater.\n */\nfunction getMinSafeVersion(\n log: LogContext,\n current: VersionHistory,\n proposedSafeVersion?: number,\n): number {\n if (proposedSafeVersion === undefined) {\n return current.minSafeVersion;\n }\n if (current.minSafeVersion >= proposedSafeVersion) {\n // The rollback limit must never move backwards.\n log.debug?.(\n `rollback limit is already at ${current.minSafeVersion}, ` +\n `don't need to bump to ${proposedSafeVersion}`,\n );\n return current.minSafeVersion;\n }\n log.info?.(\n `bumping rollback limit from ${current.minSafeVersion} to ${proposedSafeVersion}`,\n );\n return proposedSafeVersion;\n}\n"],"mappings":"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"}
@@ -110,7 +110,7 @@ var BinaryCopyParser = class {
110
110
  return true;
111
111
  }
112
112
  };
113
- var KNOWN_BINARY_OIDS = new Set([
113
+ var KNOWN_BINARY_OIDS = /* @__PURE__ */ new Set([
114
114
  16,
115
115
  21,
116
116
  23,
@@ -340,7 +340,8 @@ function decodeArray(buf) {
340
340
  }
341
341
  return arr;
342
342
  }
343
- return stringify(readDimension(0));
343
+ const result = readDimension(0);
344
+ return stringify(result);
344
345
  }
345
346
  /**
346
347
  * Creates a decoder for array elements. Array elements use the same
@@ -376,6 +377,6 @@ function makeElementDecoder(elemOid) {
376
377
  }
377
378
  }
378
379
  //#endregion
379
- export { BinaryCopyParser, hasBinaryDecoder, makeBinaryDecoder, textCastDecoder };
380
+ export { BinaryCopyParser, decodeArray, decodeDate, decodeNumeric, decodeTime, decodeTimeTZ, decodeTimestamp, decodeUUID, hasBinaryDecoder, makeBinaryDecoder, textCastDecoder };
380
381
 
381
382
  //# sourceMappingURL=pg-copy-binary.js.map