@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":"pg-copy-binary.js","names":["#append","#headerParsed","#tryParseHeader","#fieldsRemaining","#remaining","#buffer","#offset","#compact"],"sources":["../../../../../zero-cache/src/db/pg-copy-binary.ts"],"sourcesContent":["import {stringify} from '../../../shared/src/bigint-json.ts';\nimport type {LiteValueType} from '../types/lite.ts';\nimport {\n BOOL,\n BPCHAR,\n BYTEA,\n CHAR,\n DATE,\n FLOAT4,\n FLOAT8,\n INT2,\n INT4,\n INT8,\n JSONB,\n NUMERIC,\n TEXT,\n TIME,\n TIMESTAMP,\n TIMESTAMPTZ,\n TIMETZ,\n UUID,\n VARCHAR,\n} from '../types/pg-types.ts';\nimport {JSON as JSON_OID} from '../types/pg-types.ts';\nimport * as PostgresTypeClass from './postgres-type-class-enum.ts';\nimport type {ColumnSpec} from './specs.ts';\n\n// PostgreSQL COPY binary format signature: \"PGCOPY\\n\\xff\\r\\n\\0\"\nconst PGCOPY_SIGNATURE = Buffer.from([\n 0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00,\n]);\n\nconst HEADER_MIN_SIZE = 11 + 4 + 4; // signature + flags + extension length\n\n// PostgreSQL epoch is 2000-01-01T00:00:00Z.\n// Offset from Unix epoch (1970-01-01) in milliseconds.\nconst PG_EPOCH_UNIX_MILLIS = 946_684_800_000;\n\n// Days from Unix epoch (1970-01-01) to PG epoch (2000-01-01).\nconst PG_EPOCH_UNIX_DAYS = 10_957;\n\nconst MS_PER_DAY = 86_400_000;\n\n// Sentinel values for infinity in PG binary format (as hi/lo int32 pairs).\nconst PG_TIMESTAMP_INF_HI = 0x7fffffff;\nconst PG_TIMESTAMP_INF_LO = 0xffffffff;\nconst PG_TIMESTAMP_NEG_INF_HI = -0x80000000; // readInt32BE of 0x80000000\nconst PG_TIMESTAMP_NEG_INF_LO = 0;\nconst PG_DATE_INFINITY = 0x7fffffff;\nconst PG_DATE_NEG_INFINITY = -0x80000000;\n\n/**\n * Streaming parser for PostgreSQL `COPY ... TO STDOUT WITH (FORMAT binary)`.\n *\n * Analogous to {@link import('./pg-copy.ts').TsvParser} but for binary format.\n * Yields `Buffer | null` per field (null = SQL NULL).\n *\n * The caller tracks column position the same way as with TsvParser.\n */\nexport class BinaryCopyParser {\n #buffer: Buffer = Buffer.alloc(0);\n #offset = 0;\n #headerParsed = false;\n #fieldsRemaining = 0; // fields left in current tuple (0 = need new tuple header)\n\n *parse(chunk: Buffer): Iterable<Buffer | null> {\n this.#append(chunk);\n\n if (!this.#headerParsed) {\n if (!this.#tryParseHeader()) {\n return;\n }\n }\n\n for (;;) {\n // If we're at the start of a tuple, read the field count.\n if (this.#fieldsRemaining === 0) {\n if (this.#remaining() < 2) {\n break;\n }\n const fieldCount = this.#buffer.readInt16BE(this.#offset);\n if (fieldCount === -1) {\n // Trailer marker — end of data.\n break;\n }\n this.#offset += 2;\n this.#fieldsRemaining = fieldCount;\n }\n\n // Parse fields within the current tuple.\n while (this.#fieldsRemaining > 0) {\n if (this.#remaining() < 4) {\n // Not enough data for field length — wait for next chunk.\n this.#compact();\n return;\n }\n const fieldLen = this.#buffer.readInt32BE(this.#offset);\n this.#offset += 4;\n\n if (fieldLen === -1) {\n // NULL field.\n yield null;\n } else {\n if (this.#remaining() < fieldLen) {\n // Not enough data for field value — rewind past the length\n // we just read and wait for more data.\n this.#offset -= 4;\n this.#compact();\n return;\n }\n yield this.#buffer.subarray(this.#offset, this.#offset + fieldLen);\n this.#offset += fieldLen;\n }\n this.#fieldsRemaining--;\n }\n }\n\n this.#compact();\n }\n\n #remaining(): number {\n return this.#buffer.length - this.#offset;\n }\n\n #append(chunk: Buffer): void {\n if (this.#buffer.length === this.#offset) {\n // Fully consumed — replace.\n this.#buffer = chunk;\n this.#offset = 0;\n } else {\n // Concatenate unconsumed remainder with new chunk.\n this.#buffer = Buffer.concat([\n this.#buffer.subarray(this.#offset),\n chunk,\n ]);\n this.#offset = 0;\n }\n }\n\n #compact(): void {\n if (this.#offset > 0) {\n this.#buffer = this.#buffer.subarray(this.#offset);\n this.#offset = 0;\n }\n }\n\n #tryParseHeader(): boolean {\n if (this.#remaining() < HEADER_MIN_SIZE) {\n return false;\n }\n\n // Validate signature.\n for (let i = 0; i < PGCOPY_SIGNATURE.length; i++) {\n if (this.#buffer[this.#offset + i] !== PGCOPY_SIGNATURE[i]) {\n throw new Error('Invalid PGCOPY binary signature');\n }\n }\n this.#offset += 11;\n\n // Flags (int32) — currently only bit 16 (has OID column) is defined.\n // We don't use OID columns, so just skip.\n const flags = this.#buffer.readInt32BE(this.#offset);\n this.#offset += 4;\n if (flags !== 0) {\n throw new Error(`Unsupported PGCOPY flags: ${flags}`);\n }\n\n // Extension area length (int32).\n const extensionLen = this.#buffer.readInt32BE(this.#offset);\n this.#offset += 4;\n\n // Skip extension data if present.\n if (extensionLen > 0) {\n if (this.#remaining() < extensionLen) {\n // Rewind and wait for more data.\n this.#offset -= HEADER_MIN_SIZE;\n return false;\n }\n this.#offset += extensionLen;\n }\n\n this.#headerParsed = true;\n return true;\n }\n}\n\n// ---- Binary Type Decoders ----\n\nexport type BinaryDecoder = (buf: Buffer) => LiteValueType;\n\ntype BinaryColumnSpec = Pick<\n ColumnSpec,\n 'dataType' | 'pgTypeClass' | 'elemPgTypeClass'\n> & {typeOID: number};\n\nconst KNOWN_BINARY_OIDS = new Set([\n BOOL,\n INT2,\n INT4,\n INT8,\n FLOAT4,\n FLOAT8,\n TEXT,\n VARCHAR,\n BPCHAR,\n CHAR,\n UUID,\n BYTEA,\n JSON_OID,\n JSONB,\n TIMESTAMP,\n TIMESTAMPTZ,\n DATE,\n TIME,\n TIMETZ,\n NUMERIC,\n]);\n\n/**\n * Returns true if the column's binary format is known and can be decoded\n * natively. For columns where this returns false, the COPY SELECT should\n * cast the column to `::text` so PG sends the text representation inside\n * the binary frame.\n */\nexport function hasBinaryDecoder(spec: BinaryColumnSpec): boolean {\n if (spec.elemPgTypeClass !== null && spec.elemPgTypeClass !== undefined) {\n return true; // Array types\n }\n if (spec.pgTypeClass === PostgresTypeClass.Enum) {\n return true; // Enums are sent as UTF-8 text in binary format\n }\n return KNOWN_BINARY_OIDS.has(spec.typeOID);\n}\n\n/** Decoder for columns cast to `::text` in the COPY SELECT. */\nexport const textCastDecoder: BinaryDecoder = buf => buf.toString('utf8');\n\n/**\n * Creates a specialized binary decoder for the given column spec.\n * The returned function converts a raw COPY binary field `Buffer`\n * directly to a `LiteValueType`, bypassing text parsing entirely.\n *\n * Only call this for columns where {@link hasBinaryDecoder} returns true.\n * For other columns, cast to `::text` in the SELECT and use\n * {@link textCastDecoder}.\n */\nexport function makeBinaryDecoder(spec: BinaryColumnSpec): BinaryDecoder {\n const {typeOID, pgTypeClass, elemPgTypeClass} = spec;\n\n // Array types: elemPgTypeClass is non-null for arrays.\n if (elemPgTypeClass !== null && elemPgTypeClass !== undefined) {\n return buf => decodeArray(buf);\n }\n\n // Enum types: binary representation is UTF-8 text.\n if (pgTypeClass === PostgresTypeClass.Enum) {\n return buf => buf.toString('utf8');\n }\n\n switch (typeOID) {\n case BOOL:\n return buf => (buf[0] ? 1 : 0);\n case INT2:\n return buf => buf.readInt16BE(0);\n case INT4:\n return buf => buf.readInt32BE(0);\n case INT8:\n return buf => buf.readBigInt64BE(0);\n case FLOAT4:\n return buf => buf.readFloatBE(0);\n case FLOAT8:\n return buf => buf.readDoubleBE(0);\n case TEXT:\n case VARCHAR:\n case BPCHAR:\n case CHAR:\n return buf => buf.toString('utf8');\n case UUID:\n return buf => decodeUUID(buf);\n case BYTEA:\n return buf => Uint8Array.prototype.slice.call(buf) as Uint8Array;\n case JSON_OID:\n return buf => buf.toString('utf8');\n case JSONB:\n // JSONB binary format has a 1-byte version prefix (currently 0x01).\n return buf => buf.toString('utf8', 1);\n case TIMESTAMP:\n case TIMESTAMPTZ:\n return buf => decodeTimestamp(buf);\n case DATE:\n return buf => decodeDate(buf);\n case TIME:\n return buf => decodeTime(buf);\n case TIMETZ:\n return buf => decodeTimeTZ(buf);\n case NUMERIC:\n return buf => decodeNumeric(buf);\n default:\n throw new Error(\n `No binary decoder for type OID ${typeOID}. ` +\n `Use hasBinaryDecoder() to check before calling makeBinaryDecoder().`,\n );\n }\n}\n\n// ---- Individual Decoders (exported for testing) ----\n\n/**\n * UUID: 16 bytes → \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n */\nexport function decodeUUID(buf: Buffer): string {\n const hex = buf.toString('hex');\n return (\n hex.substring(0, 8) +\n '-' +\n hex.substring(8, 12) +\n '-' +\n hex.substring(12, 16) +\n '-' +\n hex.substring(16, 20) +\n '-' +\n hex.substring(20, 32)\n );\n}\n\n/**\n * TIMESTAMP / TIMESTAMPTZ: int64 microseconds since PG epoch (2000-01-01 UTC)\n * → floating-point milliseconds since Unix epoch.\n *\n * Matches the output of `timestampToFpMillis()` in `types/pg.ts`.\n *\n * Uses Number arithmetic (avoiding BigInt) for speed. The microsecond value\n * fits safely in a Number for all practical dates (up to ~year 285,000).\n */\nexport function decodeTimestamp(buf: Buffer): number {\n const hi = buf.readInt32BE(0);\n const lo = buf.readUInt32BE(4);\n if (hi === PG_TIMESTAMP_INF_HI && lo === PG_TIMESTAMP_INF_LO) return Infinity;\n if (hi === PG_TIMESTAMP_NEG_INF_HI && lo === PG_TIMESTAMP_NEG_INF_LO) {\n return -Infinity;\n }\n const microseconds = hi * 0x100000000 + lo;\n return microseconds / 1000 + PG_EPOCH_UNIX_MILLIS;\n}\n\n/**\n * DATE: int32 days since PG epoch (2000-01-01) → millis since Unix epoch at\n * UTC midnight. Matches `dateToUTCMidnight()` in `types/pg.ts`.\n */\nexport function decodeDate(buf: Buffer): number {\n const pgDays = buf.readInt32BE(0);\n if (pgDays === PG_DATE_INFINITY) return Infinity;\n if (pgDays === PG_DATE_NEG_INFINITY) return -Infinity;\n return (pgDays + PG_EPOCH_UNIX_DAYS) * MS_PER_DAY;\n}\n\n/**\n * TIME: int64 microseconds since midnight → milliseconds since midnight.\n * Matches `postgresTimeToMilliseconds()` in `types/pg.ts`.\n *\n * Max value is 86,400,000,000 (~8.6e10), well within Number.MAX_SAFE_INTEGER.\n */\nexport function decodeTime(buf: Buffer): number {\n const hi = buf.readInt32BE(0);\n const lo = buf.readUInt32BE(4);\n const micros = hi * 0x100000000 + lo;\n return Math.trunc(micros / 1000);\n}\n\n/**\n * TIMETZ: int64 microseconds since midnight + int32 timezone offset in seconds.\n * PG stores the offset with inverted sign from ISO (POSIX convention):\n * positive = west of UTC, negative = east of UTC.\n * UTC = local_time + pg_offset.\n * → UTC milliseconds since midnight.\n *\n * Max value ~1.3e11 microseconds, well within Number.MAX_SAFE_INTEGER.\n */\nexport function decodeTimeTZ(buf: Buffer): number {\n const hi = buf.readInt32BE(0);\n const lo = buf.readUInt32BE(4);\n const localMicros = hi * 0x100000000 + lo;\n const tzOffsetSeconds = buf.readInt32BE(8);\n const utcMicros = localMicros + tzOffsetSeconds * 1_000_000;\n let ms = Math.trunc(utcMicros / 1000);\n // Normalize to [0, MS_PER_DAY).\n if (ms < 0 || ms >= MS_PER_DAY) {\n ms = ((ms % MS_PER_DAY) + MS_PER_DAY) % MS_PER_DAY;\n }\n return ms;\n}\n\n// NUMERIC binary format constants.\nconst NUMERIC_NEG = 0x4000;\nconst NUMERIC_NAN = 0xc000;\nconst NUMERIC_PINF = 0xd000;\nconst NUMERIC_NINF = 0xf000;\nconst NBASE = 10_000;\n\n/**\n * NUMERIC: variable-length binary format.\n * Header: {ndigits: int16, weight: int16, sign: int16, dscale: int16}\n * Followed by ndigits x int16 base-10000 digits.\n *\n * Converts to a JS `number` (matching the text path's `Number(x)` behavior).\n */\nexport function decodeNumeric(buf: Buffer): number {\n const ndigits = buf.readInt16BE(0);\n const weight = buf.readInt16BE(2);\n const sign = buf.readUInt16BE(4);\n // const dscale = buf.readInt16BE(6); // display scale, not needed for value\n\n if (sign === NUMERIC_NAN) {\n return NaN;\n }\n if (sign === NUMERIC_PINF) {\n return Infinity;\n }\n if (sign === NUMERIC_NINF) {\n return -Infinity;\n }\n if (ndigits === 0) {\n return 0;\n }\n\n // Accumulate base-10000 digits into an integer, then do a single\n // division at the end. Repeated `scale /= NBASE` accumulates\n // floating-point error (e.g. 9900 * 0.0001 = 0.9900000000000001).\n // A single division lets IEEE 754 round to the nearest double,\n // matching the text path's `Number(\"0.99\")` behavior.\n //\n // For numerics with many digits (ndigits > 3), intVal can exceed\n // MAX_SAFE_INTEGER. In that case, fall back to building a string\n // and using Number() to match the text path exactly.\n if (ndigits > 3) {\n return decodeNumericViaString(buf, ndigits, weight, sign);\n }\n\n let intVal = 0;\n for (let i = 0; i < ndigits; i++) {\n intVal = intVal * NBASE + buf.readInt16BE(8 + i * 2);\n }\n\n // weight indicates the power-of-NBASE of the first digit.\n // shift is how many base-10000 positions to divide by.\n const shift = ndigits - weight - 1;\n let result;\n if (shift > 0) {\n result = intVal / NBASE ** shift;\n } else if (shift < 0) {\n result = intVal * NBASE ** -shift;\n } else {\n result = intVal;\n }\n return sign === NUMERIC_NEG ? -result : result;\n}\n\n/**\n * Fallback for numerics with many base-10000 digits where accumulating\n * into an integer would exceed MAX_SAFE_INTEGER. Builds the decimal\n * string and uses Number() to match the text path exactly.\n */\nfunction decodeNumericViaString(\n buf: Buffer,\n ndigits: number,\n weight: number,\n sign: number,\n): number {\n // Number of base-10000 digit groups before the decimal point.\n const intGroups = weight + 1;\n\n let str = '';\n for (let i = 0; i < ndigits; i++) {\n const digit = buf.readInt16BE(8 + i * 2);\n if (i === intGroups) {\n str = str || '0';\n str += '.';\n }\n str += i === 0 ? String(digit) : String(digit).padStart(4, '0');\n }\n\n // Append trailing zero groups if the integer part extends beyond ndigits.\n if (intGroups > ndigits) {\n str += '0'.repeat((intGroups - ndigits) * 4);\n }\n\n return Number((sign === NUMERIC_NEG ? '-' : '') + str);\n}\n\n/**\n * Array: binary format.\n *\n * Header:\n * int32 ndim — number of dimensions (0 for empty array)\n * int32 flags — 0 or 1 (has-nulls)\n * int32 elem_oid — OID of element type\n * Per dimension:\n * int32 dim_size — number of elements in this dimension\n * int32 dim_lb — lower bound (usually 1)\n *\n * Then for each element (in row-major order):\n * int32 length — -1 for NULL, otherwise byte length\n * bytes — element data\n *\n * Result is JSON.stringify'd for storage in SQLite (matching text path behavior).\n */\nexport function decodeArray(buf: Buffer): string {\n let offset = 0;\n\n const ndim = buf.readInt32BE(offset);\n offset += 4;\n // skip flags (has-nulls)\n offset += 4;\n const elemOid = buf.readInt32BE(offset);\n offset += 4;\n\n if (ndim === 0) {\n return '[]';\n }\n\n // Read dimension sizes.\n const dims: number[] = [];\n for (let d = 0; d < ndim; d++) {\n dims.push(buf.readInt32BE(offset));\n offset += 4;\n // skip lower bound\n offset += 4;\n }\n\n const elemDecoder = makeElementDecoder(elemOid);\n\n // Recursively build the nested array structure.\n function readDimension(dim: number): unknown[] {\n const size = dims[dim];\n const arr: unknown[] = [];\n for (let i = 0; i < size; i++) {\n if (dim < ndim - 1) {\n arr.push(readDimension(dim + 1));\n } else {\n // Leaf dimension — read element.\n const elemLen = buf.readInt32BE(offset);\n offset += 4;\n if (elemLen === -1) {\n arr.push(null);\n } else {\n arr.push(elemDecoder(buf.subarray(offset, offset + elemLen)));\n offset += elemLen;\n }\n }\n }\n return arr;\n }\n\n const result = readDimension(0);\n return stringify(result);\n}\n\n/**\n * Creates a decoder for array elements. Array elements use the same\n * binary encoding as scalar columns, but we need to map the element\n * OID to the right decoder. Returns JS values (not LiteValueType)\n * since the result will be JSON.stringify'd.\n */\nfunction makeElementDecoder(elemOid: number): (buf: Buffer) => unknown {\n switch (elemOid) {\n case BOOL:\n return buf => (buf[0] ? true : false);\n case INT2:\n return buf => buf.readInt16BE(0);\n case INT4:\n return buf => buf.readInt32BE(0);\n case INT8:\n return buf => {\n const val = buf.readBigInt64BE(0);\n // Use number if it fits safely, otherwise bigint for JSON.\n return val >= Number.MIN_SAFE_INTEGER && val <= Number.MAX_SAFE_INTEGER\n ? Number(val)\n : val;\n };\n case FLOAT4:\n return buf => buf.readFloatBE(0);\n case FLOAT8:\n return buf => buf.readDoubleBE(0);\n case TEXT:\n case VARCHAR:\n case BPCHAR:\n case CHAR:\n return buf => buf.toString('utf8');\n case UUID:\n return buf => decodeUUID(buf);\n case JSON_OID:\n return buf => JSON.parse(buf.toString('utf8'));\n case JSONB:\n return buf => JSON.parse(buf.toString('utf8', 1));\n case TIMESTAMP:\n case TIMESTAMPTZ:\n return buf => decodeTimestamp(buf);\n case DATE:\n return buf => decodeDate(buf);\n case TIME:\n return buf => decodeTime(buf);\n case TIMETZ:\n return buf => decodeTimeTZ(buf);\n case NUMERIC:\n return buf => decodeNumeric(buf);\n default:\n return buf => buf.toString('utf8');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pg-copy-binary.js","names":[],"sources":["../../../../../zero-cache/src/db/pg-copy-binary.ts"],"sourcesContent":["import {stringify} from '../../../shared/src/bigint-json.ts';\nimport type {LiteValueType} from '../types/lite.ts';\nimport {\n BOOL,\n BPCHAR,\n BYTEA,\n CHAR,\n DATE,\n FLOAT4,\n FLOAT8,\n INT2,\n INT4,\n INT8,\n JSONB,\n NUMERIC,\n TEXT,\n TIME,\n TIMESTAMP,\n TIMESTAMPTZ,\n TIMETZ,\n UUID,\n VARCHAR,\n} from '../types/pg-types.ts';\nimport {JSON as JSON_OID} from '../types/pg-types.ts';\nimport * as PostgresTypeClass from './postgres-type-class-enum.ts';\nimport type {ColumnSpec} from './specs.ts';\n\n// PostgreSQL COPY binary format signature: \"PGCOPY\\n\\xff\\r\\n\\0\"\nconst PGCOPY_SIGNATURE = Buffer.from([\n 0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00,\n]);\n\nconst HEADER_MIN_SIZE = 11 + 4 + 4; // signature + flags + extension length\n\n// PostgreSQL epoch is 2000-01-01T00:00:00Z.\n// Offset from Unix epoch (1970-01-01) in milliseconds.\nconst PG_EPOCH_UNIX_MILLIS = 946_684_800_000;\n\n// Days from Unix epoch (1970-01-01) to PG epoch (2000-01-01).\nconst PG_EPOCH_UNIX_DAYS = 10_957;\n\nconst MS_PER_DAY = 86_400_000;\n\n// Sentinel values for infinity in PG binary format (as hi/lo int32 pairs).\nconst PG_TIMESTAMP_INF_HI = 0x7fffffff;\nconst PG_TIMESTAMP_INF_LO = 0xffffffff;\nconst PG_TIMESTAMP_NEG_INF_HI = -0x80000000; // readInt32BE of 0x80000000\nconst PG_TIMESTAMP_NEG_INF_LO = 0;\nconst PG_DATE_INFINITY = 0x7fffffff;\nconst PG_DATE_NEG_INFINITY = -0x80000000;\n\n/**\n * Streaming parser for PostgreSQL `COPY ... TO STDOUT WITH (FORMAT binary)`.\n *\n * Analogous to {@link import('./pg-copy.ts').TsvParser} but for binary format.\n * Yields `Buffer | null` per field (null = SQL NULL).\n *\n * The caller tracks column position the same way as with TsvParser.\n */\nexport class BinaryCopyParser {\n #buffer: Buffer = Buffer.alloc(0);\n #offset = 0;\n #headerParsed = false;\n #fieldsRemaining = 0; // fields left in current tuple (0 = need new tuple header)\n\n *parse(chunk: Buffer): Iterable<Buffer | null> {\n this.#append(chunk);\n\n if (!this.#headerParsed) {\n if (!this.#tryParseHeader()) {\n return;\n }\n }\n\n for (;;) {\n // If we're at the start of a tuple, read the field count.\n if (this.#fieldsRemaining === 0) {\n if (this.#remaining() < 2) {\n break;\n }\n const fieldCount = this.#buffer.readInt16BE(this.#offset);\n if (fieldCount === -1) {\n // Trailer marker — end of data.\n break;\n }\n this.#offset += 2;\n this.#fieldsRemaining = fieldCount;\n }\n\n // Parse fields within the current tuple.\n while (this.#fieldsRemaining > 0) {\n if (this.#remaining() < 4) {\n // Not enough data for field length — wait for next chunk.\n this.#compact();\n return;\n }\n const fieldLen = this.#buffer.readInt32BE(this.#offset);\n this.#offset += 4;\n\n if (fieldLen === -1) {\n // NULL field.\n yield null;\n } else {\n if (this.#remaining() < fieldLen) {\n // Not enough data for field value — rewind past the length\n // we just read and wait for more data.\n this.#offset -= 4;\n this.#compact();\n return;\n }\n yield this.#buffer.subarray(this.#offset, this.#offset + fieldLen);\n this.#offset += fieldLen;\n }\n this.#fieldsRemaining--;\n }\n }\n\n this.#compact();\n }\n\n #remaining(): number {\n return this.#buffer.length - this.#offset;\n }\n\n #append(chunk: Buffer): void {\n if (this.#buffer.length === this.#offset) {\n // Fully consumed — replace.\n this.#buffer = chunk;\n this.#offset = 0;\n } else {\n // Concatenate unconsumed remainder with new chunk.\n this.#buffer = Buffer.concat([\n this.#buffer.subarray(this.#offset),\n chunk,\n ]);\n this.#offset = 0;\n }\n }\n\n #compact(): void {\n if (this.#offset > 0) {\n this.#buffer = this.#buffer.subarray(this.#offset);\n this.#offset = 0;\n }\n }\n\n #tryParseHeader(): boolean {\n if (this.#remaining() < HEADER_MIN_SIZE) {\n return false;\n }\n\n // Validate signature.\n for (let i = 0; i < PGCOPY_SIGNATURE.length; i++) {\n if (this.#buffer[this.#offset + i] !== PGCOPY_SIGNATURE[i]) {\n throw new Error('Invalid PGCOPY binary signature');\n }\n }\n this.#offset += 11;\n\n // Flags (int32) — currently only bit 16 (has OID column) is defined.\n // We don't use OID columns, so just skip.\n const flags = this.#buffer.readInt32BE(this.#offset);\n this.#offset += 4;\n if (flags !== 0) {\n throw new Error(`Unsupported PGCOPY flags: ${flags}`);\n }\n\n // Extension area length (int32).\n const extensionLen = this.#buffer.readInt32BE(this.#offset);\n this.#offset += 4;\n\n // Skip extension data if present.\n if (extensionLen > 0) {\n if (this.#remaining() < extensionLen) {\n // Rewind and wait for more data.\n this.#offset -= HEADER_MIN_SIZE;\n return false;\n }\n this.#offset += extensionLen;\n }\n\n this.#headerParsed = true;\n return true;\n }\n}\n\n// ---- Binary Type Decoders ----\n\nexport type BinaryDecoder = (buf: Buffer) => LiteValueType;\n\ntype BinaryColumnSpec = Pick<\n ColumnSpec,\n 'dataType' | 'pgTypeClass' | 'elemPgTypeClass'\n> & {typeOID: number};\n\nconst KNOWN_BINARY_OIDS = new Set([\n BOOL,\n INT2,\n INT4,\n INT8,\n FLOAT4,\n FLOAT8,\n TEXT,\n VARCHAR,\n BPCHAR,\n CHAR,\n UUID,\n BYTEA,\n JSON_OID,\n JSONB,\n TIMESTAMP,\n TIMESTAMPTZ,\n DATE,\n TIME,\n TIMETZ,\n NUMERIC,\n]);\n\n/**\n * Returns true if the column's binary format is known and can be decoded\n * natively. For columns where this returns false, the COPY SELECT should\n * cast the column to `::text` so PG sends the text representation inside\n * the binary frame.\n */\nexport function hasBinaryDecoder(spec: BinaryColumnSpec): boolean {\n if (spec.elemPgTypeClass !== null && spec.elemPgTypeClass !== undefined) {\n return true; // Array types\n }\n if (spec.pgTypeClass === PostgresTypeClass.Enum) {\n return true; // Enums are sent as UTF-8 text in binary format\n }\n return KNOWN_BINARY_OIDS.has(spec.typeOID);\n}\n\n/** Decoder for columns cast to `::text` in the COPY SELECT. */\nexport const textCastDecoder: BinaryDecoder = buf => buf.toString('utf8');\n\n/**\n * Creates a specialized binary decoder for the given column spec.\n * The returned function converts a raw COPY binary field `Buffer`\n * directly to a `LiteValueType`, bypassing text parsing entirely.\n *\n * Only call this for columns where {@link hasBinaryDecoder} returns true.\n * For other columns, cast to `::text` in the SELECT and use\n * {@link textCastDecoder}.\n */\nexport function makeBinaryDecoder(spec: BinaryColumnSpec): BinaryDecoder {\n const {typeOID, pgTypeClass, elemPgTypeClass} = spec;\n\n // Array types: elemPgTypeClass is non-null for arrays.\n if (elemPgTypeClass !== null && elemPgTypeClass !== undefined) {\n return buf => decodeArray(buf);\n }\n\n // Enum types: binary representation is UTF-8 text.\n if (pgTypeClass === PostgresTypeClass.Enum) {\n return buf => buf.toString('utf8');\n }\n\n switch (typeOID) {\n case BOOL:\n return buf => (buf[0] ? 1 : 0);\n case INT2:\n return buf => buf.readInt16BE(0);\n case INT4:\n return buf => buf.readInt32BE(0);\n case INT8:\n return buf => buf.readBigInt64BE(0);\n case FLOAT4:\n return buf => buf.readFloatBE(0);\n case FLOAT8:\n return buf => buf.readDoubleBE(0);\n case TEXT:\n case VARCHAR:\n case BPCHAR:\n case CHAR:\n return buf => buf.toString('utf8');\n case UUID:\n return buf => decodeUUID(buf);\n case BYTEA:\n return buf => Uint8Array.prototype.slice.call(buf) as Uint8Array;\n case JSON_OID:\n return buf => buf.toString('utf8');\n case JSONB:\n // JSONB binary format has a 1-byte version prefix (currently 0x01).\n return buf => buf.toString('utf8', 1);\n case TIMESTAMP:\n case TIMESTAMPTZ:\n return buf => decodeTimestamp(buf);\n case DATE:\n return buf => decodeDate(buf);\n case TIME:\n return buf => decodeTime(buf);\n case TIMETZ:\n return buf => decodeTimeTZ(buf);\n case NUMERIC:\n return buf => decodeNumeric(buf);\n default:\n throw new Error(\n `No binary decoder for type OID ${typeOID}. ` +\n `Use hasBinaryDecoder() to check before calling makeBinaryDecoder().`,\n );\n }\n}\n\n// ---- Individual Decoders (exported for testing) ----\n\n/**\n * UUID: 16 bytes → \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n */\nexport function decodeUUID(buf: Buffer): string {\n const hex = buf.toString('hex');\n return (\n hex.substring(0, 8) +\n '-' +\n hex.substring(8, 12) +\n '-' +\n hex.substring(12, 16) +\n '-' +\n hex.substring(16, 20) +\n '-' +\n hex.substring(20, 32)\n );\n}\n\n/**\n * TIMESTAMP / TIMESTAMPTZ: int64 microseconds since PG epoch (2000-01-01 UTC)\n * → floating-point milliseconds since Unix epoch.\n *\n * Matches the output of `timestampToFpMillis()` in `types/pg.ts`.\n *\n * Uses Number arithmetic (avoiding BigInt) for speed. The microsecond value\n * fits safely in a Number for all practical dates (up to ~year 285,000).\n */\nexport function decodeTimestamp(buf: Buffer): number {\n const hi = buf.readInt32BE(0);\n const lo = buf.readUInt32BE(4);\n if (hi === PG_TIMESTAMP_INF_HI && lo === PG_TIMESTAMP_INF_LO) return Infinity;\n if (hi === PG_TIMESTAMP_NEG_INF_HI && lo === PG_TIMESTAMP_NEG_INF_LO) {\n return -Infinity;\n }\n const microseconds = hi * 0x100000000 + lo;\n return microseconds / 1000 + PG_EPOCH_UNIX_MILLIS;\n}\n\n/**\n * DATE: int32 days since PG epoch (2000-01-01) → millis since Unix epoch at\n * UTC midnight. Matches `dateToUTCMidnight()` in `types/pg.ts`.\n */\nexport function decodeDate(buf: Buffer): number {\n const pgDays = buf.readInt32BE(0);\n if (pgDays === PG_DATE_INFINITY) return Infinity;\n if (pgDays === PG_DATE_NEG_INFINITY) return -Infinity;\n return (pgDays + PG_EPOCH_UNIX_DAYS) * MS_PER_DAY;\n}\n\n/**\n * TIME: int64 microseconds since midnight → milliseconds since midnight.\n * Matches `postgresTimeToMilliseconds()` in `types/pg.ts`.\n *\n * Max value is 86,400,000,000 (~8.6e10), well within Number.MAX_SAFE_INTEGER.\n */\nexport function decodeTime(buf: Buffer): number {\n const hi = buf.readInt32BE(0);\n const lo = buf.readUInt32BE(4);\n const micros = hi * 0x100000000 + lo;\n return Math.trunc(micros / 1000);\n}\n\n/**\n * TIMETZ: int64 microseconds since midnight + int32 timezone offset in seconds.\n * PG stores the offset with inverted sign from ISO (POSIX convention):\n * positive = west of UTC, negative = east of UTC.\n * UTC = local_time + pg_offset.\n * → UTC milliseconds since midnight.\n *\n * Max value ~1.3e11 microseconds, well within Number.MAX_SAFE_INTEGER.\n */\nexport function decodeTimeTZ(buf: Buffer): number {\n const hi = buf.readInt32BE(0);\n const lo = buf.readUInt32BE(4);\n const localMicros = hi * 0x100000000 + lo;\n const tzOffsetSeconds = buf.readInt32BE(8);\n const utcMicros = localMicros + tzOffsetSeconds * 1_000_000;\n let ms = Math.trunc(utcMicros / 1000);\n // Normalize to [0, MS_PER_DAY).\n if (ms < 0 || ms >= MS_PER_DAY) {\n ms = ((ms % MS_PER_DAY) + MS_PER_DAY) % MS_PER_DAY;\n }\n return ms;\n}\n\n// NUMERIC binary format constants.\nconst NUMERIC_NEG = 0x4000;\nconst NUMERIC_NAN = 0xc000;\nconst NUMERIC_PINF = 0xd000;\nconst NUMERIC_NINF = 0xf000;\nconst NBASE = 10_000;\n\n/**\n * NUMERIC: variable-length binary format.\n * Header: {ndigits: int16, weight: int16, sign: int16, dscale: int16}\n * Followed by ndigits x int16 base-10000 digits.\n *\n * Converts to a JS `number` (matching the text path's `Number(x)` behavior).\n */\nexport function decodeNumeric(buf: Buffer): number {\n const ndigits = buf.readInt16BE(0);\n const weight = buf.readInt16BE(2);\n const sign = buf.readUInt16BE(4);\n // const dscale = buf.readInt16BE(6); // display scale, not needed for value\n\n if (sign === NUMERIC_NAN) {\n return NaN;\n }\n if (sign === NUMERIC_PINF) {\n return Infinity;\n }\n if (sign === NUMERIC_NINF) {\n return -Infinity;\n }\n if (ndigits === 0) {\n return 0;\n }\n\n // Accumulate base-10000 digits into an integer, then do a single\n // division at the end. Repeated `scale /= NBASE` accumulates\n // floating-point error (e.g. 9900 * 0.0001 = 0.9900000000000001).\n // A single division lets IEEE 754 round to the nearest double,\n // matching the text path's `Number(\"0.99\")` behavior.\n //\n // For numerics with many digits (ndigits > 3), intVal can exceed\n // MAX_SAFE_INTEGER. In that case, fall back to building a string\n // and using Number() to match the text path exactly.\n if (ndigits > 3) {\n return decodeNumericViaString(buf, ndigits, weight, sign);\n }\n\n let intVal = 0;\n for (let i = 0; i < ndigits; i++) {\n intVal = intVal * NBASE + buf.readInt16BE(8 + i * 2);\n }\n\n // weight indicates the power-of-NBASE of the first digit.\n // shift is how many base-10000 positions to divide by.\n const shift = ndigits - weight - 1;\n let result;\n if (shift > 0) {\n result = intVal / NBASE ** shift;\n } else if (shift < 0) {\n result = intVal * NBASE ** -shift;\n } else {\n result = intVal;\n }\n return sign === NUMERIC_NEG ? -result : result;\n}\n\n/**\n * Fallback for numerics with many base-10000 digits where accumulating\n * into an integer would exceed MAX_SAFE_INTEGER. Builds the decimal\n * string and uses Number() to match the text path exactly.\n */\nfunction decodeNumericViaString(\n buf: Buffer,\n ndigits: number,\n weight: number,\n sign: number,\n): number {\n // Number of base-10000 digit groups before the decimal point.\n const intGroups = weight + 1;\n\n let str = '';\n for (let i = 0; i < ndigits; i++) {\n const digit = buf.readInt16BE(8 + i * 2);\n if (i === intGroups) {\n str = str || '0';\n str += '.';\n }\n str += i === 0 ? String(digit) : String(digit).padStart(4, '0');\n }\n\n // Append trailing zero groups if the integer part extends beyond ndigits.\n if (intGroups > ndigits) {\n str += '0'.repeat((intGroups - ndigits) * 4);\n }\n\n return Number((sign === NUMERIC_NEG ? '-' : '') + str);\n}\n\n/**\n * Array: binary format.\n *\n * Header:\n * int32 ndim — number of dimensions (0 for empty array)\n * int32 flags — 0 or 1 (has-nulls)\n * int32 elem_oid — OID of element type\n * Per dimension:\n * int32 dim_size — number of elements in this dimension\n * int32 dim_lb — lower bound (usually 1)\n *\n * Then for each element (in row-major order):\n * int32 length — -1 for NULL, otherwise byte length\n * bytes — element data\n *\n * Result is JSON.stringify'd for storage in SQLite (matching text path behavior).\n */\nexport function decodeArray(buf: Buffer): string {\n let offset = 0;\n\n const ndim = buf.readInt32BE(offset);\n offset += 4;\n // skip flags (has-nulls)\n offset += 4;\n const elemOid = buf.readInt32BE(offset);\n offset += 4;\n\n if (ndim === 0) {\n return '[]';\n }\n\n // Read dimension sizes.\n const dims: number[] = [];\n for (let d = 0; d < ndim; d++) {\n dims.push(buf.readInt32BE(offset));\n offset += 4;\n // skip lower bound\n offset += 4;\n }\n\n const elemDecoder = makeElementDecoder(elemOid);\n\n // Recursively build the nested array structure.\n function readDimension(dim: number): unknown[] {\n const size = dims[dim];\n const arr: unknown[] = [];\n for (let i = 0; i < size; i++) {\n if (dim < ndim - 1) {\n arr.push(readDimension(dim + 1));\n } else {\n // Leaf dimension — read element.\n const elemLen = buf.readInt32BE(offset);\n offset += 4;\n if (elemLen === -1) {\n arr.push(null);\n } else {\n arr.push(elemDecoder(buf.subarray(offset, offset + elemLen)));\n offset += elemLen;\n }\n }\n }\n return arr;\n }\n\n const result = readDimension(0);\n return stringify(result);\n}\n\n/**\n * Creates a decoder for array elements. Array elements use the same\n * binary encoding as scalar columns, but we need to map the element\n * OID to the right decoder. Returns JS values (not LiteValueType)\n * since the result will be JSON.stringify'd.\n */\nfunction makeElementDecoder(elemOid: number): (buf: Buffer) => unknown {\n switch (elemOid) {\n case BOOL:\n return buf => (buf[0] ? true : false);\n case INT2:\n return buf => buf.readInt16BE(0);\n case INT4:\n return buf => buf.readInt32BE(0);\n case INT8:\n return buf => {\n const val = buf.readBigInt64BE(0);\n // Use number if it fits safely, otherwise bigint for JSON.\n return val >= Number.MIN_SAFE_INTEGER && val <= Number.MAX_SAFE_INTEGER\n ? Number(val)\n : val;\n };\n case FLOAT4:\n return buf => buf.readFloatBE(0);\n case FLOAT8:\n return buf => buf.readDoubleBE(0);\n case TEXT:\n case VARCHAR:\n case BPCHAR:\n case CHAR:\n return buf => buf.toString('utf8');\n case UUID:\n return buf => decodeUUID(buf);\n case JSON_OID:\n return buf => JSON.parse(buf.toString('utf8'));\n case JSONB:\n return buf => JSON.parse(buf.toString('utf8', 1));\n case TIMESTAMP:\n case TIMESTAMPTZ:\n return buf => decodeTimestamp(buf);\n case DATE:\n return buf => decodeDate(buf);\n case TIME:\n return buf => decodeTime(buf);\n case TIMETZ:\n return buf => decodeTimeTZ(buf);\n case NUMERIC:\n return buf => decodeNumeric(buf);\n default:\n return buf => buf.toString('utf8');\n }\n}\n"],"mappings":"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"}
@@ -36,7 +36,6 @@ var TsvParser = class {
36
36
  l = r + 1;
37
37
  yield this.#currVal === "\0" ? null : this.#currVal;
38
38
  this.#currVal = "";
39
- break;
40
39
  }
41
40
  }
42
41
  l < r && (this.#currVal += chunk.toString("utf8", l, r));
@@ -1 +1 @@
1
- {"version":3,"file":"pg-copy.js","names":["#parser","#escaped","#currVal"],"sources":["../../../../../zero-cache/src/db/pg-copy.ts"],"sourcesContent":["import {Transform} from 'node:stream';\n\n/**\n * A stream Transform that parses a Postgres `COPY ... TO` text stream into\n * individual text values. The special {@link NULL_BYTE} string is used to\n * indicate a `null` value (as the `null` value itself indicates the end of\n * the stream and cannot be pushed as an element).\n *\n * Note that the transform assumes that the next step of the pipeline\n * understands the cardinality of values per row and does not push any\n * special value when reaching the end of a row.\n */\nexport class TextTransform extends Transform {\n readonly #parser = new TsvParser();\n\n constructor() {\n super({objectMode: true});\n }\n\n _transform(\n chunk: Buffer,\n _encoding: BufferEncoding,\n callback: (e?: Error) => void,\n ) {\n try {\n for (const value of this.#parser.parse(chunk)) {\n this.push(value === null ? NULL_BYTE : value);\n }\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n }\n}\n\n/**\n * Parsing a stream of tab-separated values from a Postgres `COPY` command.\n * The object keeps state and should be reused across chunks of a stream in\n * order to properly recognize that values are split across chunks.\n *\n * Note that `null` values are yielded as `null`. This object does not return\n * the {@link NULL_BYTE} string.\n */\nexport class TsvParser {\n #currVal: string = '';\n #escaped = false;\n\n *parse(chunk: Buffer): Iterable<string | null> {\n let l = 0;\n let r = 0;\n\n for (; r < chunk.length; r++) {\n const ch = chunk[r];\n if (this.#escaped) {\n const escapedChar = ESCAPED_CHARACTERS[ch];\n if (escapedChar === undefined) {\n throw new Error(\n `Unexpected escape character \\\\${String.fromCharCode(ch)}`,\n );\n }\n this.#currVal += escapedChar;\n l = r + 1;\n this.#escaped = false;\n continue;\n }\n switch (ch) {\n case 0x5c: // '\\'\n // flush segment\n l < r && (this.#currVal += chunk.toString('utf8', l, r));\n l = r + 1;\n this.#escaped = true;\n break;\n\n case 0x09: // '\\t'\n case 0x0a: // '\\n'\n // flush segment\n l < r && (this.#currVal += chunk.toString('utf8', l, r));\n l = r + 1;\n\n // Value is done in both cases.\n yield this.#currVal === NULL_BYTE ? null : this.#currVal;\n this.#currVal = '';\n break;\n }\n }\n // flush segment\n l < r && (this.#currVal += chunk.toString('utf8', l, r));\n }\n}\n\n// The lone NULL byte signifies that the column value is `null`.\n// (Postgres does not permit NULL bytes in TEXT values).\n//\n// Note that although NULL bytes can appear in JSON strings,\n// those will always be represented within double-quotes,\n// and thus never as a lone NULL byte.\nexport const NULL_BYTE = '\\u0000';\n\n// escaped characters used in https://www.postgresql.org/docs/current/sql-copy.html\nconst ESCAPED_CHARACTERS: Record<number, string | undefined> = {\n 0x4e: NULL_BYTE, // \\N signifies the NULL character.\n 0x5c: '\\\\',\n 0x62: '\\b',\n 0x66: '\\f',\n 0x6e: '\\n',\n 0x72: '\\r',\n 0x74: '\\t',\n 0x76: '\\v',\n} as const;\n"],"mappings":";;;;;;;;;;AA2CA,IAAa,YAAb,MAAuB;CACrB,WAAmB;CACnB,WAAW;CAEX,CAAC,MAAM,OAAwC;EAC7C,IAAI,IAAI;EACR,IAAI,IAAI;EAER,OAAO,IAAI,MAAM,QAAQ,KAAK;GAC5B,MAAM,KAAK,MAAM;GACjB,IAAI,KAAKC,UAAU;IACjB,MAAM,cAAc,mBAAmB;IACvC,IAAI,gBAAgB,KAAA,GAClB,MAAM,IAAI,MACR,iCAAiC,OAAO,aAAa,EAAE,GACzD;IAEF,KAAKC,YAAY;IACjB,IAAI,IAAI;IACR,KAAKD,WAAW;IAChB;GACF;GACA,QAAQ,IAAR;IACE,KAAK;KAEH,IAAI,MAAM,KAAKC,YAAY,MAAM,SAAS,QAAQ,GAAG,CAAC;KACtD,IAAI,IAAI;KACR,KAAKD,WAAW;KAChB;IAEF,KAAK;IACL,KAAK;KAEH,IAAI,MAAM,KAAKC,YAAY,MAAM,SAAS,QAAQ,GAAG,CAAC;KACtD,IAAI,IAAI;KAGR,MAAM,KAAKA,aAAAA,OAAyB,OAAO,KAAKA;KAChD,KAAKA,WAAW;KAChB;GACJ;EACF;EAEA,IAAI,MAAM,KAAKA,YAAY,MAAM,SAAS,QAAQ,GAAG,CAAC;CACxD;AACF;AAWA,IAAM,qBAAyD;CAC7D,IAAA;CACA,IAAM;CACN,IAAM;CACN,KAAM;CACN,KAAM;CACN,KAAM;CACN,KAAM;CACN,KAAM;AACR"}
1
+ {"version":3,"file":"pg-copy.js","names":[],"sources":["../../../../../zero-cache/src/db/pg-copy.ts"],"sourcesContent":["import {Transform} from 'node:stream';\n\n/**\n * A stream Transform that parses a Postgres `COPY ... TO` text stream into\n * individual text values. The special {@link NULL_BYTE} string is used to\n * indicate a `null` value (as the `null` value itself indicates the end of\n * the stream and cannot be pushed as an element).\n *\n * Note that the transform assumes that the next step of the pipeline\n * understands the cardinality of values per row and does not push any\n * special value when reaching the end of a row.\n */\nexport class TextTransform extends Transform {\n readonly #parser = new TsvParser();\n\n constructor() {\n super({objectMode: true});\n }\n\n _transform(\n chunk: Buffer,\n _encoding: BufferEncoding,\n callback: (e?: Error) => void,\n ) {\n try {\n for (const value of this.#parser.parse(chunk)) {\n this.push(value === null ? NULL_BYTE : value);\n }\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n }\n}\n\n/**\n * Parsing a stream of tab-separated values from a Postgres `COPY` command.\n * The object keeps state and should be reused across chunks of a stream in\n * order to properly recognize that values are split across chunks.\n *\n * Note that `null` values are yielded as `null`. This object does not return\n * the {@link NULL_BYTE} string.\n */\nexport class TsvParser {\n #currVal: string = '';\n #escaped = false;\n\n *parse(chunk: Buffer): Iterable<string | null> {\n let l = 0;\n let r = 0;\n\n for (; r < chunk.length; r++) {\n const ch = chunk[r];\n if (this.#escaped) {\n const escapedChar = ESCAPED_CHARACTERS[ch];\n if (escapedChar === undefined) {\n throw new Error(\n `Unexpected escape character \\\\${String.fromCharCode(ch)}`,\n );\n }\n this.#currVal += escapedChar;\n l = r + 1;\n this.#escaped = false;\n continue;\n }\n switch (ch) {\n case 0x5c: // '\\'\n // flush segment\n l < r && (this.#currVal += chunk.toString('utf8', l, r));\n l = r + 1;\n this.#escaped = true;\n break;\n\n case 0x09: // '\\t'\n case 0x0a: // '\\n'\n // flush segment\n l < r && (this.#currVal += chunk.toString('utf8', l, r));\n l = r + 1;\n\n // Value is done in both cases.\n yield this.#currVal === NULL_BYTE ? null : this.#currVal;\n this.#currVal = '';\n break;\n }\n }\n // flush segment\n l < r && (this.#currVal += chunk.toString('utf8', l, r));\n }\n}\n\n// The lone NULL byte signifies that the column value is `null`.\n// (Postgres does not permit NULL bytes in TEXT values).\n//\n// Note that although NULL bytes can appear in JSON strings,\n// those will always be represented within double-quotes,\n// and thus never as a lone NULL byte.\nexport const NULL_BYTE = '\\u0000';\n\n// escaped characters used in https://www.postgresql.org/docs/current/sql-copy.html\nconst ESCAPED_CHARACTERS: Record<number, string | undefined> = {\n 0x4e: NULL_BYTE, // \\N signifies the NULL character.\n 0x5c: '\\\\',\n 0x62: '\\b',\n 0x66: '\\f',\n 0x6e: '\\n',\n 0x72: '\\r',\n 0x74: '\\t',\n 0x76: '\\v',\n} as const;\n"],"mappings":";;;;;;;;;;AA2CA,IAAa,YAAb,MAAuB;CACrB,WAAmB;CACnB,WAAW;CAEX,CAAC,MAAM,OAAwC;EAC7C,IAAI,IAAI;EACR,IAAI,IAAI;EAER,OAAO,IAAI,MAAM,QAAQ,KAAK;GAC5B,MAAM,KAAK,MAAM;GACjB,IAAI,KAAK,UAAU;IACjB,MAAM,cAAc,mBAAmB;IACvC,IAAI,gBAAgB,KAAA,GAClB,MAAM,IAAI,MACR,iCAAiC,OAAO,aAAa,EAAE,GACzD;IAEF,KAAK,YAAY;IACjB,IAAI,IAAI;IACR,KAAK,WAAW;IAChB;GACF;GACA,QAAQ,IAAR;IACE,KAAK;KAEH,IAAI,MAAM,KAAK,YAAY,MAAM,SAAS,QAAQ,GAAG,CAAC;KACtD,IAAI,IAAI;KACR,KAAK,WAAW;KAChB;IAEF,KAAK;IACL,KAAK;KAEH,IAAI,MAAM,KAAK,YAAY,MAAM,SAAS,QAAQ,GAAG,CAAC;KACtD,IAAI,IAAI;KAGR,MAAM,KAAK,aAAA,OAAyB,OAAO,KAAK;KAChD,KAAK,WAAW;GAEpB;EACF;EAEA,IAAI,MAAM,KAAK,YAAY,MAAM,SAAS,QAAQ,GAAG,CAAC;CACxD;AACF;AAWA,IAAM,qBAAyD;CAC7D,IAAA;CACA,IAAM;CACN,IAAM;CACN,KAAM;CACN,KAAM;CACN,KAAM;CACN,KAAM;CACN,KAAM;AACR"}
@@ -88,6 +88,6 @@ var UnsupportedColumnDefaultError = class extends Error {
88
88
  name = "UnsupportedColumnDefaultError";
89
89
  };
90
90
  //#endregion
91
- export { UnsupportedColumnDefaultError, isArrayColumn, isEnumColumn, mapPostgresToLite, mapPostgresToLiteColumn, mapPostgresToLiteIndex, warnIfDataTypeSupported };
91
+ export { UnsupportedColumnDefaultError, isArrayColumn, isEnumColumn, mapPostgresToLite, mapPostgresToLiteColumn, mapPostgresToLiteDefault, mapPostgresToLiteIndex, warnIfDataTypeSupported };
92
92
 
93
93
  //# sourceMappingURL=pg-to-lite.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pg-to-lite.js","names":[],"sources":["../../../../../zero-cache/src/db/pg-to-lite.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {ZERO_VERSION_COLUMN_NAME} from '../services/replicator/schema/constants.ts';\nimport {\n liteTypeString,\n liteTypeToZqlValueType,\n upstreamDataType,\n type LiteTypeString,\n} from '../types/lite.ts';\nimport {liteTableName} from '../types/names.ts';\nimport * as PostgresTypeClass from './postgres-type-class-enum.ts';\nimport {\n type ColumnSpec,\n type IndexSpec,\n type LiteIndexSpec,\n type LiteTableSpec,\n type TableSpec,\n} from './specs.ts';\n\n/**\n * Determines if a PostgreSQL column is an enum type.\n * This checks both the element type class (for arrays of enums) and the main type class.\n */\nexport function isEnumColumn(\n spec: Pick<ColumnSpec, 'pgTypeClass' | 'elemPgTypeClass'>,\n): boolean {\n return (spec.elemPgTypeClass ?? spec.pgTypeClass) === PostgresTypeClass.Enum;\n}\n\n/**\n * Determines if a PostgreSQL column is an array type.\n * In PostgreSQL's system, array columns have a non-null elemPgTypeClass.\n */\nexport function isArrayColumn(\n spec: Pick<ColumnSpec, 'elemPgTypeClass'>,\n): boolean {\n return spec.elemPgTypeClass !== null && spec.elemPgTypeClass !== undefined;\n}\n\nfunction zeroVersionColumnSpec(defaultVersion: string | undefined): ColumnSpec {\n return {\n pos: Number.MAX_SAFE_INTEGER, // i.e. last\n characterMaximumLength: null,\n dataType: 'text',\n notNull: false,\n dflt: !defaultVersion ? null : `'${defaultVersion}'`,\n elemPgTypeClass: null,\n };\n}\n\nexport function warnIfDataTypeSupported(\n lc: LogContext,\n liteTypeString: LiteTypeString,\n table: string,\n column: string,\n) {\n if (liteTypeToZqlValueType(liteTypeString) === undefined) {\n lc.warn?.(\n `\\n\\nWARNING: zero does not yet support the \"${upstreamDataType(\n liteTypeString,\n )}\" data type.\\n` +\n `The \"${table}\".\"${column}\" column will not be synced to clients.\\n\\n`,\n );\n }\n}\n\n// Numeric literals: integers and decimals, optionally negative\nconst NUMERIC_LITERAL_REGEX = /^-?\\d+(\\.\\d+)?$/;\n\n// Boolean literals (PG emits lowercase)\nconst BOOLEAN_LITERAL_REGEX = /^(true|false)$/;\n\n// Quoted string with type cast to a simple scalar type: 'value'::typename\n// For strings and certain incarnations of primitives (e.g. integers greater\n// than 2^31-1, Postgres' nodeToString() represents the values as type-casted\n// 'string' values, e.g. `'2147483648'::bigint`, `'foo'::text`.\n// Only matches simple type names (word characters) - array types like\n// `::text[]` won't match and will trigger backfill.\nconst QUOTED_STRING_WITH_CAST_REGEX = /^('.*')::(\\w+)$/;\n\n// Empty array constructor syntax: ARRAY[]::text[], ARRAY[]::integer[], etc.\n// Maps to '[]' (JSON empty array) in SQLite.\nconst EMPTY_ARRAY_CONSTRUCTOR_REGEX = /^ARRAY\\s*\\[\\s*\\]::\\w+\\[\\]$/i;\n\n// Empty array literal syntax: '{}'::text[], '{}'::integer[], etc.\n// Maps to '[]' (JSON empty array) in SQLite.\nconst EMPTY_ARRAY_LITERAL_REGEX = /^'\\{\\}'::\\w+\\[\\]$/;\n\n// Conservative allowlist approach for SQLite ADD COLUMN defaults.\n// We only allow patterns we know are safe. Everything else triggers\n// backfill from PostgreSQL, which correctly handles complex defaults.\n//\n// Note: We don't validate that the default value matches the column type\n// (e.g., that a numeric literal is used with a numeric column). PostgreSQL\n// already enforces this at schema definition time - you can't define\n// `ALTER TABLE foo ADD bar TEXT DEFAULT 123` in PG. So we trust that any\n// default we receive from the replication stream is type-compatible with\n// whatever we map the type to in SQLite.\n//\n// Example: `true`/`false` literals can only appear as defaults for boolean\n// columns in PG, so we don't need to check the column type before converting\n// to 1/0.\n//\n// See: https://www.sqlite.org/lang_altertable.html#altertabaddcol\n//\n// Exported for testing.\nexport function mapPostgresToLiteDefault(\n table: string,\n column: string,\n defaultExpression: string | null | undefined,\n): string | null {\n if (!defaultExpression) {\n return null;\n }\n\n // Numeric literals pass through unchanged\n if (NUMERIC_LITERAL_REGEX.test(defaultExpression)) {\n return defaultExpression;\n }\n\n // Boolean literals convert to SQLite's 1/0\n if (BOOLEAN_LITERAL_REGEX.test(defaultExpression)) {\n return defaultExpression === 'true' ? '1' : '0';\n }\n\n // Quoted strings with type casts: extract just the quoted part\n const match = QUOTED_STRING_WITH_CAST_REGEX.exec(defaultExpression);\n if (match) {\n return match[1];\n }\n\n // Empty arrays: ARRAY[]::type[] or '{}'::type[] → '[]'\n if (\n EMPTY_ARRAY_CONSTRUCTOR_REGEX.test(defaultExpression) ||\n EMPTY_ARRAY_LITERAL_REGEX.test(defaultExpression)\n ) {\n return \"'[]'\";\n }\n\n // Everything else triggers backfill\n throw new UnsupportedColumnDefaultError(\n `Unsupported default value for ${table}.${column}: ${defaultExpression}`,\n );\n}\n\nexport function mapPostgresToLiteColumn(\n table: string,\n column: {name: string; spec: ColumnSpec},\n ignoreDefault?: 'ignore-default',\n): ColumnSpec {\n const {pos, dataType, notNull, dflt, elemPgTypeClass = null} = column.spec;\n\n // PostgreSQL includes [] in dataType for array types (e.g., 'int4[]',\n // 'int4[][]'). liteTypeString() appends attributes:\n // \"varchar[]|NOT_NULL|TEXT_ARRAY\", \"my_enum[][]|TEXT_ENUM|TEXT_ARRAY\"\n const liteType = liteTypeString(\n dataType,\n notNull,\n isEnumColumn(column.spec),\n isArrayColumn(column.spec),\n );\n\n return {\n pos,\n dataType: liteType,\n characterMaximumLength: null,\n // Note: NOT NULL constraints are always ignored for SQLite (replica) tables.\n // 1. They are enforced by the replication stream.\n // 2. We need nullability for columns with defaults to support\n // write permissions on the \"proposed mutation\" state. Proposed\n // mutations are written to SQLite in a `BEGIN CONCURRENT` transaction in mutagen.\n // Permission policies are run against that state (to get their ruling) then the\n // transaction is rolled back.\n notNull: false,\n // Note: DEFAULT constraints are ignored when creating new tables, but are\n // necessary for adding columns to tables with existing rows.\n dflt:\n ignoreDefault === 'ignore-default'\n ? null\n : mapPostgresToLiteDefault(table, column.name, dflt),\n elemPgTypeClass,\n };\n}\n\nexport function mapPostgresToLite(\n t: TableSpec,\n defaultVersion?: string,\n): LiteTableSpec {\n // PRIMARY KEYS are not written to the replica. Instead, we rely\n // UNIQUE indexes, including those created for upstream PRIMARY KEYs.\n const {schema: _, primaryKey: _dropped, ...liteSpec} = t;\n const name = liteTableName(t);\n return {\n ...liteSpec,\n name,\n columns: {\n ...Object.fromEntries(\n Object.entries(t.columns).map(([col, spec]) => [\n col,\n // `ignore-default` for create table statements because\n // there are no rows to set the default for.\n mapPostgresToLiteColumn(name, {name: col, spec}, 'ignore-default'),\n ]),\n ),\n [ZERO_VERSION_COLUMN_NAME]: zeroVersionColumnSpec(defaultVersion),\n },\n };\n}\n\nexport function mapPostgresToLiteIndex(index: IndexSpec): LiteIndexSpec {\n const {schema, tableName, name, ...liteIndex} = index;\n return {\n tableName: liteTableName({schema, name: tableName}),\n name: liteTableName({schema, name}),\n ...liteIndex,\n };\n}\n\nexport class UnsupportedColumnDefaultError extends Error {\n readonly name = 'UnsupportedColumnDefaultError';\n}\n"],"mappings":";;;;;;;;;AAsBA,SAAgB,aACd,MACS;CACT,QAAQ,KAAK,mBAAmB,KAAK,iBAAiB;AACxD;;;;;AAMA,SAAgB,cACd,MACS;CACT,OAAO,KAAK,oBAAoB,QAAQ,KAAK,oBAAoB,KAAA;AACnE;AAEA,SAAS,sBAAsB,gBAAgD;CAC7E,OAAO;EACL,KAAK,OAAO;EACZ,wBAAwB;EACxB,UAAU;EACV,SAAS;EACT,MAAM,CAAC,iBAAiB,OAAO,IAAI,eAAe;EAClD,iBAAiB;CACnB;AACF;AAEA,SAAgB,wBACd,IACA,gBACA,OACA,QACA;CACA,IAAI,uBAAuB,cAAc,MAAM,KAAA,GAC7C,GAAG,OACD,+CAA+C,iBAC7C,cACF,EAAE,qBACQ,MAAM,KAAK,OAAO,4CAC9B;AAEJ;AAGA,IAAM,wBAAwB;AAG9B,IAAM,wBAAwB;AAQ9B,IAAM,gCAAgC;AAItC,IAAM,gCAAgC;AAItC,IAAM,4BAA4B;AAoBlC,SAAgB,yBACd,OACA,QACA,mBACe;CACf,IAAI,CAAC,mBACH,OAAO;CAIT,IAAI,sBAAsB,KAAK,iBAAiB,GAC9C,OAAO;CAIT,IAAI,sBAAsB,KAAK,iBAAiB,GAC9C,OAAO,sBAAsB,SAAS,MAAM;CAI9C,MAAM,QAAQ,8BAA8B,KAAK,iBAAiB;CAClE,IAAI,OACF,OAAO,MAAM;CAIf,IACE,8BAA8B,KAAK,iBAAiB,KACpD,0BAA0B,KAAK,iBAAiB,GAEhD,OAAO;CAIT,MAAM,IAAI,8BACR,iCAAiC,MAAM,GAAG,OAAO,IAAI,mBACvD;AACF;AAEA,SAAgB,wBACd,OACA,QACA,eACY;CACZ,MAAM,EAAC,KAAK,UAAU,SAAS,MAAM,kBAAkB,SAAQ,OAAO;CAYtE,OAAO;EACL;EACA,UATe,eACf,UACA,SACA,aAAa,OAAO,IAAI,GACxB,cAAc,OAAO,IAAI,CAKf;EACV,wBAAwB;EAQxB,SAAS;EAGT,MACE,kBAAkB,mBACd,OACA,yBAAyB,OAAO,OAAO,MAAM,IAAI;EACvD;CACF;AACF;AAEA,SAAgB,kBACd,GACA,gBACe;CAGf,MAAM,EAAC,QAAQ,GAAG,YAAY,UAAU,GAAG,aAAY;CACvD,MAAM,OAAO,cAAc,CAAC;CAC5B,OAAO;EACL,GAAG;EACH;EACA,SAAS;GACP,GAAG,OAAO,YACR,OAAO,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,UAAU,CAC7C,KAGA,wBAAwB,MAAM;IAAC,MAAM;IAAK;GAAI,GAAG,gBAAgB,CACnE,CAAC,CACH;IACC,2BAA2B,sBAAsB,cAAc;EAClE;CACF;AACF;AAEA,SAAgB,uBAAuB,OAAiC;CACtE,MAAM,EAAC,QAAQ,WAAW,MAAM,GAAG,cAAa;CAChD,OAAO;EACL,WAAW,cAAc;GAAC;GAAQ,MAAM;EAAS,CAAC;EAClD,MAAM,cAAc;GAAC;GAAQ;EAAI,CAAC;EAClC,GAAG;CACL;AACF;AAEA,IAAa,gCAAb,cAAmD,MAAM;CACvD,OAAgB;AAClB"}
1
+ {"version":3,"file":"pg-to-lite.js","names":[],"sources":["../../../../../zero-cache/src/db/pg-to-lite.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {ZERO_VERSION_COLUMN_NAME} from '../services/replicator/schema/constants.ts';\nimport {\n liteTypeString,\n liteTypeToZqlValueType,\n upstreamDataType,\n type LiteTypeString,\n} from '../types/lite.ts';\nimport {liteTableName} from '../types/names.ts';\nimport * as PostgresTypeClass from './postgres-type-class-enum.ts';\nimport {\n type ColumnSpec,\n type IndexSpec,\n type LiteIndexSpec,\n type LiteTableSpec,\n type TableSpec,\n} from './specs.ts';\n\n/**\n * Determines if a PostgreSQL column is an enum type.\n * This checks both the element type class (for arrays of enums) and the main type class.\n */\nexport function isEnumColumn(\n spec: Pick<ColumnSpec, 'pgTypeClass' | 'elemPgTypeClass'>,\n): boolean {\n return (spec.elemPgTypeClass ?? spec.pgTypeClass) === PostgresTypeClass.Enum;\n}\n\n/**\n * Determines if a PostgreSQL column is an array type.\n * In PostgreSQL's system, array columns have a non-null elemPgTypeClass.\n */\nexport function isArrayColumn(\n spec: Pick<ColumnSpec, 'elemPgTypeClass'>,\n): boolean {\n return spec.elemPgTypeClass !== null && spec.elemPgTypeClass !== undefined;\n}\n\nfunction zeroVersionColumnSpec(defaultVersion: string | undefined): ColumnSpec {\n return {\n pos: Number.MAX_SAFE_INTEGER, // i.e. last\n characterMaximumLength: null,\n dataType: 'text',\n notNull: false,\n dflt: !defaultVersion ? null : `'${defaultVersion}'`,\n elemPgTypeClass: null,\n };\n}\n\nexport function warnIfDataTypeSupported(\n lc: LogContext,\n liteTypeString: LiteTypeString,\n table: string,\n column: string,\n) {\n if (liteTypeToZqlValueType(liteTypeString) === undefined) {\n lc.warn?.(\n `\\n\\nWARNING: zero does not yet support the \"${upstreamDataType(\n liteTypeString,\n )}\" data type.\\n` +\n `The \"${table}\".\"${column}\" column will not be synced to clients.\\n\\n`,\n );\n }\n}\n\n// Numeric literals: integers and decimals, optionally negative\nconst NUMERIC_LITERAL_REGEX = /^-?\\d+(\\.\\d+)?$/;\n\n// Boolean literals (PG emits lowercase)\nconst BOOLEAN_LITERAL_REGEX = /^(true|false)$/;\n\n// Quoted string with type cast to a simple scalar type: 'value'::typename\n// For strings and certain incarnations of primitives (e.g. integers greater\n// than 2^31-1, Postgres' nodeToString() represents the values as type-casted\n// 'string' values, e.g. `'2147483648'::bigint`, `'foo'::text`.\n// Only matches simple type names (word characters) - array types like\n// `::text[]` won't match and will trigger backfill.\nconst QUOTED_STRING_WITH_CAST_REGEX = /^('.*')::(\\w+)$/;\n\n// Empty array constructor syntax: ARRAY[]::text[], ARRAY[]::integer[], etc.\n// Maps to '[]' (JSON empty array) in SQLite.\nconst EMPTY_ARRAY_CONSTRUCTOR_REGEX = /^ARRAY\\s*\\[\\s*\\]::\\w+\\[\\]$/i;\n\n// Empty array literal syntax: '{}'::text[], '{}'::integer[], etc.\n// Maps to '[]' (JSON empty array) in SQLite.\nconst EMPTY_ARRAY_LITERAL_REGEX = /^'\\{\\}'::\\w+\\[\\]$/;\n\n// Conservative allowlist approach for SQLite ADD COLUMN defaults.\n// We only allow patterns we know are safe. Everything else triggers\n// backfill from PostgreSQL, which correctly handles complex defaults.\n//\n// Note: We don't validate that the default value matches the column type\n// (e.g., that a numeric literal is used with a numeric column). PostgreSQL\n// already enforces this at schema definition time - you can't define\n// `ALTER TABLE foo ADD bar TEXT DEFAULT 123` in PG. So we trust that any\n// default we receive from the replication stream is type-compatible with\n// whatever we map the type to in SQLite.\n//\n// Example: `true`/`false` literals can only appear as defaults for boolean\n// columns in PG, so we don't need to check the column type before converting\n// to 1/0.\n//\n// See: https://www.sqlite.org/lang_altertable.html#altertabaddcol\n//\n// Exported for testing.\nexport function mapPostgresToLiteDefault(\n table: string,\n column: string,\n defaultExpression: string | null | undefined,\n): string | null {\n if (!defaultExpression) {\n return null;\n }\n\n // Numeric literals pass through unchanged\n if (NUMERIC_LITERAL_REGEX.test(defaultExpression)) {\n return defaultExpression;\n }\n\n // Boolean literals convert to SQLite's 1/0\n if (BOOLEAN_LITERAL_REGEX.test(defaultExpression)) {\n return defaultExpression === 'true' ? '1' : '0';\n }\n\n // Quoted strings with type casts: extract just the quoted part\n const match = QUOTED_STRING_WITH_CAST_REGEX.exec(defaultExpression);\n if (match) {\n return match[1];\n }\n\n // Empty arrays: ARRAY[]::type[] or '{}'::type[] → '[]'\n if (\n EMPTY_ARRAY_CONSTRUCTOR_REGEX.test(defaultExpression) ||\n EMPTY_ARRAY_LITERAL_REGEX.test(defaultExpression)\n ) {\n return \"'[]'\";\n }\n\n // Everything else triggers backfill\n throw new UnsupportedColumnDefaultError(\n `Unsupported default value for ${table}.${column}: ${defaultExpression}`,\n );\n}\n\nexport function mapPostgresToLiteColumn(\n table: string,\n column: {name: string; spec: ColumnSpec},\n ignoreDefault?: 'ignore-default',\n): ColumnSpec {\n const {pos, dataType, notNull, dflt, elemPgTypeClass = null} = column.spec;\n\n // PostgreSQL includes [] in dataType for array types (e.g., 'int4[]',\n // 'int4[][]'). liteTypeString() appends attributes:\n // \"varchar[]|NOT_NULL|TEXT_ARRAY\", \"my_enum[][]|TEXT_ENUM|TEXT_ARRAY\"\n const liteType = liteTypeString(\n dataType,\n notNull,\n isEnumColumn(column.spec),\n isArrayColumn(column.spec),\n );\n\n return {\n pos,\n dataType: liteType,\n characterMaximumLength: null,\n // Note: NOT NULL constraints are always ignored for SQLite (replica) tables.\n // 1. They are enforced by the replication stream.\n // 2. We need nullability for columns with defaults to support\n // write permissions on the \"proposed mutation\" state. Proposed\n // mutations are written to SQLite in a `BEGIN CONCURRENT` transaction in mutagen.\n // Permission policies are run against that state (to get their ruling) then the\n // transaction is rolled back.\n notNull: false,\n // Note: DEFAULT constraints are ignored when creating new tables, but are\n // necessary for adding columns to tables with existing rows.\n dflt:\n ignoreDefault === 'ignore-default'\n ? null\n : mapPostgresToLiteDefault(table, column.name, dflt),\n elemPgTypeClass,\n };\n}\n\nexport function mapPostgresToLite(\n t: TableSpec,\n defaultVersion?: string,\n): LiteTableSpec {\n // PRIMARY KEYS are not written to the replica. Instead, we rely\n // UNIQUE indexes, including those created for upstream PRIMARY KEYs.\n const {schema: _, primaryKey: _dropped, ...liteSpec} = t;\n const name = liteTableName(t);\n return {\n ...liteSpec,\n name,\n columns: {\n ...Object.fromEntries(\n Object.entries(t.columns).map(([col, spec]) => [\n col,\n // `ignore-default` for create table statements because\n // there are no rows to set the default for.\n mapPostgresToLiteColumn(name, {name: col, spec}, 'ignore-default'),\n ]),\n ),\n [ZERO_VERSION_COLUMN_NAME]: zeroVersionColumnSpec(defaultVersion),\n },\n };\n}\n\nexport function mapPostgresToLiteIndex(index: IndexSpec): LiteIndexSpec {\n const {schema, tableName, name, ...liteIndex} = index;\n return {\n tableName: liteTableName({schema, name: tableName}),\n name: liteTableName({schema, name}),\n ...liteIndex,\n };\n}\n\nexport class UnsupportedColumnDefaultError extends Error {\n readonly name = 'UnsupportedColumnDefaultError';\n}\n"],"mappings":";;;;;;;;;AAsBA,SAAgB,aACd,MACS;CACT,QAAQ,KAAK,mBAAmB,KAAK,iBAAiB;AACxD;;;;;AAMA,SAAgB,cACd,MACS;CACT,OAAO,KAAK,oBAAoB,QAAQ,KAAK,oBAAoB,KAAA;AACnE;AAEA,SAAS,sBAAsB,gBAAgD;CAC7E,OAAO;EACL,KAAK,OAAO;EACZ,wBAAwB;EACxB,UAAU;EACV,SAAS;EACT,MAAM,CAAC,iBAAiB,OAAO,IAAI,eAAe;EAClD,iBAAiB;CACnB;AACF;AAEA,SAAgB,wBACd,IACA,gBACA,OACA,QACA;CACA,IAAI,uBAAuB,cAAc,MAAM,KAAA,GAC7C,GAAG,OACD,+CAA+C,iBAC7C,cACF,EAAE,qBACQ,MAAM,KAAK,OAAO,4CAC9B;AAEJ;AAGA,IAAM,wBAAwB;AAG9B,IAAM,wBAAwB;AAQ9B,IAAM,gCAAgC;AAItC,IAAM,gCAAgC;AAItC,IAAM,4BAA4B;AAoBlC,SAAgB,yBACd,OACA,QACA,mBACe;CACf,IAAI,CAAC,mBACH,OAAO;CAIT,IAAI,sBAAsB,KAAK,iBAAiB,GAC9C,OAAO;CAIT,IAAI,sBAAsB,KAAK,iBAAiB,GAC9C,OAAO,sBAAsB,SAAS,MAAM;CAI9C,MAAM,QAAQ,8BAA8B,KAAK,iBAAiB;CAClE,IAAI,OACF,OAAO,MAAM;CAIf,IACE,8BAA8B,KAAK,iBAAiB,KACpD,0BAA0B,KAAK,iBAAiB,GAEhD,OAAO;CAIT,MAAM,IAAI,8BACR,iCAAiC,MAAM,GAAG,OAAO,IAAI,mBACvD;AACF;AAEA,SAAgB,wBACd,OACA,QACA,eACY;CACZ,MAAM,EAAC,KAAK,UAAU,SAAS,MAAM,kBAAkB,SAAQ,OAAO;CAYtE,OAAO;EACL;EACA,UATe,eACf,UACA,SACA,aAAa,OAAO,IAAI,GACxB,cAAc,OAAO,IAAI,CAKf;EACV,wBAAwB;EAQxB,SAAS;EAGT,MACE,kBAAkB,mBACd,OACA,yBAAyB,OAAO,OAAO,MAAM,IAAI;EACvD;CACF;AACF;AAEA,SAAgB,kBACd,GACA,gBACe;CAGf,MAAM,EAAC,QAAQ,GAAG,YAAY,UAAU,GAAG,aAAY;CACvD,MAAM,OAAO,cAAc,CAAC;CAC5B,OAAO;EACL,GAAG;EACH;EACA,SAAS;GACP,GAAG,OAAO,YACR,OAAO,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,UAAU,CAC7C,KAGA,wBAAwB,MAAM;IAAC,MAAM;IAAK;GAAI,GAAG,gBAAgB,CACnE,CAAC,CACH;IACC,2BAA2B,sBAAsB,cAAc;EAClE;CACF;AACF;AAEA,SAAgB,uBAAuB,OAAiC;CACtE,MAAM,EAAC,QAAQ,WAAW,MAAM,GAAG,cAAa;CAChD,OAAO;EACL,WAAW,cAAc;GAAC;GAAQ,MAAM;EAAS,CAAC;EAClD,MAAM,cAAc;GAAC;GAAQ;EAAI,CAAC;EAClC,GAAG;CACL;AACF;AAEA,IAAa,gCAAb,cAAmD,MAAM;CACvD,OAAgB;AAClB"}
@@ -1 +1 @@
1
- {"version":3,"file":"run-transaction.js","names":[],"sources":["../../../../../zero-cache/src/db/run-transaction.ts"],"sourcesContent":["import type {Enum} from '../../../shared/src/enum.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport type * as Mode from './mode-enum.ts';\nimport {READ_COMMITTED} from './mode-enum.ts';\n\ntype Mode = Enum<typeof Mode>;\n\n// The default timeout is settable by ZERO_IDLE_IN_TRANSACTION_SESSION_TIMEOUT\n// as an emergency measure and is explicitly not made available as a server\n// option. This value is function of how the zero-cache uses transactions, and\n// should never need to be \"tuned\" or adjusted for different environments.\nconst IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS = parseInt(\n process.env.ZERO_IDLE_IN_TRANSACTION_SESSION_TIMEOUT ?? '60000',\n);\n\nexport type TransactionOptions = {\n mode?: Mode;\n};\n\n/**\n * Runs a zero-cache transaction on the given postgres DB. For consistency\n * across various postgres providers, certain transaction-level parameters\n * are set to override any defaults set by the provider, including:\n * * `statement_timeout` (disabled)\n * * `idle_in_transaction_session_timeout` (1min)\n */\nexport function runTx<T>(\n db: PostgresDB,\n fn: (tx: PostgresTransaction) => T | Promise<T>,\n opts: TransactionOptions = {},\n) {\n const {\n // Explicitly default to the Postgres default to override any custom\n // `default_transaction_isolation`.\n mode = READ_COMMITTED,\n } = opts;\n return db.begin(mode, sql => {\n // Disable any statement_timeout for the current transaction. By default,\n // Postgres does not impose a statement timeout, but some users and\n // providers set one at the database level (even though it is explicitly\n // discouraged by the Postgres documentation).\n //\n // Zero logic in particular often does not fit into the category of general\n // application logic; for potentially long-running operations like\n // migrations and background cleanup, the statement timeout should be\n // disabled to prevent these operations from timing out.\n void sql`SET LOCAL statement_timeout = 0;`.execute().catch(() => {});\n\n // Set an idle_in_transaction_session_timeout to limit the blast radius of\n // orphaned transactions. The zero-cache does not keep transactions open\n // and idle (though this may change if support for streaming of in-progress\n // transactions is added to our logical replication layer).\n void sql\n .unsafe(\n /*sql*/ `SET LOCAL idle_in_transaction_session_timeout = ${IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS}`,\n )\n .execute()\n .catch(() => {});\n\n return fn(sql);\n });\n}\n"],"mappings":";;;AAWA,IAAM,yCAAyC,SAC7C,QAAQ,IAAI,4CAA4C,OAC1D;;;;;;;;AAaA,SAAgB,MACd,IACA,IACA,OAA2B,CAAC,GAC5B;CACA,MAAM,EAGJ,OAAO,mBACL;CACJ,OAAO,GAAG,MAAM,OAAM,QAAO;EAU3B,GAAQ,mCAAmC,QAAQ,EAAE,YAAY,CAAC,CAAC;EAMnE,IACG,OACS,mDAAmD,wCAC7D,EACC,QAAQ,EACR,YAAY,CAAC,CAAC;EAEjB,OAAO,GAAG,GAAG;CACf,CAAC;AACH"}
1
+ {"version":3,"file":"run-transaction.js","names":[],"sources":["../../../../../zero-cache/src/db/run-transaction.ts"],"sourcesContent":["import type {Enum} from '../../../shared/src/enum.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport type * as Mode from './mode-enum.ts';\nimport {READ_COMMITTED} from './mode-enum.ts';\n\ntype Mode = Enum<typeof Mode>;\n\n// The default timeout is settable by ZERO_IDLE_IN_TRANSACTION_SESSION_TIMEOUT\n// as an emergency measure and is explicitly not made available as a server\n// option. This value is function of how the zero-cache uses transactions, and\n// should never need to be \"tuned\" or adjusted for different environments.\nconst IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS = parseInt(\n process.env.ZERO_IDLE_IN_TRANSACTION_SESSION_TIMEOUT ?? '60000',\n);\n\nexport type TransactionOptions = {\n mode?: Mode;\n};\n\n/**\n * Runs a zero-cache transaction on the given postgres DB. For consistency\n * across various postgres providers, certain transaction-level parameters\n * are set to override any defaults set by the provider, including:\n * * `statement_timeout` (disabled)\n * * `idle_in_transaction_session_timeout` (1min)\n */\nexport function runTx<T>(\n db: PostgresDB,\n fn: (tx: PostgresTransaction) => T | Promise<T>,\n opts: TransactionOptions = {},\n) {\n const {\n // Explicitly default to the Postgres default to override any custom\n // `default_transaction_isolation`.\n mode = READ_COMMITTED,\n } = opts;\n return db.begin(mode, sql => {\n // Disable any statement_timeout for the current transaction. By default,\n // Postgres does not impose a statement timeout, but some users and\n // providers set one at the database level (even though it is explicitly\n // discouraged by the Postgres documentation).\n //\n // Zero logic in particular often does not fit into the category of general\n // application logic; for potentially long-running operations like\n // migrations and background cleanup, the statement timeout should be\n // disabled to prevent these operations from timing out.\n void sql`SET LOCAL statement_timeout = 0;`.execute().catch(() => {});\n\n // Set an idle_in_transaction_session_timeout to limit the blast radius of\n // orphaned transactions. The zero-cache does not keep transactions open\n // and idle (though this may change if support for streaming of in-progress\n // transactions is added to our logical replication layer).\n void sql\n .unsafe(\n /*sql*/ `SET LOCAL idle_in_transaction_session_timeout = ${IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS}`,\n )\n .execute()\n .catch(() => {});\n\n return fn(sql);\n });\n}\n"],"mappings":";;;AAWA,IAAM,yCAAyC,SAC7C,QAAQ,IAAI,4CAA4C,OAC1D;;;;;;;;AAaA,SAAgB,MACd,IACA,IACA,OAA2B,CAAC,GAC5B;CACA,MAAM,EAGJ,OAAO,mBACL;CACJ,OAAO,GAAG,MAAM,OAAM,QAAO;EAU3B,GAAQ,mCAAmC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC;EAMnE,IACG,OACS,mDAAmD,wCAC7D,CAAC,CACA,QAAQ,CAAC,CACT,YAAY,CAAC,CAAC;EAEjB,OAAO,GAAG,GAAG;CACf,CAAC;AACH"}
@@ -12,11 +12,12 @@ var columnSpec = valita_exports.object({
12
12
  dflt: valita_exports.string().nullable().optional()
13
13
  });
14
14
  var publishedColumnSpec = columnSpec.extend({ typeOID: valita_exports.number() });
15
- var tableSpec = valita_exports.object({
15
+ var liteTableSpec = valita_exports.object({
16
16
  name: valita_exports.string(),
17
17
  columns: valita_exports.record(columnSpec),
18
18
  primaryKey: valita_exports.array(valita_exports.string()).optional()
19
- }).extend({ schema: valita_exports.string() });
19
+ });
20
+ var tableSpec = liteTableSpec.extend({ schema: valita_exports.string() });
20
21
  var publishedTableSpec = tableSpec.extend({
21
22
  oid: valita_exports.number(),
22
23
  schemaOID: valita_exports.number().optional(),
@@ -51,19 +52,20 @@ var indexPredicateSchema = valita_exports.lazy(() => valita_exports.union(readon
51
52
  op: literalUnion("=", "<>", "<", "<=", ">", ">="),
52
53
  value: indexPredicateValueSchema
53
54
  })));
54
- var indexSpec = valita_exports.object({
55
+ var liteIndexSpec = valita_exports.object({
55
56
  name: valita_exports.string(),
56
57
  tableName: valita_exports.string(),
57
58
  unique: valita_exports.boolean(),
58
59
  columns: valita_exports.record(directionSchema),
59
60
  predicate: indexPredicateSchema.optional()
60
- }).extend({ schema: valita_exports.string() });
61
+ });
62
+ var indexSpec = liteIndexSpec.extend({ schema: valita_exports.string() });
61
63
  var publishedIndexSpec = indexSpec.extend({
62
64
  isReplicaIdentity: valita_exports.boolean().optional(),
63
65
  isPrimaryKey: valita_exports.boolean().optional(),
64
66
  isImmediate: valita_exports.boolean().optional()
65
67
  });
66
68
  //#endregion
67
- export { columnSpec, indexSpec, publishedColumnSpec, publishedIndexSpec, publishedTableSpec, tableSpec };
69
+ export { columnSpec, directionSchema, indexPredicateSchema, indexSpec, liteIndexSpec, liteTableSpec, pgTypeClassSchema, publishedColumnSpec, publishedIndexSpec, publishedTableSpec, tableSpec };
68
70
 
69
71
  //# sourceMappingURL=specs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"specs.js","names":[],"sources":["../../../../../zero-cache/src/db/specs.ts"],"sourcesContent":["import type {DeepReadonly} from '../../../shared/src/json.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue} from '../../../zero-schema/src/table-schema.ts';\nimport * as PostgresReplicaIdentity from './postgres-replica-identity-enum.ts';\nimport * as PostgresTypeClass from './postgres-type-class-enum.ts';\n\nexport const pgTypeClassSchema = v.literalUnion(\n PostgresTypeClass.Base,\n PostgresTypeClass.Composite,\n PostgresTypeClass.Domain,\n PostgresTypeClass.Enum,\n PostgresTypeClass.Pseudo,\n PostgresTypeClass.Range,\n PostgresTypeClass.Multirange,\n);\n\nconst pgReplicaIdentitySchema = v.literalUnion(\n PostgresReplicaIdentity.Default,\n PostgresReplicaIdentity.Nothing,\n PostgresReplicaIdentity.Full,\n PostgresReplicaIdentity.Index,\n);\n\nexport const columnSpec = v.object({\n pos: v.number(),\n dataType: v.string(),\n pgTypeClass: pgTypeClassSchema.optional(),\n\n // If the column is an array, this will be the type of the\n // elements in the array. If the column is not an array,\n // this will be null.\n elemPgTypeClass: pgTypeClassSchema.nullable().optional(),\n\n characterMaximumLength: v.number().nullable().optional(),\n notNull: v.boolean().nullable().optional(),\n dflt: v.string().nullable().optional(),\n});\n\nexport type ColumnSpec = Readonly<v.Infer<typeof columnSpec>>;\n\nexport const publishedColumnSpec = columnSpec.extend({\n typeOID: v.number(),\n});\n\nexport const liteTableSpec = v.object({\n name: v.string(),\n columns: v.record(columnSpec),\n primaryKey: v.array(v.string()).optional(),\n});\n\nexport const tableSpec = liteTableSpec.extend({\n schema: v.string(),\n});\n\nexport const publishedTableSpec = tableSpec.extend({\n oid: v.number(),\n // Always present for new instances (e.g. from DDL triggers), but\n // may from `initialSchema` object stored in the `replicas` table.\n schemaOID: v.number().optional(),\n columns: v.record(publishedColumnSpec),\n replicaIdentity: pgReplicaIdentitySchema.optional(),\n publications: v.record(v.object({rowFilter: v.string().nullable()})),\n});\n\nexport type MutableLiteTableSpec = v.Infer<typeof liteTableSpec>;\n\nexport type LiteTableSpec = Readonly<MutableLiteTableSpec>;\n\nexport type LiteTableSpecWithKeysAndVersion = Omit<\n LiteTableSpec,\n 'primaryKey'\n> & {\n /**\n * All keys associated with a unique index. Includes indexes with\n * nullable columns.\n */\n uniqueKeys: PrimaryKey[];\n\n /**\n * The key selected to act as the \"primary key\". Primary keys\n * are not explicitly set on the replica, but an appropriate\n * unique index is required.\n */\n primaryKey: PrimaryKey; // note: required\n\n /**\n * All keys associated with a unique index over non-null\n * columns, i.e. suitable as a primary key.\n */\n allPotentialPrimaryKeys: PrimaryKey[];\n\n /**\n * The minimum `_0_version` value for every row in the table. If this is\n * present, `_0_version` value in the row itself should only be used if\n * it is greater (i.e. later) than the `minRowVersion`.\n */\n minRowVersion: string | null;\n};\n\nexport type LiteAndZqlSpec = {\n tableSpec: LiteTableSpecWithKeysAndVersion;\n zqlSpec: Record<string, SchemaValue>;\n};\n\nexport type TableSpec = Readonly<v.Infer<typeof tableSpec>>;\n\nexport type PublishedTableSpec = Readonly<v.Infer<typeof publishedTableSpec>>;\n\nexport const directionSchema = v.literalUnion('ASC', 'DESC');\n\nexport type IndexPredicateValue =\n | {readonly type: 'boolean'; readonly value: boolean}\n | {readonly type: 'integer'; readonly value: string}\n | {readonly type: 'string'; readonly value: string};\n\nexport type IndexPredicate =\n | {\n readonly type: 'and' | 'or';\n readonly conditions: readonly IndexPredicate[];\n }\n | {readonly type: 'not'; readonly condition: IndexPredicate}\n | {\n readonly type: 'null-test';\n readonly column: string;\n readonly op: 'IS NULL' | 'IS NOT NULL';\n }\n | {\n readonly type: 'comparison';\n readonly column: string;\n readonly op: '=' | '<>' | '<' | '<=' | '>' | '>=';\n readonly value: IndexPredicateValue;\n };\n\nconst indexPredicateValueSchema: v.Type<IndexPredicateValue> = v.union(\n v.readonlyObject({type: v.literal('boolean'), value: v.boolean()}),\n v.readonlyObject({type: v.literal('integer'), value: v.string()}),\n v.readonlyObject({type: v.literal('string'), value: v.string()}),\n);\n\nexport const indexPredicateSchema: v.Type<IndexPredicate> = v.lazy(() =>\n v.union(\n v.readonlyObject({\n type: v.literalUnion('and', 'or'),\n conditions: v.readonlyArray(indexPredicateSchema),\n }),\n v.readonlyObject({\n type: v.literal('not'),\n condition: indexPredicateSchema,\n }),\n v.readonlyObject({\n type: v.literal('null-test'),\n column: v.string(),\n op: v.literalUnion('IS NULL', 'IS NOT NULL'),\n }),\n v.readonlyObject({\n type: v.literal('comparison'),\n column: v.string(),\n op: v.literalUnion('=', '<>', '<', '<=', '>', '>='),\n value: indexPredicateValueSchema,\n }),\n ),\n);\n\nexport const liteIndexSpec = v.object({\n name: v.string(),\n tableName: v.string(),\n unique: v.boolean(),\n columns: v.record(directionSchema),\n predicate: indexPredicateSchema.optional(),\n});\n\nexport type MutableLiteIndexSpec = v.Infer<typeof liteIndexSpec>;\n\nexport type LiteIndexSpec = Readonly<MutableLiteIndexSpec>;\n\nexport const indexSpec = liteIndexSpec.extend({\n schema: v.string(),\n});\n\nexport type IndexSpec = DeepReadonly<v.Infer<typeof indexSpec>>;\n\nexport const publishedIndexSpec = indexSpec.extend({\n isReplicaIdentity: v.boolean().optional(),\n isPrimaryKey: v.boolean().optional(),\n isImmediate: v.boolean().optional(),\n});\n\nexport type PublishedIndexSpec = DeepReadonly<\n v.Infer<typeof publishedIndexSpec>\n>;\n"],"mappings":";;AAOA,IAAa,oBAAoB,aAC/B,KACA,KACA,KACA,KACA,KACA,KACA,GACF;AAEA,IAAM,0BAA0B,aAC9B,KACA,KACA,KACA,GACF;AAEA,IAAa,aAAa,eAAE,OAAO;CACjC,KAAK,eAAE,OAAO;CACd,UAAU,eAAE,OAAO;CACnB,aAAa,kBAAkB,SAAS;CAKxC,iBAAiB,kBAAkB,SAAS,EAAE,SAAS;CAEvD,wBAAwB,eAAE,OAAO,EAAE,SAAS,EAAE,SAAS;CACvD,SAAS,eAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;CACzC,MAAM,eAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AACvC,CAAC;AAID,IAAa,sBAAsB,WAAW,OAAO,EACnD,SAAS,eAAE,OAAO,EACpB,CAAC;AAQD,IAAa,YANgB,eAAE,OAAO;CACpC,MAAM,eAAE,OAAO;CACf,SAAS,eAAE,OAAO,UAAU;CAC5B,YAAY,eAAE,MAAM,eAAE,OAAO,CAAC,EAAE,SAAS;AAC3C,CAEyB,EAAc,OAAO,EAC5C,QAAQ,eAAE,OAAO,EACnB,CAAC;AAED,IAAa,qBAAqB,UAAU,OAAO;CACjD,KAAK,eAAE,OAAO;CAGd,WAAW,eAAE,OAAO,EAAE,SAAS;CAC/B,SAAS,eAAE,OAAO,mBAAmB;CACrC,iBAAiB,wBAAwB,SAAS;CAClD,cAAc,eAAE,OAAO,eAAE,OAAO,EAAC,WAAW,eAAE,OAAO,EAAE,SAAS,EAAC,CAAC,CAAC;AACrE,CAAC;AA8CD,IAAa,kBAAkB,aAAe,OAAO,MAAM;AAyB3D,IAAM,4BAAyD,eAAE,MAC/D,eAAiB;CAAC,MAAM,eAAE,QAAQ,SAAS;CAAG,OAAO,eAAE,QAAQ;AAAC,CAAC,GACjE,eAAiB;CAAC,MAAM,eAAE,QAAQ,SAAS;CAAG,OAAO,eAAE,OAAO;AAAC,CAAC,GAChE,eAAiB;CAAC,MAAM,eAAE,QAAQ,QAAQ;CAAG,OAAO,eAAE,OAAO;AAAC,CAAC,CACjE;AAEA,IAAa,uBAA+C,eAAE,WAC5D,eAAE,MACA,eAAiB;CACf,MAAM,aAAe,OAAO,IAAI;CAChC,YAAY,cAAgB,oBAAoB;AAClD,CAAC,GACD,eAAiB;CACf,MAAM,eAAE,QAAQ,KAAK;CACrB,WAAW;AACb,CAAC,GACD,eAAiB;CACf,MAAM,eAAE,QAAQ,WAAW;CAC3B,QAAQ,eAAE,OAAO;CACjB,IAAI,aAAe,WAAW,aAAa;AAC7C,CAAC,GACD,eAAiB;CACf,MAAM,eAAE,QAAQ,YAAY;CAC5B,QAAQ,eAAE,OAAO;CACjB,IAAI,aAAe,KAAK,MAAM,KAAK,MAAM,KAAK,IAAI;CAClD,OAAO;AACT,CAAC,CACH,CACF;AAcA,IAAa,YAZgB,eAAE,OAAO;CACpC,MAAM,eAAE,OAAO;CACf,WAAW,eAAE,OAAO;CACpB,QAAQ,eAAE,QAAQ;CAClB,SAAS,eAAE,OAAO,eAAe;CACjC,WAAW,qBAAqB,SAAS;AAC3C,CAMyB,EAAc,OAAO,EAC5C,QAAQ,eAAE,OAAO,EACnB,CAAC;AAID,IAAa,qBAAqB,UAAU,OAAO;CACjD,mBAAmB,eAAE,QAAQ,EAAE,SAAS;CACxC,cAAc,eAAE,QAAQ,EAAE,SAAS;CACnC,aAAa,eAAE,QAAQ,EAAE,SAAS;AACpC,CAAC"}
1
+ {"version":3,"file":"specs.js","names":["v.literalUnion","v","v.readonlyObject","v.readonlyArray"],"sources":["../../../../../zero-cache/src/db/specs.ts"],"sourcesContent":["import type {DeepReadonly} from '../../../shared/src/json.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue} from '../../../zero-schema/src/table-schema.ts';\nimport * as PostgresReplicaIdentity from './postgres-replica-identity-enum.ts';\nimport * as PostgresTypeClass from './postgres-type-class-enum.ts';\n\nexport const pgTypeClassSchema = v.literalUnion(\n PostgresTypeClass.Base,\n PostgresTypeClass.Composite,\n PostgresTypeClass.Domain,\n PostgresTypeClass.Enum,\n PostgresTypeClass.Pseudo,\n PostgresTypeClass.Range,\n PostgresTypeClass.Multirange,\n);\n\nconst pgReplicaIdentitySchema = v.literalUnion(\n PostgresReplicaIdentity.Default,\n PostgresReplicaIdentity.Nothing,\n PostgresReplicaIdentity.Full,\n PostgresReplicaIdentity.Index,\n);\n\nexport const columnSpec = v.object({\n pos: v.number(),\n dataType: v.string(),\n pgTypeClass: pgTypeClassSchema.optional(),\n\n // If the column is an array, this will be the type of the\n // elements in the array. If the column is not an array,\n // this will be null.\n elemPgTypeClass: pgTypeClassSchema.nullable().optional(),\n\n characterMaximumLength: v.number().nullable().optional(),\n notNull: v.boolean().nullable().optional(),\n dflt: v.string().nullable().optional(),\n});\n\nexport type ColumnSpec = Readonly<v.Infer<typeof columnSpec>>;\n\nexport const publishedColumnSpec = columnSpec.extend({\n typeOID: v.number(),\n});\n\nexport const liteTableSpec = v.object({\n name: v.string(),\n columns: v.record(columnSpec),\n primaryKey: v.array(v.string()).optional(),\n});\n\nexport const tableSpec = liteTableSpec.extend({\n schema: v.string(),\n});\n\nexport const publishedTableSpec = tableSpec.extend({\n oid: v.number(),\n // Always present for new instances (e.g. from DDL triggers), but\n // may from `initialSchema` object stored in the `replicas` table.\n schemaOID: v.number().optional(),\n columns: v.record(publishedColumnSpec),\n replicaIdentity: pgReplicaIdentitySchema.optional(),\n publications: v.record(v.object({rowFilter: v.string().nullable()})),\n});\n\nexport type MutableLiteTableSpec = v.Infer<typeof liteTableSpec>;\n\nexport type LiteTableSpec = Readonly<MutableLiteTableSpec>;\n\nexport type LiteTableSpecWithKeysAndVersion = Omit<\n LiteTableSpec,\n 'primaryKey'\n> & {\n /**\n * All keys associated with a unique index. Includes indexes with\n * nullable columns.\n */\n uniqueKeys: PrimaryKey[];\n\n /**\n * The key selected to act as the \"primary key\". Primary keys\n * are not explicitly set on the replica, but an appropriate\n * unique index is required.\n */\n primaryKey: PrimaryKey; // note: required\n\n /**\n * All keys associated with a unique index over non-null\n * columns, i.e. suitable as a primary key.\n */\n allPotentialPrimaryKeys: PrimaryKey[];\n\n /**\n * The minimum `_0_version` value for every row in the table. If this is\n * present, `_0_version` value in the row itself should only be used if\n * it is greater (i.e. later) than the `minRowVersion`.\n */\n minRowVersion: string | null;\n};\n\nexport type LiteAndZqlSpec = {\n tableSpec: LiteTableSpecWithKeysAndVersion;\n zqlSpec: Record<string, SchemaValue>;\n};\n\nexport type TableSpec = Readonly<v.Infer<typeof tableSpec>>;\n\nexport type PublishedTableSpec = Readonly<v.Infer<typeof publishedTableSpec>>;\n\nexport const directionSchema = v.literalUnion('ASC', 'DESC');\n\nexport type IndexPredicateValue =\n | {readonly type: 'boolean'; readonly value: boolean}\n | {readonly type: 'integer'; readonly value: string}\n | {readonly type: 'string'; readonly value: string};\n\nexport type IndexPredicate =\n | {\n readonly type: 'and' | 'or';\n readonly conditions: readonly IndexPredicate[];\n }\n | {readonly type: 'not'; readonly condition: IndexPredicate}\n | {\n readonly type: 'null-test';\n readonly column: string;\n readonly op: 'IS NULL' | 'IS NOT NULL';\n }\n | {\n readonly type: 'comparison';\n readonly column: string;\n readonly op: '=' | '<>' | '<' | '<=' | '>' | '>=';\n readonly value: IndexPredicateValue;\n };\n\nconst indexPredicateValueSchema: v.Type<IndexPredicateValue> = v.union(\n v.readonlyObject({type: v.literal('boolean'), value: v.boolean()}),\n v.readonlyObject({type: v.literal('integer'), value: v.string()}),\n v.readonlyObject({type: v.literal('string'), value: v.string()}),\n);\n\nexport const indexPredicateSchema: v.Type<IndexPredicate> = v.lazy(() =>\n v.union(\n v.readonlyObject({\n type: v.literalUnion('and', 'or'),\n conditions: v.readonlyArray(indexPredicateSchema),\n }),\n v.readonlyObject({\n type: v.literal('not'),\n condition: indexPredicateSchema,\n }),\n v.readonlyObject({\n type: v.literal('null-test'),\n column: v.string(),\n op: v.literalUnion('IS NULL', 'IS NOT NULL'),\n }),\n v.readonlyObject({\n type: v.literal('comparison'),\n column: v.string(),\n op: v.literalUnion('=', '<>', '<', '<=', '>', '>='),\n value: indexPredicateValueSchema,\n }),\n ),\n);\n\nexport const liteIndexSpec = v.object({\n name: v.string(),\n tableName: v.string(),\n unique: v.boolean(),\n columns: v.record(directionSchema),\n predicate: indexPredicateSchema.optional(),\n});\n\nexport type MutableLiteIndexSpec = v.Infer<typeof liteIndexSpec>;\n\nexport type LiteIndexSpec = Readonly<MutableLiteIndexSpec>;\n\nexport const indexSpec = liteIndexSpec.extend({\n schema: v.string(),\n});\n\nexport type IndexSpec = DeepReadonly<v.Infer<typeof indexSpec>>;\n\nexport const publishedIndexSpec = indexSpec.extend({\n isReplicaIdentity: v.boolean().optional(),\n isPrimaryKey: v.boolean().optional(),\n isImmediate: v.boolean().optional(),\n});\n\nexport type PublishedIndexSpec = DeepReadonly<\n v.Infer<typeof publishedIndexSpec>\n>;\n"],"mappings":";;AAOA,IAAa,oBAAoBA,aAC/B,KACA,KACA,KACA,KACA,KACA,KACA,GACF;AAEA,IAAM,0BAA0BA,aAC9B,KACA,KACA,KACA,GACF;AAEA,IAAa,aAAaC,eAAE,OAAO;CACjC,KAAKA,eAAE,OAAO;CACd,UAAUA,eAAE,OAAO;CACnB,aAAa,kBAAkB,SAAS;CAKxC,iBAAiB,kBAAkB,SAAS,CAAC,CAAC,SAAS;CAEvD,wBAAwBA,eAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CACvD,SAASA,eAAE,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CACzC,MAAMA,eAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;AACvC,CAAC;AAID,IAAa,sBAAsB,WAAW,OAAO,EACnD,SAASA,eAAE,OAAO,EACpB,CAAC;AAED,IAAa,gBAAgBA,eAAE,OAAO;CACpC,MAAMA,eAAE,OAAO;CACf,SAASA,eAAE,OAAO,UAAU;CAC5B,YAAYA,eAAE,MAAMA,eAAE,OAAO,CAAC,CAAC,CAAC,SAAS;AAC3C,CAAC;AAED,IAAa,YAAY,cAAc,OAAO,EAC5C,QAAQA,eAAE,OAAO,EACnB,CAAC;AAED,IAAa,qBAAqB,UAAU,OAAO;CACjD,KAAKA,eAAE,OAAO;CAGd,WAAWA,eAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,SAASA,eAAE,OAAO,mBAAmB;CACrC,iBAAiB,wBAAwB,SAAS;CAClD,cAAcA,eAAE,OAAOA,eAAE,OAAO,EAAC,WAAWA,eAAE,OAAO,CAAC,CAAC,SAAS,EAAC,CAAC,CAAC;AACrE,CAAC;AA8CD,IAAa,kBAAkBD,aAAe,OAAO,MAAM;AAyB3D,IAAM,4BAAyDC,eAAE,MAC/DC,eAAiB;CAAC,MAAMD,eAAE,QAAQ,SAAS;CAAG,OAAOA,eAAE,QAAQ;AAAC,CAAC,GACjEC,eAAiB;CAAC,MAAMD,eAAE,QAAQ,SAAS;CAAG,OAAOA,eAAE,OAAO;AAAC,CAAC,GAChEC,eAAiB;CAAC,MAAMD,eAAE,QAAQ,QAAQ;CAAG,OAAOA,eAAE,OAAO;AAAC,CAAC,CACjE;AAEA,IAAa,uBAA+CA,eAAE,WAC5DA,eAAE,MACAC,eAAiB;CACf,MAAMF,aAAe,OAAO,IAAI;CAChC,YAAYG,cAAgB,oBAAoB;AAClD,CAAC,GACDD,eAAiB;CACf,MAAMD,eAAE,QAAQ,KAAK;CACrB,WAAW;AACb,CAAC,GACDC,eAAiB;CACf,MAAMD,eAAE,QAAQ,WAAW;CAC3B,QAAQA,eAAE,OAAO;CACjB,IAAID,aAAe,WAAW,aAAa;AAC7C,CAAC,GACDE,eAAiB;CACf,MAAMD,eAAE,QAAQ,YAAY;CAC5B,QAAQA,eAAE,OAAO;CACjB,IAAID,aAAe,KAAK,MAAM,KAAK,MAAM,KAAK,IAAI;CAClD,OAAO;AACT,CAAC,CACH,CACF;AAEA,IAAa,gBAAgBC,eAAE,OAAO;CACpC,MAAMA,eAAE,OAAO;CACf,WAAWA,eAAE,OAAO;CACpB,QAAQA,eAAE,QAAQ;CAClB,SAASA,eAAE,OAAO,eAAe;CACjC,WAAW,qBAAqB,SAAS;AAC3C,CAAC;AAMD,IAAa,YAAY,cAAc,OAAO,EAC5C,QAAQA,eAAE,OAAO,EACnB,CAAC;AAID,IAAa,qBAAqB,UAAU,OAAO;CACjD,mBAAmBA,eAAE,QAAQ,CAAC,CAAC,SAAS;CACxC,cAAcA,eAAE,QAAQ,CAAC,CAAC,SAAS;CACnC,aAAaA,eAAE,QAAQ,CAAC,CAAC,SAAS;AACpC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-corruption.js","names":[],"sources":["../../../../../zero-cache/src/db/sqlite-corruption.ts"],"sourcesContent":["import {existsSync, statSync} from 'node:fs';\nimport type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {Database} from '../../../zqlite/src/db.ts';\n\n// Matches primary corruption codes and extended variants (e.g.\n// SQLITE_CORRUPT_INDEX, SQLITE_CORRUPT_VTAB, SQLITE_CORRUPT_SEQUENCE), which\n// the driver reports because it enables sqlite3_extended_result_codes().\nconst SQLITE_CORRUPTION_CODE = /^SQLITE_(CORRUPT(_|$)|NOTADB$)/;\nconst SQLITE_CORRUPTION_MESSAGE =\n /database disk image is malformed|file is not a database|database is malformed|malformed database/i;\nconst SQLITE_FILE_SUFFIXES = ['', '-wal', '-wal2', '-shm', '-journal'] as const;\nconst MAX_LOGGED_CHECK_ISSUES = 20;\n\ntype ErrorSummary = {\n readonly name: string;\n readonly message: string;\n readonly code?: string | undefined;\n readonly cause?: ErrorSummary | string | undefined;\n};\n\ntype SQLiteFileStats = {\n readonly suffix: string;\n readonly path: string;\n readonly sizeBytes: number;\n readonly mtimeMs: number;\n};\n\ntype CheckSummary = {\n readonly status: 'ok' | 'failed';\n readonly rowCount: number;\n readonly issueCount: number;\n readonly loggedIssueCount: number;\n readonly truncated: boolean;\n readonly issues: string[];\n};\n\ntype DiagnosticTarget = {\n readonly debugName: string;\n readonly dbPath: string;\n};\n\ntype RegisteredDiagnosticTarget = DiagnosticTarget & {\n readonly corruptionChecks: SQLiteCorruptionChecks;\n};\n\nconst diagnosticTargets = new Map<number, RegisteredDiagnosticTarget>();\nlet nextDiagnosticTargetID = 0;\n\nexport type SQLiteCorruptionChecks = boolean;\n\nexport function isSQLiteCorruption(e: unknown): boolean {\n return getSQLiteCorruptionCode(e) !== undefined;\n}\n\nexport function registerSQLiteCorruptionDiagnosticTarget(\n target: DiagnosticTarget,\n corruptionChecks: SQLiteCorruptionChecks = false,\n): () => void {\n const id = nextDiagnosticTargetID++;\n diagnosticTargets.set(id, {...target, corruptionChecks});\n return () => diagnosticTargets.delete(id);\n}\n\nexport function logLastChanceSQLiteCorruptionDiagnostics(\n lc: LogContext,\n cause: unknown,\n): boolean {\n if (!isSQLiteCorruption(cause) || diagnosticTargets.size === 0) {\n return false;\n }\n for (const {\n debugName,\n dbPath,\n corruptionChecks,\n } of diagnosticTargets.values()) {\n logSQLiteCorruptionDiagnostics(\n lc,\n debugName,\n dbPath,\n cause,\n corruptionChecks,\n );\n }\n return true;\n}\n\nexport function logSQLiteCorruptionDiagnostics(\n lc: LogContext,\n debugName: string,\n dbPath: string,\n cause: unknown,\n corruptionChecks: SQLiteCorruptionChecks = false,\n): void {\n const start = Date.now();\n lc.error?.('SQLite replica corruption detected', {\n debugName,\n dbPath,\n cause: summarizeError(cause),\n });\n\n logDiagnostic(lc, debugName, 'file-stats', () => ({\n files: getSQLiteFileStats(dbPath),\n }));\n\n let db: Database | undefined;\n try {\n db = new Database(lc, dbPath, {readonly: true});\n db.pragma('busy_timeout = 1000');\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step: 'open-readonly',\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n return;\n }\n\n try {\n logDiagnostic(lc, debugName, 'basic-pragmas', () => ({\n pragmas: getBasicPragmas(db),\n }));\n logDiagnostic(lc, debugName, 'replica-metadata', () => ({\n metadata: getReplicaMetadata(db),\n }));\n if (corruptionChecks) {\n logDiagnostic(lc, debugName, 'quick-check', () => ({\n check: runCheck(db, 'quick_check'),\n }));\n logDiagnostic(lc, debugName, 'integrity-check', () => ({\n check: runCheck(db, 'integrity_check'),\n }));\n }\n lc.error?.('SQLite corruption diagnostics completed', {\n debugName,\n dbPath,\n durationMs: Date.now() - start,\n });\n } finally {\n db.close();\n }\n}\n\nfunction getSQLiteCorruptionCode(e: unknown): string | undefined {\n let current = e;\n const seen = new Set<unknown>();\n\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current);\n\n const code = getErrorCode(current);\n if (code !== undefined && SQLITE_CORRUPTION_CODE.test(code)) {\n return code;\n }\n\n if (current instanceof SqliteError || current instanceof Error) {\n if (SQLITE_CORRUPTION_MESSAGE.test(current.message)) {\n return code ?? 'SQLITE_CORRUPT';\n }\n current = getErrorCause(current);\n continue;\n }\n\n break;\n }\n\n return undefined;\n}\n\nfunction getErrorCode(e: unknown): string | undefined {\n if (e && typeof e === 'object' && 'code' in e) {\n const code = (e as {readonly code?: unknown}).code;\n return typeof code === 'string' ? code : undefined;\n }\n return undefined;\n}\n\nfunction getErrorCause(e: Error): unknown {\n return (e as {readonly cause?: unknown}).cause;\n}\n\nfunction summarizeError(e: unknown, depth = 0): ErrorSummary | string {\n if (!(e instanceof Error)) {\n return String(e);\n }\n const cause = getErrorCause(e);\n return {\n name: e.name,\n message: e.message,\n code: getErrorCode(e),\n cause:\n cause === undefined || depth >= 4\n ? undefined\n : summarizeError(cause, depth + 1),\n };\n}\n\nfunction getSQLiteFileStats(dbPath: string): SQLiteFileStats[] {\n const stats: SQLiteFileStats[] = [];\n for (const suffix of SQLITE_FILE_SUFFIXES) {\n const path = `${dbPath}${suffix}`;\n if (!existsSync(path)) {\n continue;\n }\n const stat = statSync(path);\n stats.push({\n suffix,\n path,\n sizeBytes: stat.size,\n mtimeMs: stat.mtimeMs,\n });\n }\n return stats;\n}\n\nfunction getBasicPragmas(db: Database) {\n return {\n pageSize: firstPragmaValue<number>(db, 'page_size', 'page_size'),\n pageCount: firstPragmaValue<number>(db, 'page_count', 'page_count'),\n freelistCount: firstPragmaValue<number>(\n db,\n 'freelist_count',\n 'freelist_count',\n ),\n journalMode: firstPragmaValue<string>(db, 'journal_mode', 'journal_mode'),\n schemaVersion: firstPragmaValue<number>(\n db,\n 'schema_version',\n 'schema_version',\n ),\n userVersion: firstPragmaValue<number>(db, 'user_version', 'user_version'),\n applicationID: firstPragmaValue<number>(\n db,\n 'application_id',\n 'application_id',\n ),\n };\n}\n\nfunction getReplicaMetadata(db: Database) {\n return {\n versionHistory: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT dataVersion, schemaVersion, minSafeVersion\n FROM \"_zero.versionHistory\"`,\n )\n .get(),\n ),\n subscriptionState: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT c.replicaVersion, s.stateVersion as watermark\n FROM \"_zero.replicationConfig\" as c\n JOIN \"_zero.replicationState\" as s\n ON c.lock = s.lock`,\n )\n .get(),\n ),\n sqliteCatalogCounts: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT type, count(*) as count\n FROM sqlite_master\n GROUP BY type\n ORDER BY type`,\n )\n .all(),\n ),\n };\n}\n\nfunction queryDiagnostic<T>(query: () => T):\n | {readonly ok: true; readonly value: T}\n | {\n readonly ok: false;\n readonly error: ErrorSummary | string;\n } {\n try {\n return {ok: true, value: query()};\n } catch (e) {\n return {ok: false, error: summarizeError(e)};\n }\n}\n\nfunction firstPragmaValue<T>(\n db: Database,\n pragma: string,\n column: string,\n): T | undefined {\n const [row] = db.pragma<Record<string, T>>(pragma);\n return row?.[column];\n}\n\nfunction runCheck(db: Database, pragma: 'quick_check' | 'integrity_check') {\n const rows = db.pragma<Record<string, string>>(pragma);\n const issues = rows\n .map(row => row[pragma] ?? Object.values(row)[0] ?? '')\n .filter(issue => issue !== '' && issue !== 'ok');\n const loggedIssues = issues.slice(0, MAX_LOGGED_CHECK_ISSUES);\n return {\n status: issues.length === 0 ? 'ok' : 'failed',\n rowCount: rows.length,\n issueCount: issues.length,\n loggedIssueCount: loggedIssues.length,\n truncated: issues.length > loggedIssues.length,\n issues: loggedIssues,\n } satisfies CheckSummary;\n}\n\nfunction logDiagnostic<T>(\n lc: LogContext,\n debugName: string,\n step: string,\n fn: () => T,\n): void {\n const start = Date.now();\n try {\n const result = fn();\n lc.error?.('SQLite corruption diagnostic', {\n debugName,\n step,\n durationMs: Date.now() - start,\n result,\n });\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step,\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-corruption.js","names":[],"sources":["../../../../../zero-cache/src/db/sqlite-corruption.ts"],"sourcesContent":["import {existsSync, statSync} from 'node:fs';\nimport type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {Database} from '../../../zqlite/src/db.ts';\n\n// Matches primary corruption codes and extended variants (e.g.\n// SQLITE_CORRUPT_INDEX, SQLITE_CORRUPT_VTAB, SQLITE_CORRUPT_SEQUENCE), which\n// the driver reports because it enables sqlite3_extended_result_codes().\nconst SQLITE_CORRUPTION_CODE = /^SQLITE_(CORRUPT(_|$)|NOTADB$)/;\nconst SQLITE_CORRUPTION_MESSAGE =\n /database disk image is malformed|file is not a database|database is malformed|malformed database/i;\nconst SQLITE_FILE_SUFFIXES = ['', '-wal', '-wal2', '-shm', '-journal'] as const;\nconst MAX_LOGGED_CHECK_ISSUES = 20;\n\ntype ErrorSummary = {\n readonly name: string;\n readonly message: string;\n readonly code?: string | undefined;\n readonly cause?: ErrorSummary | string | undefined;\n};\n\ntype SQLiteFileStats = {\n readonly suffix: string;\n readonly path: string;\n readonly sizeBytes: number;\n readonly mtimeMs: number;\n};\n\ntype CheckSummary = {\n readonly status: 'ok' | 'failed';\n readonly rowCount: number;\n readonly issueCount: number;\n readonly loggedIssueCount: number;\n readonly truncated: boolean;\n readonly issues: string[];\n};\n\ntype DiagnosticTarget = {\n readonly debugName: string;\n readonly dbPath: string;\n};\n\ntype RegisteredDiagnosticTarget = DiagnosticTarget & {\n readonly corruptionChecks: SQLiteCorruptionChecks;\n};\n\nconst diagnosticTargets = new Map<number, RegisteredDiagnosticTarget>();\nlet nextDiagnosticTargetID = 0;\n\nexport type SQLiteCorruptionChecks = boolean;\n\nexport function isSQLiteCorruption(e: unknown): boolean {\n return getSQLiteCorruptionCode(e) !== undefined;\n}\n\nexport function registerSQLiteCorruptionDiagnosticTarget(\n target: DiagnosticTarget,\n corruptionChecks: SQLiteCorruptionChecks = false,\n): () => void {\n const id = nextDiagnosticTargetID++;\n diagnosticTargets.set(id, {...target, corruptionChecks});\n return () => diagnosticTargets.delete(id);\n}\n\nexport function logLastChanceSQLiteCorruptionDiagnostics(\n lc: LogContext,\n cause: unknown,\n): boolean {\n if (!isSQLiteCorruption(cause) || diagnosticTargets.size === 0) {\n return false;\n }\n for (const {\n debugName,\n dbPath,\n corruptionChecks,\n } of diagnosticTargets.values()) {\n logSQLiteCorruptionDiagnostics(\n lc,\n debugName,\n dbPath,\n cause,\n corruptionChecks,\n );\n }\n return true;\n}\n\nexport function logSQLiteCorruptionDiagnostics(\n lc: LogContext,\n debugName: string,\n dbPath: string,\n cause: unknown,\n corruptionChecks: SQLiteCorruptionChecks = false,\n): void {\n const start = Date.now();\n lc.error?.('SQLite replica corruption detected', {\n debugName,\n dbPath,\n cause: summarizeError(cause),\n });\n\n logDiagnostic(lc, debugName, 'file-stats', () => ({\n files: getSQLiteFileStats(dbPath),\n }));\n\n let db: Database | undefined;\n try {\n db = new Database(lc, dbPath, {readonly: true});\n db.pragma('busy_timeout = 1000');\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step: 'open-readonly',\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n return;\n }\n\n try {\n logDiagnostic(lc, debugName, 'basic-pragmas', () => ({\n pragmas: getBasicPragmas(db),\n }));\n logDiagnostic(lc, debugName, 'replica-metadata', () => ({\n metadata: getReplicaMetadata(db),\n }));\n if (corruptionChecks) {\n logDiagnostic(lc, debugName, 'quick-check', () => ({\n check: runCheck(db, 'quick_check'),\n }));\n logDiagnostic(lc, debugName, 'integrity-check', () => ({\n check: runCheck(db, 'integrity_check'),\n }));\n }\n lc.error?.('SQLite corruption diagnostics completed', {\n debugName,\n dbPath,\n durationMs: Date.now() - start,\n });\n } finally {\n db.close();\n }\n}\n\nfunction getSQLiteCorruptionCode(e: unknown): string | undefined {\n let current = e;\n const seen = new Set<unknown>();\n\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current);\n\n const code = getErrorCode(current);\n if (code !== undefined && SQLITE_CORRUPTION_CODE.test(code)) {\n return code;\n }\n\n if (current instanceof SqliteError || current instanceof Error) {\n if (SQLITE_CORRUPTION_MESSAGE.test(current.message)) {\n return code ?? 'SQLITE_CORRUPT';\n }\n current = getErrorCause(current);\n continue;\n }\n\n break;\n }\n\n return undefined;\n}\n\nfunction getErrorCode(e: unknown): string | undefined {\n if (e && typeof e === 'object' && 'code' in e) {\n const code = (e as {readonly code?: unknown}).code;\n return typeof code === 'string' ? code : undefined;\n }\n return undefined;\n}\n\nfunction getErrorCause(e: Error): unknown {\n return (e as {readonly cause?: unknown}).cause;\n}\n\nfunction summarizeError(e: unknown, depth = 0): ErrorSummary | string {\n if (!(e instanceof Error)) {\n return String(e);\n }\n const cause = getErrorCause(e);\n return {\n name: e.name,\n message: e.message,\n code: getErrorCode(e),\n cause:\n cause === undefined || depth >= 4\n ? undefined\n : summarizeError(cause, depth + 1),\n };\n}\n\nfunction getSQLiteFileStats(dbPath: string): SQLiteFileStats[] {\n const stats: SQLiteFileStats[] = [];\n for (const suffix of SQLITE_FILE_SUFFIXES) {\n const path = `${dbPath}${suffix}`;\n if (!existsSync(path)) {\n continue;\n }\n const stat = statSync(path);\n stats.push({\n suffix,\n path,\n sizeBytes: stat.size,\n mtimeMs: stat.mtimeMs,\n });\n }\n return stats;\n}\n\nfunction getBasicPragmas(db: Database) {\n return {\n pageSize: firstPragmaValue<number>(db, 'page_size', 'page_size'),\n pageCount: firstPragmaValue<number>(db, 'page_count', 'page_count'),\n freelistCount: firstPragmaValue<number>(\n db,\n 'freelist_count',\n 'freelist_count',\n ),\n journalMode: firstPragmaValue<string>(db, 'journal_mode', 'journal_mode'),\n schemaVersion: firstPragmaValue<number>(\n db,\n 'schema_version',\n 'schema_version',\n ),\n userVersion: firstPragmaValue<number>(db, 'user_version', 'user_version'),\n applicationID: firstPragmaValue<number>(\n db,\n 'application_id',\n 'application_id',\n ),\n };\n}\n\nfunction getReplicaMetadata(db: Database) {\n return {\n versionHistory: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT dataVersion, schemaVersion, minSafeVersion\n FROM \"_zero.versionHistory\"`,\n )\n .get(),\n ),\n subscriptionState: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT c.replicaVersion, s.stateVersion as watermark\n FROM \"_zero.replicationConfig\" as c\n JOIN \"_zero.replicationState\" as s\n ON c.lock = s.lock`,\n )\n .get(),\n ),\n sqliteCatalogCounts: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT type, count(*) as count\n FROM sqlite_master\n GROUP BY type\n ORDER BY type`,\n )\n .all(),\n ),\n };\n}\n\nfunction queryDiagnostic<T>(query: () => T):\n | {readonly ok: true; readonly value: T}\n | {\n readonly ok: false;\n readonly error: ErrorSummary | string;\n } {\n try {\n return {ok: true, value: query()};\n } catch (e) {\n return {ok: false, error: summarizeError(e)};\n }\n}\n\nfunction firstPragmaValue<T>(\n db: Database,\n pragma: string,\n column: string,\n): T | undefined {\n const [row] = db.pragma<Record<string, T>>(pragma);\n return row?.[column];\n}\n\nfunction runCheck(db: Database, pragma: 'quick_check' | 'integrity_check') {\n const rows = db.pragma<Record<string, string>>(pragma);\n const issues = rows\n .map(row => row[pragma] ?? Object.values(row)[0] ?? '')\n .filter(issue => issue !== '' && issue !== 'ok');\n const loggedIssues = issues.slice(0, MAX_LOGGED_CHECK_ISSUES);\n return {\n status: issues.length === 0 ? 'ok' : 'failed',\n rowCount: rows.length,\n issueCount: issues.length,\n loggedIssueCount: loggedIssues.length,\n truncated: issues.length > loggedIssues.length,\n issues: loggedIssues,\n } satisfies CheckSummary;\n}\n\nfunction logDiagnostic<T>(\n lc: LogContext,\n debugName: string,\n step: string,\n fn: () => T,\n): void {\n const start = Date.now();\n try {\n const result = fn();\n lc.error?.('SQLite corruption diagnostic', {\n debugName,\n step,\n durationMs: Date.now() - start,\n result,\n });\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step,\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n }\n}\n"],"mappings":"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"}
@@ -88,8 +88,10 @@ var TransactionPool = class {
88
88
  if (numWorkers < this.#workers.length) await Promise.all(this.#workers);
89
89
  this.#lc.debug?.("transaction pool done");
90
90
  const elapsed = performance.now() - this.#start;
91
- if (elapsed > 6e4) if (this.#stmts > 0) this.#lc.warn?.(`finished long transaction with ${this.#stmts} statements (${elapsed.toFixed(3)} ms)`);
92
- else this.#lc.warn?.(`finished long read transaction (${elapsed.toFixed(3)} ms)`);
91
+ if (elapsed > 6e4) {
92
+ if (this.#stmts > 0) this.#lc.warn?.(`finished long transaction with ${this.#stmts} statements (${elapsed.toFixed(3)} ms)`);
93
+ else this.#lc.warn?.(`finished long read transaction (${elapsed.toFixed(3)} ms)`);
94
+ }
93
95
  }
94
96
  #addWorker(db) {
95
97
  const id = this.#workers.length + 1;
@@ -370,6 +372,6 @@ var TIMEOUT_TASKS = {
370
372
  }
371
373
  };
372
374
  //#endregion
373
- export { TransactionPool, importSnapshot, sharedSnapshot };
375
+ export { ControlFlowError, TIMEOUT_TASKS, TransactionPool, importSnapshot, sharedSnapshot };
374
376
 
375
377
  //# sourceMappingURL=transaction-pool.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transaction-pool.js","names":["#mode","#init","#cleanup","#tasks","#workers","#initialWorkers","#maxWorkers","#timeoutTask","#lc","#stmtRunner","#numWorkers","#db","#addWorker","#start","#stmts","#numWorking","#failure","#done","#process","#readRunner"],"sources":["../../../../../zero-cache/src/db/transaction-pool.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Resolver, resolver} from '@rocicorp/resolver';\nimport type postgres from 'postgres';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport type * as Mode from './mode-enum.ts';\nimport {runTx} from './run-transaction.ts';\n\ntype Mode = Enum<typeof Mode>;\n\ntype MaybePromise<T> = Promise<T> | T;\n\nexport type Statement =\n | postgres.PendingQuery<(postgres.Row & Iterable<postgres.Row>)[]>\n | postgres.PendingQuery<postgres.Row[]>;\n\n/**\n * A {@link Task} is logic run from within a transaction in a {@link TransactionPool}.\n * It returns a list of `Statements` that the transaction executes asynchronously and\n * awaits when it receives the 'done' signal.\n *\n */\nexport type Task = (\n tx: PostgresTransaction,\n lc: LogContext,\n) => MaybePromise<Statement[]>;\n\n/**\n * A {@link ReadTask} is run from within a transaction, but unlike a {@link Task},\n * the results of a ReadTask are opaque to the TransactionPool and returned to the\n * caller of {@link TransactionPool.processReadTask}.\n */\nexport type ReadTask<T> = (\n tx: PostgresTransaction,\n lc: LogContext,\n) => MaybePromise<T>;\n\nexport type Options = {\n mode: Mode;\n\n /**\n * A {@link Task} that is run in each Transaction before it begins\n * processing general tasks. This can be used to to set the transaction\n * mode, export/set snapshots, etc. This will be run even if\n * {@link fail} has been called on the pool.\n */\n init?: Task | undefined;\n\n /**\n * A {@link Task} that is run in each Transaction before it closes.\n * This will be run even if {@link fail} has been called, or if a\n * preceding Task threw an Error.\n */\n cleanup?: Task | undefined;\n\n /**\n * The initial number of transaction workers to process tasks.\n * This is the steady state number of workers that will be kept\n * alive if the TransactionPool is long lived.\n * This must be greater than 0. Defaults to 1.\n */\n initialWorkers?: number;\n\n /**\n * When specified, allows the pool to grow to `maxWorkers`. This\n * must be greater than or equal to `initialWorkers`. On-demand\n * workers will be shut down after an idle timeout of 5 seconds.\n */\n maxWorkers?: number;\n};\n\n/**\n * A TransactionPool is a pool of one or more {@link postgres.TransactionSql}\n * objects that participate in processing a dynamic queue of tasks.\n *\n * This can be used for serializing a set of tasks that arrive asynchronously\n * to a single transaction (for writing) or performing parallel reads across\n * multiple connections at the same snapshot (e.g. read only snapshot transactions).\n */\nexport class TransactionPool {\n #lc: LogContext;\n readonly #mode: Mode;\n readonly #init: TaskRunner | undefined;\n readonly #cleanup: TaskRunner | undefined;\n readonly #tasks = new Queue<TaskRunner | Error | 'done'>();\n readonly #workers: Promise<unknown>[] = [];\n readonly #initialWorkers: number;\n readonly #maxWorkers: number;\n readonly #timeoutTask: TimeoutTasks;\n #numWorkers: number;\n #numWorking = 0;\n #db: PostgresDB | undefined; // set when running. stored to allow adaptive pool sizing.\n\n #done = false;\n #failure: Error | undefined;\n\n constructor(\n lc: LogContext,\n opts: Options,\n timeoutTasks = TIMEOUT_TASKS, // Overridden for tests.\n ) {\n const {\n mode,\n init,\n cleanup,\n initialWorkers = 1,\n maxWorkers = initialWorkers,\n } = opts;\n assert(initialWorkers > 0, 'initialWorkers must be positive');\n assert(\n maxWorkers >= initialWorkers,\n 'maxWorkers must be >= initialWorkers',\n );\n\n this.#lc = lc;\n this.#mode = mode;\n this.#init = init ? this.#stmtRunner(init) : undefined;\n this.#cleanup = cleanup ? this.#stmtRunner(cleanup) : undefined;\n this.#initialWorkers = initialWorkers;\n this.#numWorkers = initialWorkers;\n this.#maxWorkers = maxWorkers;\n this.#timeoutTask = timeoutTasks;\n }\n\n /**\n * Starts the pool of workers to process Tasks with transactions opened from the\n * specified {@link db}.\n */\n run(db: PostgresDB): this {\n assert(!this.#db, 'already running');\n this.#db = db;\n for (let i = 0; i < this.#numWorkers; i++) {\n this.#addWorker(db);\n }\n return this;\n }\n\n /**\n * Adds context parameters to internal LogContext. This is useful for context values that\n * are not known when the TransactionPool is constructed (e.g. determined after a database\n * call when the pool is running).\n *\n * Returns an object that can be used to add more parameters.\n */\n addLoggingContext(key: string, value: string) {\n this.#lc = this.#lc.withContext(key, value);\n\n return {\n addLoggingContext: (key: string, value: string) =>\n this.addLoggingContext(key, value),\n };\n }\n\n /**\n * Returns a promise that:\n *\n * * resolves after {@link setDone} has been called (or the the pool as been {@link unref}ed\n * to a 0 ref count), once all added tasks have been processed and all transactions have been\n * committed or closed.\n *\n * * rejects if processing was aborted with {@link fail} or if processing any of\n * the tasks resulted in an error. All uncommitted transactions will have been\n * rolled back.\n *\n * Note that partial failures are possible if processing writes with multiple workers\n * (e.g. `setDone` is called, allowing some workers to commit, after which other\n * workers encounter errors). Using a TransactionPool in this manner does not make\n * sense in terms of transactional semantics, and is thus not recommended.\n *\n * For reads, however, multiple workers is useful for performing parallel reads\n * at the same snapshot. See {@link synchronizedSnapshots} for an example.\n * Resolves or rejects when all workers are done or failed.\n */\n async done() {\n const numWorkers = this.#workers.length;\n await Promise.all(this.#workers);\n\n if (numWorkers < this.#workers.length) {\n // If workers were added after the initial set, they must be awaited to ensure\n // that the results (i.e. rejections) of all workers are accounted for. This only\n // needs to be re-done once, because the fact that the first `await` completed\n // guarantees that the pool is in a terminal state and no new workers can be added.\n await Promise.all(this.#workers);\n }\n this.#lc.debug?.('transaction pool done');\n\n const elapsed = performance.now() - this.#start;\n if (elapsed > 60_000) {\n if (this.#stmts > 0) {\n this.#lc.warn?.(\n `finished long transaction with ${this.#stmts} statements (${elapsed.toFixed(3)} ms)`,\n );\n } else {\n this.#lc.warn?.(\n `finished long read transaction (${elapsed.toFixed(3)} ms)`,\n );\n }\n }\n }\n\n #addWorker(db: PostgresDB) {\n const id = this.#workers.length + 1;\n const lc = this.#lc.withContext('tx', id);\n\n const tt: TimeoutTask =\n this.#workers.length < this.#initialWorkers\n ? this.#timeoutTask.forInitialWorkers\n : this.#timeoutTask.forExtraWorkers;\n const {timeoutMs} = tt;\n const timeoutTask = tt.task === 'done' ? 'done' : this.#stmtRunner(tt.task);\n\n const worker = async (tx: PostgresTransaction) => {\n const start = performance.now();\n try {\n lc.debug?.('started transaction');\n\n let last: Promise<void> = promiseVoid;\n\n const executeTask = async (runner: TaskRunner) => {\n runner !== this.#init && this.#numWorking++;\n const {pending} = await runner.run(tx, lc, () => {\n runner !== this.#init && this.#numWorking--;\n });\n last = pending ?? last;\n };\n\n let task: TaskRunner | Error | 'done' =\n this.#init ?? (await this.#tasks.dequeue(timeoutTask, timeoutMs));\n\n try {\n while (task !== 'done') {\n if (\n task instanceof Error ||\n (task !== this.#init && this.#failure)\n ) {\n throw this.#failure ?? task;\n }\n await executeTask(task);\n\n // await the next task.\n task = await this.#tasks.dequeue(timeoutTask, timeoutMs);\n }\n } finally {\n // Execute the cleanup task even on failure.\n if (this.#cleanup) {\n await executeTask(this.#cleanup);\n }\n }\n\n const elapsed = performance.now() - start;\n lc.debug?.(`closing transaction (${elapsed.toFixed(3)} ms)`);\n // Given the semantics of a Postgres transaction, the last statement\n // will only succeed if all of the preceding statements succeeded.\n return last;\n } catch (e) {\n if (e !== this.#failure) {\n this.fail(e); // A failure in any worker should fail the pool.\n }\n throw e;\n }\n };\n\n const workerTx = runTx(db, worker, {mode: this.#mode})\n .catch(e => {\n if (e instanceof RollbackSignal) {\n // A RollbackSignal is used to gracefully rollback the postgres.js\n // transaction block. It should not be thrown up to the application.\n lc.debug?.('aborted transaction');\n } else {\n throw e;\n }\n })\n .finally(() => this.#numWorkers--);\n\n // Attach a rejection handler immediately to prevent unhandledRejections.\n // The application will handle errors when it awaits processReadTask()\n // or done().\n workerTx.catch(() => {});\n\n this.#workers.push(workerTx);\n\n // After adding the worker, enqueue a terminal signal if we are in either of the\n // terminal states (both of which prevent more tasks from being enqueued), to ensure\n // that the added worker eventually exits.\n if (this.#done) {\n this.#tasks.enqueue('done');\n }\n if (this.#failure) {\n this.#tasks.enqueue(this.#failure);\n }\n }\n\n /**\n * Processes the statements produced by the specified {@link Task},\n * returning a Promise that resolves when the statements are either processed\n * by the database or rejected.\n *\n * Note that statement failures will result in failing the entire\n * TransactionPool (per transaction semantics). However, the returned Promise\n * itself will resolve rather than reject. As such, it is fine to ignore\n * returned Promises in order to pipeline requests to the database. It is\n * recommended to occasionally await them (e.g. after some threshold) in\n * order to avoid memory blowup in the case of database slowness.\n */\n process(task: Task): Promise<void> {\n const r = resolver<void>();\n this.#process(this.#stmtRunner(task, r));\n return r.promise;\n }\n\n readonly #start = performance.now();\n #stmts = 0;\n\n /**\n * Implements the semantics specified in {@link process()}.\n *\n * Specifically:\n * * `freeWorker()` is called as soon as the statements are produced,\n * allowing them to be pipelined to the database.\n * * Statement errors result in failing the transaction pool.\n * * The client-supplied Resolver resolves on success or failure;\n * it is never rejected.\n */\n #stmtRunner(task: Task, r: {resolve: () => void} = resolver()): TaskRunner {\n return {\n run: async (tx, lc, freeWorker) => {\n let stmts: Statement[];\n try {\n stmts = await task(tx, lc);\n } catch (e) {\n r.resolve();\n throw e;\n } finally {\n freeWorker();\n }\n\n if (stmts.length === 0) {\n r.resolve();\n return {pending: null};\n }\n\n // Execute the statements (i.e. send to the db) immediately.\n // The last result is returned for the worker to await before\n // closing the transaction.\n const last = stmts.reduce((_, stmt) => {\n const query = stmt as unknown as Query;\n const index = ++this.#stmts;\n return stmt\n .execute()\n .then(() => {\n if (index % 1000 === 0) {\n const log = index % 10000 === 0 ? 'info' : 'debug';\n lc[log]?.(\n `executed ${this.#stmts}th statement (${(performance.now() - this.#start).toFixed(3)} ms)`,\n {statement: query.string},\n );\n }\n })\n .catch(e => this.fail(e));\n }, promiseVoid);\n return {pending: last.finally(r.resolve)};\n },\n rejected: r.resolve,\n };\n }\n\n /**\n * Processes and returns the result of executing the {@link ReadTask} from\n * within the transaction. An error thrown by the task will result in\n * rejecting the returned Promise, but will not affect the transaction pool\n * itself.\n */\n processReadTask<T>(readTask: ReadTask<T>): Promise<T> {\n const r = resolver<T>();\n this.#process(this.#readRunner(readTask, r));\n return r.promise;\n }\n\n /**\n * Implements the semantics specified in {@link processReadTask()}.\n *\n * Specifically:\n * * `freeWorker()` is called as soon as the result is produced,\n * before resolving the client-supplied Resolver.\n * * Errors result in rejecting the client-supplied Resolver but\n * do not affect transaction pool.\n */\n #readRunner<T>(readTask: ReadTask<T>, r: Resolver<T>): TaskRunner {\n return {\n run: async (tx, lc, freeWorker) => {\n let result: T;\n try {\n result = await readTask(tx, lc);\n freeWorker();\n r.resolve(result);\n } catch (e) {\n freeWorker();\n r.reject(e);\n }\n return {pending: null};\n },\n rejected: r.reject,\n };\n }\n\n #process(runner: TaskRunner): void {\n assert(!this.#done, 'already set done');\n if (this.#failure) {\n runner.rejected(this.#failure);\n return;\n }\n\n this.#tasks.enqueue(runner);\n\n // Check if the pool size can and should be increased.\n if (this.#numWorkers < this.#maxWorkers) {\n const outstanding = this.#tasks.size();\n\n if (outstanding > this.#numWorkers - this.#numWorking) {\n this.#db && this.#addWorker(this.#db);\n this.#numWorkers++;\n this.#lc.debug?.(`Increased pool size to ${this.#numWorkers}`);\n }\n }\n }\n\n /**\n * Ends all workers with a ROLLBACK. Throws if the pool is already done\n * or aborted.\n */\n abort() {\n this.fail(new RollbackSignal());\n }\n\n /**\n * Signals to all workers to end their transaction once all pending tasks have\n * been completed. Throws if the pool is already done or aborted.\n */\n setDone() {\n assert(!this.#done, 'already set done');\n this.#done = true;\n\n for (let i = 0; i < this.#numWorkers; i++) {\n this.#tasks.enqueue('done');\n }\n }\n\n isRunning(): boolean {\n return this.#db !== undefined && !this.#done && this.#failure === undefined;\n }\n\n /**\n * Signals all workers to fail their transactions with the given {@link err}.\n */\n fail(err: unknown) {\n if (!this.#done && !this.#failure) {\n this.#failure = ensureError(err); // Fail fast: this is checked in the worker loop.\n // Logged for informational purposes. It is the responsibility of\n // higher level logic to classify and handle the exception.\n const level =\n this.#failure instanceof ControlFlowError ? 'debug' : 'info';\n this.#lc[level]?.(this.#failure);\n\n for (let i = 0; i < this.#numWorkers; i++) {\n // Enqueue the Error to terminate any workers waiting for tasks.\n this.#tasks.enqueue(this.#failure);\n }\n }\n }\n}\n\ntype SynchronizeSnapshotTasks = {\n /**\n * The `init` Task for the TransactionPool from which the snapshot originates.\n * The pool must have Mode.SERIALIZABLE, and will be set to READ ONLY by the\n * `exportSnapshot` init task. If the TransactionPool has multiple workers, the\n * first worker will export a snapshot that the others set.\n */\n exportSnapshot: Task;\n\n /**\n * The `cleanup` Task for the TransactionPool from which the snapshot\n * originates. This Task will wait for the follower pool to `setSnapshot`\n * to ensure that the snapshot is successfully shared before the originating\n * transaction is closed.\n */\n cleanupExport: Task;\n\n /**\n * The `init` Task for the TransactionPool in which workers will\n * consequently see the same snapshot as that of the first pool. The pool\n * must have Mode.SERIALIZABLE, and will have the ability to perform writes.\n */\n setSnapshot: Task;\n\n /** The ID of the shared snapshot. */\n snapshotID: Promise<string>;\n};\n\n/**\n * Init Tasks for Postgres snapshot synchronization across transactions.\n *\n * https://www.postgresql.org/docs/9.3/functions-admin.html#:~:text=Snapshot%20Synchronization%20Functions,identical%20content%20in%20the%20database.\n */\nexport function synchronizedSnapshots(): SynchronizeSnapshotTasks {\n const {\n promise: snapshotExported,\n resolve: exportSnapshot,\n reject: failExport,\n } = resolver<string>();\n\n const {\n promise: snapshotCaptured,\n resolve: captureSnapshot,\n reject: failCapture,\n } = resolver<unknown>();\n\n // Set by the first worker to run its initTask, who becomes responsible for\n // exporting the snapshot. TODO: Plumb the workerNum and use that instead.\n let firstWorkerRun = false;\n\n // Note: Neither init task should `await`, as processing in each pool can proceed\n // as soon as the statements have been sent to the db. However, the `cleanupExport`\n // task must `await` the result of `setSnapshot` to ensure that exporting transaction\n // does not close before the snapshot has been captured.\n return {\n exportSnapshot: tx => {\n if (!firstWorkerRun) {\n firstWorkerRun = true;\n const stmt =\n tx`SELECT pg_export_snapshot() AS snapshot; SET TRANSACTION READ ONLY;`.simple();\n // Intercept the promise to propagate the information to `snapshotExported`.\n stmt.then(result => exportSnapshot(result[0].snapshot), failExport);\n return [stmt]; // Also return the stmt so that it gets awaited (and errors handled).\n }\n return snapshotExported.then(snapshotID => [\n tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`),\n tx`SET TRANSACTION READ ONLY`.simple(),\n ]);\n },\n\n setSnapshot: tx =>\n snapshotExported.then(snapshotID => {\n const stmt = tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`);\n // Intercept the promise to propagate the information to `cleanupExport`.\n stmt.then(captureSnapshot, failCapture);\n return [stmt];\n }),\n\n cleanupExport: async () => {\n await snapshotCaptured;\n return [];\n },\n\n snapshotID: snapshotExported,\n };\n}\n\n/**\n * Returns `init` and `cleanup` {@link Task}s for a TransactionPool that ensure its workers\n * share a single view of the database. This is used for View Notifier and View Syncer logic\n * that allows multiple entities to perform parallel reads on the same snapshot of the database.\n */\nexport function sharedSnapshot(): {\n init: Task;\n cleanup: Task;\n snapshotID: Promise<string>;\n} {\n const {\n promise: snapshotExported,\n resolve: exportSnapshot,\n reject: failExport,\n } = resolver<string>();\n\n // Set by the first worker to run its initTask, who becomes responsible for\n // exporting the snapshot.\n let firstWorkerRun = false;\n\n // The LogContext of the exporting worker, used to identify its cleanup call.\n // Each worker receives a unique lc instance (via withContext('tx', id)), so\n // reference equality reliably identifies the exporting worker.\n let exporterLc: LogContext | undefined;\n\n // Set when the exporting worker's cleanup runs, signalling that the snapshot\n // is no longer needed and any subsequently spawned workers should skip their\n // initTask.\n let firstWorkerDone = false;\n\n return {\n init: (tx, lc) => {\n if (!firstWorkerRun) {\n firstWorkerRun = true;\n exporterLc = lc; // Remember which worker is the exporter.\n const stmt = tx`SELECT pg_export_snapshot() AS snapshot;`.simple();\n // Intercept the promise to propagate the information to `snapshotExported`.\n stmt.then(result => exportSnapshot(result[0].snapshot), failExport);\n return [stmt]; // Also return the stmt so that it gets awaited (and errors handled).\n }\n if (!firstWorkerDone) {\n return snapshotExported.then(snapshotID => [\n tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`),\n ]);\n }\n lc.debug?.('All work is done. No need to set snapshot');\n return [];\n },\n\n cleanup: (_tx, lc) => {\n // Only the exporting worker's cleanup should disable snapshot-setting.\n // Non-exporter workers may finish early; letting them flip this flag\n // would cause subsequently spawned workers to skip SET TRANSACTION SNAPSHOT\n // and read a newer database view, violating snapshot isolation.\n if (lc === exporterLc) {\n firstWorkerDone = true;\n }\n return [];\n },\n\n snapshotID: snapshotExported,\n };\n}\n\n/**\n * @returns An `init` Task for importing a snapshot from another transaction,\n * as well as an `imported` Queue which resolves or rejects when each\n * worker is initialized.\n */\nexport function importSnapshot(snapshotID: string): {\n init: Task;\n imported: Queue<void>;\n} {\n const imported = new Queue<void>();\n\n return {\n init: tx => {\n const stmt = tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`);\n stmt.then(\n () => imported.enqueue(),\n err => imported.enqueueRejection(err),\n );\n return [stmt];\n },\n\n imported,\n };\n}\n\n/**\n * A superclass of Errors used for control flow that is needed to handle\n * another Error but does not constitute an error condition itself (e.g.\n * aborting transactions after a previous one fails). Subclassing this Error\n * will result in lowering the log level from `error` to `debug`.\n */\nexport class ControlFlowError extends Error {\n constructor(cause?: unknown) {\n super();\n this.cause = cause;\n }\n}\n\n/**\n * Internal error used to rollback the worker transaction. This is used\n * instead of executing a `ROLLBACK` statement because the postgres.js\n * library will otherwise try to execute an extraneous `COMMIT`, which\n * results in outputting a \"no transaction in progress\" warning to the\n * database logs.\n *\n * Throwing an exception, on the other hand, executes the postgres.js\n * codepath that calls `ROLLBACK` instead.\n */\nclass RollbackSignal extends ControlFlowError {\n readonly name = 'RollbackSignal';\n readonly message = 'rolling back transaction';\n}\n\nfunction ensureError(err: unknown): Error {\n if (err instanceof Error) {\n return err;\n }\n const error = new Error();\n error.cause = err;\n return error;\n}\n\ninterface TaskRunner {\n /**\n * Manages the running of a Task or ReadTask in two phases:\n *\n * - If the task involves blocking, this is done in the worker. Once the\n * blocking is done, `freeWorker()` is invoked to signal that the worker\n * is available to run another task. Note that this should be invoked\n * *before* resolving the result to the calling thread so that a\n * subsequent task can reuse the same worker.\n *\n * - Task statements are executed on the database asynchronously. The final\n * result of this processing is encapsulated in the returned `pending`\n * Promise. The worker will await the last pending Promise before closing\n * the transaction.\n *\n * @param freeWorker should be called as soon as all blocking operations are\n * completed in order to return the transaction to the pool.\n * @returns A `pending` Promise indicating when the statements have been\n * processed by the database, allowing the transaction to be closed.\n * This should be `null` if there are no transaction-dependent\n * statements to await.\n */\n run(\n tx: PostgresTransaction,\n lc: LogContext,\n freeWorker: () => void,\n ): Promise<{pending: Promise<void> | null}>;\n\n /**\n * Invoked if the TransactionPool is already in a failed state when the task\n * is requested.\n */\n rejected(reason: unknown): void;\n}\n\nconst IDLE_TIMEOUT_MS = 5_000;\n\n// The keepalive interval is settable by ZERO_TRANSACTION_POOL_KEEPALIVE_MS\n// as an emergency measure and is explicitly not made available as a server\n// option. This value is function of how the zero-cache uses transactions, and\n// should never need to be \"tuned\" or adjusted for different environments.\n//\n// Note that it must be shorter than IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS\n// with sufficient buffering to account for when the process is blocked by\n// synchronous calls (e.g. to the replica).\nconst KEEPALIVE_TIMEOUT_MS = parseInt(\n process.env.ZERO_TRANSACTION_POOL_KEEPALIVE_MS ?? '5000',\n);\n\nconst KEEPALIVE_TASK: Task = (tx, lc) => {\n lc.debug?.(`sending tx keepalive`);\n return [tx`SELECT 1`.simple()];\n};\n\ntype TimeoutTask = {\n timeoutMs: number;\n task: Task | 'done';\n};\n\ntype TimeoutTasks = {\n forInitialWorkers: TimeoutTask;\n forExtraWorkers: TimeoutTask;\n};\n\n// Production timeout tasks. Overridden in tests.\nexport const TIMEOUT_TASKS: TimeoutTasks = {\n forInitialWorkers: {\n timeoutMs: KEEPALIVE_TIMEOUT_MS,\n task: KEEPALIVE_TASK,\n },\n forExtraWorkers: {\n timeoutMs: IDLE_TIMEOUT_MS,\n task: 'done',\n },\n};\n\n// The slice of information from the Query object in Postgres.js that gets logged for debugging.\n// https://github.com/porsager/postgres/blob/f58cd4f3affd3e8ce8f53e42799672d86cd2c70b/src/connection.js#L219\ntype Query = {string: string; parameters: object[]};\n"],"mappings":"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"}
1
+ {"version":3,"file":"transaction-pool.js","names":[],"sources":["../../../../../zero-cache/src/db/transaction-pool.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Resolver, resolver} from '@rocicorp/resolver';\nimport type postgres from 'postgres';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport type * as Mode from './mode-enum.ts';\nimport {runTx} from './run-transaction.ts';\n\ntype Mode = Enum<typeof Mode>;\n\ntype MaybePromise<T> = Promise<T> | T;\n\nexport type Statement =\n | postgres.PendingQuery<(postgres.Row & Iterable<postgres.Row>)[]>\n | postgres.PendingQuery<postgres.Row[]>;\n\n/**\n * A {@link Task} is logic run from within a transaction in a {@link TransactionPool}.\n * It returns a list of `Statements` that the transaction executes asynchronously and\n * awaits when it receives the 'done' signal.\n *\n */\nexport type Task = (\n tx: PostgresTransaction,\n lc: LogContext,\n) => MaybePromise<Statement[]>;\n\n/**\n * A {@link ReadTask} is run from within a transaction, but unlike a {@link Task},\n * the results of a ReadTask are opaque to the TransactionPool and returned to the\n * caller of {@link TransactionPool.processReadTask}.\n */\nexport type ReadTask<T> = (\n tx: PostgresTransaction,\n lc: LogContext,\n) => MaybePromise<T>;\n\nexport type Options = {\n mode: Mode;\n\n /**\n * A {@link Task} that is run in each Transaction before it begins\n * processing general tasks. This can be used to to set the transaction\n * mode, export/set snapshots, etc. This will be run even if\n * {@link fail} has been called on the pool.\n */\n init?: Task | undefined;\n\n /**\n * A {@link Task} that is run in each Transaction before it closes.\n * This will be run even if {@link fail} has been called, or if a\n * preceding Task threw an Error.\n */\n cleanup?: Task | undefined;\n\n /**\n * The initial number of transaction workers to process tasks.\n * This is the steady state number of workers that will be kept\n * alive if the TransactionPool is long lived.\n * This must be greater than 0. Defaults to 1.\n */\n initialWorkers?: number;\n\n /**\n * When specified, allows the pool to grow to `maxWorkers`. This\n * must be greater than or equal to `initialWorkers`. On-demand\n * workers will be shut down after an idle timeout of 5 seconds.\n */\n maxWorkers?: number;\n};\n\n/**\n * A TransactionPool is a pool of one or more {@link postgres.TransactionSql}\n * objects that participate in processing a dynamic queue of tasks.\n *\n * This can be used for serializing a set of tasks that arrive asynchronously\n * to a single transaction (for writing) or performing parallel reads across\n * multiple connections at the same snapshot (e.g. read only snapshot transactions).\n */\nexport class TransactionPool {\n #lc: LogContext;\n readonly #mode: Mode;\n readonly #init: TaskRunner | undefined;\n readonly #cleanup: TaskRunner | undefined;\n readonly #tasks = new Queue<TaskRunner | Error | 'done'>();\n readonly #workers: Promise<unknown>[] = [];\n readonly #initialWorkers: number;\n readonly #maxWorkers: number;\n readonly #timeoutTask: TimeoutTasks;\n #numWorkers: number;\n #numWorking = 0;\n #db: PostgresDB | undefined; // set when running. stored to allow adaptive pool sizing.\n\n #done = false;\n #failure: Error | undefined;\n\n constructor(\n lc: LogContext,\n opts: Options,\n timeoutTasks = TIMEOUT_TASKS, // Overridden for tests.\n ) {\n const {\n mode,\n init,\n cleanup,\n initialWorkers = 1,\n maxWorkers = initialWorkers,\n } = opts;\n assert(initialWorkers > 0, 'initialWorkers must be positive');\n assert(\n maxWorkers >= initialWorkers,\n 'maxWorkers must be >= initialWorkers',\n );\n\n this.#lc = lc;\n this.#mode = mode;\n this.#init = init ? this.#stmtRunner(init) : undefined;\n this.#cleanup = cleanup ? this.#stmtRunner(cleanup) : undefined;\n this.#initialWorkers = initialWorkers;\n this.#numWorkers = initialWorkers;\n this.#maxWorkers = maxWorkers;\n this.#timeoutTask = timeoutTasks;\n }\n\n /**\n * Starts the pool of workers to process Tasks with transactions opened from the\n * specified {@link db}.\n */\n run(db: PostgresDB): this {\n assert(!this.#db, 'already running');\n this.#db = db;\n for (let i = 0; i < this.#numWorkers; i++) {\n this.#addWorker(db);\n }\n return this;\n }\n\n /**\n * Adds context parameters to internal LogContext. This is useful for context values that\n * are not known when the TransactionPool is constructed (e.g. determined after a database\n * call when the pool is running).\n *\n * Returns an object that can be used to add more parameters.\n */\n addLoggingContext(key: string, value: string) {\n this.#lc = this.#lc.withContext(key, value);\n\n return {\n addLoggingContext: (key: string, value: string) =>\n this.addLoggingContext(key, value),\n };\n }\n\n /**\n * Returns a promise that:\n *\n * * resolves after {@link setDone} has been called (or the the pool as been {@link unref}ed\n * to a 0 ref count), once all added tasks have been processed and all transactions have been\n * committed or closed.\n *\n * * rejects if processing was aborted with {@link fail} or if processing any of\n * the tasks resulted in an error. All uncommitted transactions will have been\n * rolled back.\n *\n * Note that partial failures are possible if processing writes with multiple workers\n * (e.g. `setDone` is called, allowing some workers to commit, after which other\n * workers encounter errors). Using a TransactionPool in this manner does not make\n * sense in terms of transactional semantics, and is thus not recommended.\n *\n * For reads, however, multiple workers is useful for performing parallel reads\n * at the same snapshot. See {@link synchronizedSnapshots} for an example.\n * Resolves or rejects when all workers are done or failed.\n */\n async done() {\n const numWorkers = this.#workers.length;\n await Promise.all(this.#workers);\n\n if (numWorkers < this.#workers.length) {\n // If workers were added after the initial set, they must be awaited to ensure\n // that the results (i.e. rejections) of all workers are accounted for. This only\n // needs to be re-done once, because the fact that the first `await` completed\n // guarantees that the pool is in a terminal state and no new workers can be added.\n await Promise.all(this.#workers);\n }\n this.#lc.debug?.('transaction pool done');\n\n const elapsed = performance.now() - this.#start;\n if (elapsed > 60_000) {\n if (this.#stmts > 0) {\n this.#lc.warn?.(\n `finished long transaction with ${this.#stmts} statements (${elapsed.toFixed(3)} ms)`,\n );\n } else {\n this.#lc.warn?.(\n `finished long read transaction (${elapsed.toFixed(3)} ms)`,\n );\n }\n }\n }\n\n #addWorker(db: PostgresDB) {\n const id = this.#workers.length + 1;\n const lc = this.#lc.withContext('tx', id);\n\n const tt: TimeoutTask =\n this.#workers.length < this.#initialWorkers\n ? this.#timeoutTask.forInitialWorkers\n : this.#timeoutTask.forExtraWorkers;\n const {timeoutMs} = tt;\n const timeoutTask = tt.task === 'done' ? 'done' : this.#stmtRunner(tt.task);\n\n const worker = async (tx: PostgresTransaction) => {\n const start = performance.now();\n try {\n lc.debug?.('started transaction');\n\n let last: Promise<void> = promiseVoid;\n\n const executeTask = async (runner: TaskRunner) => {\n runner !== this.#init && this.#numWorking++;\n const {pending} = await runner.run(tx, lc, () => {\n runner !== this.#init && this.#numWorking--;\n });\n last = pending ?? last;\n };\n\n let task: TaskRunner | Error | 'done' =\n this.#init ?? (await this.#tasks.dequeue(timeoutTask, timeoutMs));\n\n try {\n while (task !== 'done') {\n if (\n task instanceof Error ||\n (task !== this.#init && this.#failure)\n ) {\n throw this.#failure ?? task;\n }\n await executeTask(task);\n\n // await the next task.\n task = await this.#tasks.dequeue(timeoutTask, timeoutMs);\n }\n } finally {\n // Execute the cleanup task even on failure.\n if (this.#cleanup) {\n await executeTask(this.#cleanup);\n }\n }\n\n const elapsed = performance.now() - start;\n lc.debug?.(`closing transaction (${elapsed.toFixed(3)} ms)`);\n // Given the semantics of a Postgres transaction, the last statement\n // will only succeed if all of the preceding statements succeeded.\n return last;\n } catch (e) {\n if (e !== this.#failure) {\n this.fail(e); // A failure in any worker should fail the pool.\n }\n throw e;\n }\n };\n\n const workerTx = runTx(db, worker, {mode: this.#mode})\n .catch(e => {\n if (e instanceof RollbackSignal) {\n // A RollbackSignal is used to gracefully rollback the postgres.js\n // transaction block. It should not be thrown up to the application.\n lc.debug?.('aborted transaction');\n } else {\n throw e;\n }\n })\n .finally(() => this.#numWorkers--);\n\n // Attach a rejection handler immediately to prevent unhandledRejections.\n // The application will handle errors when it awaits processReadTask()\n // or done().\n workerTx.catch(() => {});\n\n this.#workers.push(workerTx);\n\n // After adding the worker, enqueue a terminal signal if we are in either of the\n // terminal states (both of which prevent more tasks from being enqueued), to ensure\n // that the added worker eventually exits.\n if (this.#done) {\n this.#tasks.enqueue('done');\n }\n if (this.#failure) {\n this.#tasks.enqueue(this.#failure);\n }\n }\n\n /**\n * Processes the statements produced by the specified {@link Task},\n * returning a Promise that resolves when the statements are either processed\n * by the database or rejected.\n *\n * Note that statement failures will result in failing the entire\n * TransactionPool (per transaction semantics). However, the returned Promise\n * itself will resolve rather than reject. As such, it is fine to ignore\n * returned Promises in order to pipeline requests to the database. It is\n * recommended to occasionally await them (e.g. after some threshold) in\n * order to avoid memory blowup in the case of database slowness.\n */\n process(task: Task): Promise<void> {\n const r = resolver<void>();\n this.#process(this.#stmtRunner(task, r));\n return r.promise;\n }\n\n readonly #start = performance.now();\n #stmts = 0;\n\n /**\n * Implements the semantics specified in {@link process()}.\n *\n * Specifically:\n * * `freeWorker()` is called as soon as the statements are produced,\n * allowing them to be pipelined to the database.\n * * Statement errors result in failing the transaction pool.\n * * The client-supplied Resolver resolves on success or failure;\n * it is never rejected.\n */\n #stmtRunner(task: Task, r: {resolve: () => void} = resolver()): TaskRunner {\n return {\n run: async (tx, lc, freeWorker) => {\n let stmts: Statement[];\n try {\n stmts = await task(tx, lc);\n } catch (e) {\n r.resolve();\n throw e;\n } finally {\n freeWorker();\n }\n\n if (stmts.length === 0) {\n r.resolve();\n return {pending: null};\n }\n\n // Execute the statements (i.e. send to the db) immediately.\n // The last result is returned for the worker to await before\n // closing the transaction.\n const last = stmts.reduce((_, stmt) => {\n const query = stmt as unknown as Query;\n const index = ++this.#stmts;\n return stmt\n .execute()\n .then(() => {\n if (index % 1000 === 0) {\n const log = index % 10000 === 0 ? 'info' : 'debug';\n lc[log]?.(\n `executed ${this.#stmts}th statement (${(performance.now() - this.#start).toFixed(3)} ms)`,\n {statement: query.string},\n );\n }\n })\n .catch(e => this.fail(e));\n }, promiseVoid);\n return {pending: last.finally(r.resolve)};\n },\n rejected: r.resolve,\n };\n }\n\n /**\n * Processes and returns the result of executing the {@link ReadTask} from\n * within the transaction. An error thrown by the task will result in\n * rejecting the returned Promise, but will not affect the transaction pool\n * itself.\n */\n processReadTask<T>(readTask: ReadTask<T>): Promise<T> {\n const r = resolver<T>();\n this.#process(this.#readRunner(readTask, r));\n return r.promise;\n }\n\n /**\n * Implements the semantics specified in {@link processReadTask()}.\n *\n * Specifically:\n * * `freeWorker()` is called as soon as the result is produced,\n * before resolving the client-supplied Resolver.\n * * Errors result in rejecting the client-supplied Resolver but\n * do not affect transaction pool.\n */\n #readRunner<T>(readTask: ReadTask<T>, r: Resolver<T>): TaskRunner {\n return {\n run: async (tx, lc, freeWorker) => {\n let result: T;\n try {\n result = await readTask(tx, lc);\n freeWorker();\n r.resolve(result);\n } catch (e) {\n freeWorker();\n r.reject(e);\n }\n return {pending: null};\n },\n rejected: r.reject,\n };\n }\n\n #process(runner: TaskRunner): void {\n assert(!this.#done, 'already set done');\n if (this.#failure) {\n runner.rejected(this.#failure);\n return;\n }\n\n this.#tasks.enqueue(runner);\n\n // Check if the pool size can and should be increased.\n if (this.#numWorkers < this.#maxWorkers) {\n const outstanding = this.#tasks.size();\n\n if (outstanding > this.#numWorkers - this.#numWorking) {\n this.#db && this.#addWorker(this.#db);\n this.#numWorkers++;\n this.#lc.debug?.(`Increased pool size to ${this.#numWorkers}`);\n }\n }\n }\n\n /**\n * Ends all workers with a ROLLBACK. Throws if the pool is already done\n * or aborted.\n */\n abort() {\n this.fail(new RollbackSignal());\n }\n\n /**\n * Signals to all workers to end their transaction once all pending tasks have\n * been completed. Throws if the pool is already done or aborted.\n */\n setDone() {\n assert(!this.#done, 'already set done');\n this.#done = true;\n\n for (let i = 0; i < this.#numWorkers; i++) {\n this.#tasks.enqueue('done');\n }\n }\n\n isRunning(): boolean {\n return this.#db !== undefined && !this.#done && this.#failure === undefined;\n }\n\n /**\n * Signals all workers to fail their transactions with the given {@link err}.\n */\n fail(err: unknown) {\n if (!this.#done && !this.#failure) {\n this.#failure = ensureError(err); // Fail fast: this is checked in the worker loop.\n // Logged for informational purposes. It is the responsibility of\n // higher level logic to classify and handle the exception.\n const level =\n this.#failure instanceof ControlFlowError ? 'debug' : 'info';\n this.#lc[level]?.(this.#failure);\n\n for (let i = 0; i < this.#numWorkers; i++) {\n // Enqueue the Error to terminate any workers waiting for tasks.\n this.#tasks.enqueue(this.#failure);\n }\n }\n }\n}\n\ntype SynchronizeSnapshotTasks = {\n /**\n * The `init` Task for the TransactionPool from which the snapshot originates.\n * The pool must have Mode.SERIALIZABLE, and will be set to READ ONLY by the\n * `exportSnapshot` init task. If the TransactionPool has multiple workers, the\n * first worker will export a snapshot that the others set.\n */\n exportSnapshot: Task;\n\n /**\n * The `cleanup` Task for the TransactionPool from which the snapshot\n * originates. This Task will wait for the follower pool to `setSnapshot`\n * to ensure that the snapshot is successfully shared before the originating\n * transaction is closed.\n */\n cleanupExport: Task;\n\n /**\n * The `init` Task for the TransactionPool in which workers will\n * consequently see the same snapshot as that of the first pool. The pool\n * must have Mode.SERIALIZABLE, and will have the ability to perform writes.\n */\n setSnapshot: Task;\n\n /** The ID of the shared snapshot. */\n snapshotID: Promise<string>;\n};\n\n/**\n * Init Tasks for Postgres snapshot synchronization across transactions.\n *\n * https://www.postgresql.org/docs/9.3/functions-admin.html#:~:text=Snapshot%20Synchronization%20Functions,identical%20content%20in%20the%20database.\n */\nexport function synchronizedSnapshots(): SynchronizeSnapshotTasks {\n const {\n promise: snapshotExported,\n resolve: exportSnapshot,\n reject: failExport,\n } = resolver<string>();\n\n const {\n promise: snapshotCaptured,\n resolve: captureSnapshot,\n reject: failCapture,\n } = resolver<unknown>();\n\n // Set by the first worker to run its initTask, who becomes responsible for\n // exporting the snapshot. TODO: Plumb the workerNum and use that instead.\n let firstWorkerRun = false;\n\n // Note: Neither init task should `await`, as processing in each pool can proceed\n // as soon as the statements have been sent to the db. However, the `cleanupExport`\n // task must `await` the result of `setSnapshot` to ensure that exporting transaction\n // does not close before the snapshot has been captured.\n return {\n exportSnapshot: tx => {\n if (!firstWorkerRun) {\n firstWorkerRun = true;\n const stmt =\n tx`SELECT pg_export_snapshot() AS snapshot; SET TRANSACTION READ ONLY;`.simple();\n // Intercept the promise to propagate the information to `snapshotExported`.\n stmt.then(result => exportSnapshot(result[0].snapshot), failExport);\n return [stmt]; // Also return the stmt so that it gets awaited (and errors handled).\n }\n return snapshotExported.then(snapshotID => [\n tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`),\n tx`SET TRANSACTION READ ONLY`.simple(),\n ]);\n },\n\n setSnapshot: tx =>\n snapshotExported.then(snapshotID => {\n const stmt = tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`);\n // Intercept the promise to propagate the information to `cleanupExport`.\n stmt.then(captureSnapshot, failCapture);\n return [stmt];\n }),\n\n cleanupExport: async () => {\n await snapshotCaptured;\n return [];\n },\n\n snapshotID: snapshotExported,\n };\n}\n\n/**\n * Returns `init` and `cleanup` {@link Task}s for a TransactionPool that ensure its workers\n * share a single view of the database. This is used for View Notifier and View Syncer logic\n * that allows multiple entities to perform parallel reads on the same snapshot of the database.\n */\nexport function sharedSnapshot(): {\n init: Task;\n cleanup: Task;\n snapshotID: Promise<string>;\n} {\n const {\n promise: snapshotExported,\n resolve: exportSnapshot,\n reject: failExport,\n } = resolver<string>();\n\n // Set by the first worker to run its initTask, who becomes responsible for\n // exporting the snapshot.\n let firstWorkerRun = false;\n\n // The LogContext of the exporting worker, used to identify its cleanup call.\n // Each worker receives a unique lc instance (via withContext('tx', id)), so\n // reference equality reliably identifies the exporting worker.\n let exporterLc: LogContext | undefined;\n\n // Set when the exporting worker's cleanup runs, signalling that the snapshot\n // is no longer needed and any subsequently spawned workers should skip their\n // initTask.\n let firstWorkerDone = false;\n\n return {\n init: (tx, lc) => {\n if (!firstWorkerRun) {\n firstWorkerRun = true;\n exporterLc = lc; // Remember which worker is the exporter.\n const stmt = tx`SELECT pg_export_snapshot() AS snapshot;`.simple();\n // Intercept the promise to propagate the information to `snapshotExported`.\n stmt.then(result => exportSnapshot(result[0].snapshot), failExport);\n return [stmt]; // Also return the stmt so that it gets awaited (and errors handled).\n }\n if (!firstWorkerDone) {\n return snapshotExported.then(snapshotID => [\n tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`),\n ]);\n }\n lc.debug?.('All work is done. No need to set snapshot');\n return [];\n },\n\n cleanup: (_tx, lc) => {\n // Only the exporting worker's cleanup should disable snapshot-setting.\n // Non-exporter workers may finish early; letting them flip this flag\n // would cause subsequently spawned workers to skip SET TRANSACTION SNAPSHOT\n // and read a newer database view, violating snapshot isolation.\n if (lc === exporterLc) {\n firstWorkerDone = true;\n }\n return [];\n },\n\n snapshotID: snapshotExported,\n };\n}\n\n/**\n * @returns An `init` Task for importing a snapshot from another transaction,\n * as well as an `imported` Queue which resolves or rejects when each\n * worker is initialized.\n */\nexport function importSnapshot(snapshotID: string): {\n init: Task;\n imported: Queue<void>;\n} {\n const imported = new Queue<void>();\n\n return {\n init: tx => {\n const stmt = tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`);\n stmt.then(\n () => imported.enqueue(),\n err => imported.enqueueRejection(err),\n );\n return [stmt];\n },\n\n imported,\n };\n}\n\n/**\n * A superclass of Errors used for control flow that is needed to handle\n * another Error but does not constitute an error condition itself (e.g.\n * aborting transactions after a previous one fails). Subclassing this Error\n * will result in lowering the log level from `error` to `debug`.\n */\nexport class ControlFlowError extends Error {\n constructor(cause?: unknown) {\n super();\n this.cause = cause;\n }\n}\n\n/**\n * Internal error used to rollback the worker transaction. This is used\n * instead of executing a `ROLLBACK` statement because the postgres.js\n * library will otherwise try to execute an extraneous `COMMIT`, which\n * results in outputting a \"no transaction in progress\" warning to the\n * database logs.\n *\n * Throwing an exception, on the other hand, executes the postgres.js\n * codepath that calls `ROLLBACK` instead.\n */\nclass RollbackSignal extends ControlFlowError {\n readonly name = 'RollbackSignal';\n readonly message = 'rolling back transaction';\n}\n\nfunction ensureError(err: unknown): Error {\n if (err instanceof Error) {\n return err;\n }\n const error = new Error();\n error.cause = err;\n return error;\n}\n\ninterface TaskRunner {\n /**\n * Manages the running of a Task or ReadTask in two phases:\n *\n * - If the task involves blocking, this is done in the worker. Once the\n * blocking is done, `freeWorker()` is invoked to signal that the worker\n * is available to run another task. Note that this should be invoked\n * *before* resolving the result to the calling thread so that a\n * subsequent task can reuse the same worker.\n *\n * - Task statements are executed on the database asynchronously. The final\n * result of this processing is encapsulated in the returned `pending`\n * Promise. The worker will await the last pending Promise before closing\n * the transaction.\n *\n * @param freeWorker should be called as soon as all blocking operations are\n * completed in order to return the transaction to the pool.\n * @returns A `pending` Promise indicating when the statements have been\n * processed by the database, allowing the transaction to be closed.\n * This should be `null` if there are no transaction-dependent\n * statements to await.\n */\n run(\n tx: PostgresTransaction,\n lc: LogContext,\n freeWorker: () => void,\n ): Promise<{pending: Promise<void> | null}>;\n\n /**\n * Invoked if the TransactionPool is already in a failed state when the task\n * is requested.\n */\n rejected(reason: unknown): void;\n}\n\nconst IDLE_TIMEOUT_MS = 5_000;\n\n// The keepalive interval is settable by ZERO_TRANSACTION_POOL_KEEPALIVE_MS\n// as an emergency measure and is explicitly not made available as a server\n// option. This value is function of how the zero-cache uses transactions, and\n// should never need to be \"tuned\" or adjusted for different environments.\n//\n// Note that it must be shorter than IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS\n// with sufficient buffering to account for when the process is blocked by\n// synchronous calls (e.g. to the replica).\nconst KEEPALIVE_TIMEOUT_MS = parseInt(\n process.env.ZERO_TRANSACTION_POOL_KEEPALIVE_MS ?? '5000',\n);\n\nconst KEEPALIVE_TASK: Task = (tx, lc) => {\n lc.debug?.(`sending tx keepalive`);\n return [tx`SELECT 1`.simple()];\n};\n\ntype TimeoutTask = {\n timeoutMs: number;\n task: Task | 'done';\n};\n\ntype TimeoutTasks = {\n forInitialWorkers: TimeoutTask;\n forExtraWorkers: TimeoutTask;\n};\n\n// Production timeout tasks. Overridden in tests.\nexport const TIMEOUT_TASKS: TimeoutTasks = {\n forInitialWorkers: {\n timeoutMs: KEEPALIVE_TIMEOUT_MS,\n task: KEEPALIVE_TASK,\n },\n forExtraWorkers: {\n timeoutMs: IDLE_TIMEOUT_MS,\n task: 'done',\n },\n};\n\n// The slice of information from the Query object in Postgres.js that gets logged for debugging.\n// https://github.com/porsager/postgres/blob/f58cd4f3affd3e8ce8f53e42799672d86cd2c70b/src/connection.js#L219\ntype Query = {string: string; parameters: object[]};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"warmup.js","names":[],"sources":["../../../../../zero-cache/src/db/warmup.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {PostgresDB} from '../types/pg.ts';\n\nconst MAX_WARMUP_CONNECTIONS = 5;\n\nexport async function warmupConnections(\n lc: LogContext,\n db: PostgresDB,\n name: string,\n) {\n const {host} = db.options;\n const max = Math.min(db.options.max, MAX_WARMUP_CONNECTIONS);\n await Promise.allSettled(\n Array.from({length: max}, () => db`SELECT 1`.simple().execute()),\n );\n const start = performance.now();\n const pingTimes = await Promise.all(\n Array.from({length: Math.min(max, 5)}, () =>\n db`SELECT 2`.simple().then(\n () => performance.now() - start,\n () => performance.now() - start,\n ),\n ),\n );\n const average = pingTimes.reduce((l, r) => l + r, 0) / pingTimes.length;\n const log = average >= 10 ? 'warn' : 'info';\n lc[log]?.(`average ping to ${name} db@${host}: ${average.toFixed(2)} ms`);\n if (log === 'warn') {\n lc.warn?.(`ideal db ping time is < 5 ms`);\n }\n}\n"],"mappings":";AAGA,IAAM,yBAAyB;AAE/B,eAAsB,kBACpB,IACA,IACA,MACA;CACA,MAAM,EAAC,SAAQ,GAAG;CAClB,MAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,KAAK,sBAAsB;CAC3D,MAAM,QAAQ,WACZ,MAAM,KAAK,EAAC,QAAQ,IAAG,SAAS,EAAE,WAAW,OAAO,EAAE,QAAQ,CAAC,CACjE;CACA,MAAM,QAAQ,YAAY,IAAI;CAC9B,MAAM,YAAY,MAAM,QAAQ,IAC9B,MAAM,KAAK,EAAC,QAAQ,KAAK,IAAI,KAAK,CAAC,EAAC,SAClC,EAAE,WAAW,OAAO,EAAE,WACd,YAAY,IAAI,IAAI,aACpB,YAAY,IAAI,IAAI,KAC5B,CACF,CACF;CACA,MAAM,UAAU,UAAU,QAAQ,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,UAAU;CACjE,MAAM,MAAM,WAAW,KAAK,SAAS;CACrC,GAAG,OAAO,mBAAmB,KAAK,MAAM,KAAK,IAAI,QAAQ,QAAQ,CAAC,EAAE,IAAI;CACxE,IAAI,QAAQ,QACV,GAAG,OAAO,8BAA8B;AAE5C"}
1
+ {"version":3,"file":"warmup.js","names":[],"sources":["../../../../../zero-cache/src/db/warmup.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {PostgresDB} from '../types/pg.ts';\n\nconst MAX_WARMUP_CONNECTIONS = 5;\n\nexport async function warmupConnections(\n lc: LogContext,\n db: PostgresDB,\n name: string,\n) {\n const {host} = db.options;\n const max = Math.min(db.options.max, MAX_WARMUP_CONNECTIONS);\n await Promise.allSettled(\n Array.from({length: max}, () => db`SELECT 1`.simple().execute()),\n );\n const start = performance.now();\n const pingTimes = await Promise.all(\n Array.from({length: Math.min(max, 5)}, () =>\n db`SELECT 2`.simple().then(\n () => performance.now() - start,\n () => performance.now() - start,\n ),\n ),\n );\n const average = pingTimes.reduce((l, r) => l + r, 0) / pingTimes.length;\n const log = average >= 10 ? 'warn' : 'info';\n lc[log]?.(`average ping to ${name} db@${host}: ${average.toFixed(2)} ms`);\n if (log === 'warn') {\n lc.warn?.(`ideal db ping time is < 5 ms`);\n }\n}\n"],"mappings":";AAGA,IAAM,yBAAyB;AAE/B,eAAsB,kBACpB,IACA,IACA,MACA;CACA,MAAM,EAAC,SAAQ,GAAG;CAClB,MAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,KAAK,sBAAsB;CAC3D,MAAM,QAAQ,WACZ,MAAM,KAAK,EAAC,QAAQ,IAAG,SAAS,EAAE,WAAW,OAAO,CAAC,CAAC,QAAQ,CAAC,CACjE;CACA,MAAM,QAAQ,YAAY,IAAI;CAC9B,MAAM,YAAY,MAAM,QAAQ,IAC9B,MAAM,KAAK,EAAC,QAAQ,KAAK,IAAI,KAAK,CAAC,EAAC,SAClC,EAAE,WAAW,OAAO,CAAC,CAAC,WACd,YAAY,IAAI,IAAI,aACpB,YAAY,IAAI,IAAI,KAC5B,CACF,CACF;CACA,MAAM,UAAU,UAAU,QAAQ,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,UAAU;CACjE,MAAM,MAAM,WAAW,KAAK,SAAS;CACrC,GAAG,IAAI,GAAG,mBAAmB,KAAK,MAAM,KAAK,IAAI,QAAQ,QAAQ,CAAC,EAAE,IAAI;CACxE,IAAI,QAAQ,QACV,GAAG,OAAO,8BAA8B;AAE5C"}