@rocicorp/zero 1.6.0-canary.12 → 1.6.0-canary.14

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 (549) hide show
  1. package/README.md +3 -28
  2. package/out/_virtual/{_@oxc-project_runtime@0.130.0 → _@oxc-project_runtime@0.122.0}/helpers/usingCtx.js +1 -1
  3. package/out/analyze-query/src/analyze-cli.js +3 -3
  4. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  5. package/out/analyze-query/src/bin-analyze.js +1 -6
  6. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  7. package/out/analyze-query/src/bin-transform.js.map +1 -1
  8. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  9. package/out/ast-to-zql/src/bin.js.map +1 -1
  10. package/out/ast-to-zql/src/format.js.map +1 -1
  11. package/out/datadog/src/datadog-log-sink.js.map +1 -1
  12. package/out/otel/src/enabled.js.map +1 -1
  13. package/out/otel/src/log-options.js.map +1 -1
  14. package/out/otel/src/maybe-time.js.map +1 -1
  15. package/out/otel/src/span.js.map +1 -1
  16. package/out/replicache/src/async-iterable-to-array.js.map +1 -1
  17. package/out/replicache/src/bg-interval.js.map +1 -1
  18. package/out/replicache/src/btree/diff.js.map +1 -1
  19. package/out/replicache/src/btree/node.js.map +1 -1
  20. package/out/replicache/src/btree/read.js.map +1 -1
  21. package/out/replicache/src/btree/splice.js.map +1 -1
  22. package/out/replicache/src/btree/write.js +3 -6
  23. package/out/replicache/src/btree/write.js.map +1 -1
  24. package/out/replicache/src/call-default-fetch.js.map +1 -1
  25. package/out/replicache/src/connection-loop-delegates.js.map +1 -1
  26. package/out/replicache/src/connection-loop.js.map +1 -1
  27. package/out/replicache/src/cookies.js.map +1 -1
  28. package/out/replicache/src/dag/chunk.js.map +1 -1
  29. package/out/replicache/src/dag/gc.js.map +1 -1
  30. package/out/replicache/src/dag/key.js.map +1 -1
  31. package/out/replicache/src/dag/lazy-store.js.map +1 -1
  32. package/out/replicache/src/dag/store-impl.js.map +1 -1
  33. package/out/replicache/src/dag/store.js.map +1 -1
  34. package/out/replicache/src/dag/visitor.js.map +1 -1
  35. package/out/replicache/src/db/commit.js.map +1 -1
  36. package/out/replicache/src/db/index.js.map +1 -1
  37. package/out/replicache/src/db/read.js.map +1 -1
  38. package/out/replicache/src/db/rebase.js.map +1 -1
  39. package/out/replicache/src/db/write.js.map +1 -1
  40. package/out/replicache/src/deleted-clients.js.map +1 -1
  41. package/out/replicache/src/error-responses.js.map +1 -1
  42. package/out/replicache/src/frozen-json.js.map +1 -1
  43. package/out/replicache/src/get-default-puller.js.map +1 -1
  44. package/out/replicache/src/get-default-pusher.js.map +1 -1
  45. package/out/replicache/src/get-kv-store-provider.js.map +1 -1
  46. package/out/replicache/src/hash.js.map +1 -1
  47. package/out/replicache/src/http-request-info.js.map +1 -1
  48. package/out/replicache/src/index-defs.js.map +1 -1
  49. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  50. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +1 -1
  51. package/out/replicache/src/kv/idb-store.js.map +1 -1
  52. package/out/replicache/src/kv/mem-store.js.map +1 -1
  53. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  54. package/out/replicache/src/kv/read-impl.js.map +1 -1
  55. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  56. package/out/replicache/src/kv/sqlite-store.js +1 -4
  57. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  58. package/out/replicache/src/kv/throw-if-closed.js.map +1 -1
  59. package/out/replicache/src/kv/write-impl-base.js.map +1 -1
  60. package/out/replicache/src/kv/write-impl.js.map +1 -1
  61. package/out/replicache/src/lazy.js.map +1 -1
  62. package/out/replicache/src/log-options.js.map +1 -1
  63. package/out/replicache/src/make-idb-name.js.map +1 -1
  64. package/out/replicache/src/new-client-channel.js.map +1 -1
  65. package/out/replicache/src/on-persist-channel.js.map +1 -1
  66. package/out/replicache/src/patch-operation.js.map +1 -1
  67. package/out/replicache/src/pending-mutations.js.map +1 -1
  68. package/out/replicache/src/persist/client-gc.js.map +1 -1
  69. package/out/replicache/src/persist/client-group-gc.js.map +1 -1
  70. package/out/replicache/src/persist/client-groups.js +0 -40
  71. package/out/replicache/src/persist/client-groups.js.map +1 -1
  72. package/out/replicache/src/persist/clients.js +0 -28
  73. package/out/replicache/src/persist/clients.js.map +1 -1
  74. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  75. package/out/replicache/src/persist/gather-mem-only-visitor.js.map +1 -1
  76. package/out/replicache/src/persist/gather-not-cached-visitor.js.map +1 -1
  77. package/out/replicache/src/persist/heartbeat.js.map +1 -1
  78. package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
  79. package/out/replicache/src/persist/idb-databases-store.js.map +1 -1
  80. package/out/replicache/src/persist/make-client-id.js.map +1 -1
  81. package/out/replicache/src/persist/persist.js.map +1 -1
  82. package/out/replicache/src/persist/refresh.js.map +1 -1
  83. package/out/replicache/src/process-scheduler.js.map +1 -1
  84. package/out/replicache/src/pusher.js.map +1 -1
  85. package/out/replicache/src/replicache-impl.js.map +1 -1
  86. package/out/replicache/src/report-error.js.map +1 -1
  87. package/out/replicache/src/request-idle.js.map +1 -1
  88. package/out/replicache/src/scan-iterator.js.map +1 -1
  89. package/out/replicache/src/scan-options.js.map +1 -1
  90. package/out/replicache/src/set-interval-with-signal.js.map +1 -1
  91. package/out/replicache/src/subscriptions.js.map +1 -1
  92. package/out/replicache/src/sync/diff.js.map +1 -1
  93. package/out/replicache/src/sync/ids.js.map +1 -1
  94. package/out/replicache/src/sync/patch.js.map +1 -1
  95. package/out/replicache/src/sync/pull-error.js.map +1 -1
  96. package/out/replicache/src/sync/pull.js.map +1 -1
  97. package/out/replicache/src/sync/push.js.map +1 -1
  98. package/out/replicache/src/sync/request-id.js.map +1 -1
  99. package/out/replicache/src/to-error.js.map +1 -1
  100. package/out/replicache/src/transaction-closed-error.js.map +1 -1
  101. package/out/replicache/src/transactions.js.map +1 -1
  102. package/out/replicache/src/with-transactions.js.map +1 -1
  103. package/out/shared/src/abort-error.js.map +1 -1
  104. package/out/shared/src/arrays.js.map +1 -1
  105. package/out/shared/src/asserts.js.map +1 -1
  106. package/out/shared/src/bigint-json.js.map +1 -1
  107. package/out/shared/src/binary-search.js.map +1 -1
  108. package/out/shared/src/broadcast-channel.js.map +1 -1
  109. package/out/shared/src/browser-env.js.map +1 -1
  110. package/out/shared/src/btree-set.js.map +1 -1
  111. package/out/shared/src/cache.js.map +1 -1
  112. package/out/shared/src/centroid.js.map +1 -1
  113. package/out/shared/src/custom-key-map.js.map +1 -1
  114. package/out/shared/src/custom-key-set.js.map +1 -1
  115. package/out/shared/src/deep-clone.js.map +1 -1
  116. package/out/shared/src/deep-merge.js.map +1 -1
  117. package/out/shared/src/document-visible.js.map +1 -1
  118. package/out/shared/src/dotenv.js.map +1 -1
  119. package/out/shared/src/error.js.map +1 -1
  120. package/out/shared/src/hash.js.map +1 -1
  121. package/out/shared/src/iterables.d.ts +0 -2
  122. package/out/shared/src/iterables.d.ts.map +1 -1
  123. package/out/shared/src/iterables.js +1 -9
  124. package/out/shared/src/iterables.js.map +1 -1
  125. package/out/shared/src/json-schema.js.map +1 -1
  126. package/out/shared/src/json.js.map +1 -1
  127. package/out/shared/src/logging-test-utils.js.map +1 -1
  128. package/out/shared/src/logging.js.map +1 -1
  129. package/out/shared/src/map.js.map +1 -1
  130. package/out/shared/src/must.js.map +1 -1
  131. package/out/shared/src/object-traversal.js.map +1 -1
  132. package/out/shared/src/objects.js.map +1 -1
  133. package/out/shared/src/options.js.map +1 -1
  134. package/out/shared/src/parse-big-int.js.map +1 -1
  135. package/out/shared/src/promise-race.js.map +1 -1
  136. package/out/shared/src/queue.d.ts.map +1 -1
  137. package/out/shared/src/queue.js +21 -15
  138. package/out/shared/src/queue.js.map +1 -1
  139. package/out/shared/src/rand.js.map +1 -1
  140. package/out/shared/src/random-uint64.js.map +1 -1
  141. package/out/shared/src/random-values.js.map +1 -1
  142. package/out/shared/src/record-proxy.js.map +1 -1
  143. package/out/shared/src/resolved-promises.js.map +1 -1
  144. package/out/shared/src/sentinels.js.map +1 -1
  145. package/out/shared/src/set-utils.js.map +1 -1
  146. package/out/shared/src/size-of-value.js.map +1 -1
  147. package/out/shared/src/sleep.js.map +1 -1
  148. package/out/shared/src/sorted-entries.js.map +1 -1
  149. package/out/shared/src/string-compare.js.map +1 -1
  150. package/out/shared/src/subscribable.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.map +1 -1
  154. package/out/z2s/src/compiler.js.map +1 -1
  155. package/out/z2s/src/sql.js.map +1 -1
  156. package/out/zero/package.js +26 -34
  157. package/out/zero/package.js.map +1 -1
  158. package/out/zero/src/build-schema.js.map +1 -1
  159. package/out/zero/src/zero-cache-dev.js.map +1 -1
  160. package/out/zero/src/zero-out.js.map +1 -1
  161. package/out/zero-cache/src/auth/auth.js.map +1 -1
  162. package/out/zero-cache/src/auth/jwt.js.map +1 -1
  163. package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
  164. package/out/zero-cache/src/auth/read-authorizer.js.map +1 -1
  165. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  166. package/out/zero-cache/src/config/network.js.map +1 -1
  167. package/out/zero-cache/src/config/normalize.js.map +1 -1
  168. package/out/zero-cache/src/config/server-context.js.map +1 -1
  169. package/out/zero-cache/src/config/zero-config.js +0 -5
  170. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  171. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  172. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  173. package/out/zero-cache/src/db/create.js.map +1 -1
  174. package/out/zero-cache/src/db/delete-lite-db.js.map +1 -1
  175. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  176. package/out/zero-cache/src/db/migration-lite.js +0 -19
  177. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  178. package/out/zero-cache/src/db/migration.js +0 -19
  179. package/out/zero-cache/src/db/migration.js.map +1 -1
  180. package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -1
  181. package/out/zero-cache/src/db/pg-copy.js.map +1 -1
  182. package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
  183. package/out/zero-cache/src/db/pg-type-parser.js.map +1 -1
  184. package/out/zero-cache/src/db/run-transaction.js.map +1 -1
  185. package/out/zero-cache/src/db/specs.js.map +1 -1
  186. package/out/zero-cache/src/db/statements.js.map +1 -1
  187. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  188. package/out/zero-cache/src/db/warmup.js.map +1 -1
  189. package/out/zero-cache/src/observability/events.js.map +1 -1
  190. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  191. package/out/zero-cache/src/scripts/decommission.js.map +1 -1
  192. package/out/zero-cache/src/scripts/deploy-permissions.js.map +1 -1
  193. package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
  194. package/out/zero-cache/src/scripts/permissions.js +2 -1
  195. package/out/zero-cache/src/scripts/permissions.js.map +1 -1
  196. package/out/zero-cache/src/server/anonymous-otel-start.js +7 -8
  197. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  198. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  199. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  200. package/out/zero-cache/src/server/logging.js.map +1 -1
  201. package/out/zero-cache/src/server/main.js.map +1 -1
  202. package/out/zero-cache/src/server/mutator.js.map +1 -1
  203. package/out/zero-cache/src/server/otel-diag-logger.js.map +1 -1
  204. package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
  205. package/out/zero-cache/src/server/otel-start.js.map +1 -1
  206. package/out/zero-cache/src/server/priority-op.js.map +1 -1
  207. package/out/zero-cache/src/server/reaper.js.map +1 -1
  208. package/out/zero-cache/src/server/replicator.js.map +1 -1
  209. package/out/zero-cache/src/server/runner/main.js.map +1 -1
  210. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  211. package/out/zero-cache/src/server/runner/runtime.js.map +1 -1
  212. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  213. package/out/zero-cache/src/server/shadow-syncer.js.map +1 -1
  214. package/out/zero-cache/src/server/syncer.js.map +1 -1
  215. package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
  216. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  217. package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
  218. package/out/zero-cache/src/services/analyze.js +2 -5
  219. package/out/zero-cache/src/services/analyze.js.map +1 -1
  220. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  221. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  222. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  223. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  224. package/out/zero-cache/src/services/change-source/pg/backfill-metadata.js.map +1 -1
  225. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  226. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  227. package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
  228. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  229. package/out/zero-cache/src/services/change-source/pg/logical-replication/binary-reader.js.map +1 -1
  230. package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js.map +1 -1
  231. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  232. package/out/zero-cache/src/services/change-source/pg/lsn.js.map +1 -1
  233. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  234. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  235. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  236. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  237. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  238. package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
  239. package/out/zero-cache/src/services/change-source/protocol/current/control.js.map +1 -1
  240. package/out/zero-cache/src/services/change-source/protocol/current/data.js +0 -2
  241. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  242. package/out/zero-cache/src/services/change-source/protocol/current/downstream.js.map +1 -1
  243. package/out/zero-cache/src/services/change-source/protocol/current/json.js.map +1 -1
  244. package/out/zero-cache/src/services/change-source/protocol/current/status.js.map +1 -1
  245. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  246. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  247. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  248. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  249. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  250. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  251. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  252. package/out/zero-cache/src/services/change-streamer/replica-monitor.js.map +1 -1
  253. package/out/zero-cache/src/services/change-streamer/schema/init.js +25 -21
  254. package/out/zero-cache/src/services/change-streamer/schema/init.js.map +1 -1
  255. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  256. package/out/zero-cache/src/services/change-streamer/snapshot.js +0 -15
  257. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  258. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  259. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  260. package/out/zero-cache/src/services/heapz.js.map +1 -1
  261. package/out/zero-cache/src/services/http-service.js.map +1 -1
  262. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  263. package/out/zero-cache/src/services/limiter/sliding-window-limiter.js.map +1 -1
  264. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  265. package/out/zero-cache/src/services/mutagen/error.js.map +1 -1
  266. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  267. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  268. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  269. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  270. package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
  271. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  272. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  273. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  274. package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -1
  275. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  276. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
  277. package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
  278. package/out/zero-cache/src/services/replicator/schema/table-metadata.js.map +1 -1
  279. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  280. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  281. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  282. package/out/zero-cache/src/services/run-ast.js +0 -1
  283. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  284. package/out/zero-cache/src/services/runner.js.map +1 -1
  285. package/out/zero-cache/src/services/running-state.js.map +1 -1
  286. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
  287. package/out/zero-cache/src/services/statz.js.map +1 -1
  288. package/out/zero-cache/src/services/view-syncer/active-users-gauge.js.map +1 -1
  289. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  290. package/out/zero-cache/src/services/view-syncer/client-schema.js.map +1 -1
  291. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  292. package/out/zero-cache/src/services/view-syncer/cvr-purger.js.map +1 -1
  293. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  294. package/out/zero-cache/src/services/view-syncer/cvr-store.js +1 -2
  295. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  296. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  297. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  298. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +14 -0
  299. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
  300. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +25 -2
  301. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  302. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  303. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  304. package/out/zero-cache/src/services/view-syncer/row-set-signature.js.map +1 -1
  305. package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
  306. package/out/zero-cache/src/services/view-syncer/schema/init.js +113 -97
  307. package/out/zero-cache/src/services/view-syncer/schema/init.js.map +1 -1
  308. package/out/zero-cache/src/services/view-syncer/schema/types.js +1 -103
  309. package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
  310. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  311. package/out/zero-cache/src/services/view-syncer/tracer.js.map +1 -1
  312. package/out/zero-cache/src/services/view-syncer/ttl-clock.js.map +1 -1
  313. package/out/zero-cache/src/services/view-syncer/view-syncer.js +1 -4
  314. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  315. package/out/zero-cache/src/types/configuration-error.js.map +1 -1
  316. package/out/zero-cache/src/types/error-with-level.js.map +1 -1
  317. package/out/zero-cache/src/types/http.js.map +1 -1
  318. package/out/zero-cache/src/types/lexi-version.js.map +1 -1
  319. package/out/zero-cache/src/types/lite.js.map +1 -1
  320. package/out/zero-cache/src/types/names.js.map +1 -1
  321. package/out/zero-cache/src/types/pg-data-type.js.map +1 -1
  322. package/out/zero-cache/src/types/pg.js.map +1 -1
  323. package/out/zero-cache/src/types/processes.js.map +1 -1
  324. package/out/zero-cache/src/types/profiler.js.map +1 -1
  325. package/out/zero-cache/src/types/row-key.js.map +1 -1
  326. package/out/zero-cache/src/types/shards.js.map +1 -1
  327. package/out/zero-cache/src/types/sql.js.map +1 -1
  328. package/out/zero-cache/src/types/state-version.js.map +1 -1
  329. package/out/zero-cache/src/types/streams.js.map +1 -1
  330. package/out/zero-cache/src/types/strings.js.map +1 -1
  331. package/out/zero-cache/src/types/subscription.js.map +1 -1
  332. package/out/zero-cache/src/types/timeout.js.map +1 -1
  333. package/out/zero-cache/src/types/url-params.js.map +1 -1
  334. package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
  335. package/out/zero-cache/src/types/ws.js.map +1 -1
  336. package/out/zero-cache/src/workers/connect-params.js.map +1 -1
  337. package/out/zero-cache/src/workers/connection.js.map +1 -1
  338. package/out/zero-cache/src/workers/mutator.js.map +1 -1
  339. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  340. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  341. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  342. package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
  343. package/out/zero-client/src/client/connection-manager.js +1 -2
  344. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  345. package/out/zero-client/src/client/connection.js.map +1 -1
  346. package/out/zero-client/src/client/context.js.map +1 -1
  347. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  348. package/out/zero-client/src/client/crud.js.map +1 -1
  349. package/out/zero-client/src/client/custom.js +1 -2
  350. package/out/zero-client/src/client/custom.js.map +1 -1
  351. package/out/zero-client/src/client/delete-clients-manager.js.map +1 -1
  352. package/out/zero-client/src/client/enable-analytics.js.map +1 -1
  353. package/out/zero-client/src/client/error.js.map +1 -1
  354. package/out/zero-client/src/client/http-string.js.map +1 -1
  355. package/out/zero-client/src/client/inspector/client-group.js.map +1 -1
  356. package/out/zero-client/src/client/inspector/client.js.map +1 -1
  357. package/out/zero-client/src/client/inspector/html-dialog-prompt.js.map +1 -1
  358. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  359. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  360. package/out/zero-client/src/client/inspector/query.js.map +1 -1
  361. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  362. package/out/zero-client/src/client/keys.js.map +1 -1
  363. package/out/zero-client/src/client/log-options.js.map +1 -1
  364. package/out/zero-client/src/client/make-mutate-property.js.map +1 -1
  365. package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
  366. package/out/zero-client/src/client/metrics.js.map +1 -1
  367. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  368. package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
  369. package/out/zero-client/src/client/options.js.map +1 -1
  370. package/out/zero-client/src/client/query-manager.js.map +1 -1
  371. package/out/zero-client/src/client/reload-error-handler.js.map +1 -1
  372. package/out/zero-client/src/client/server-option.js.map +1 -1
  373. package/out/zero-client/src/client/version.js +1 -1
  374. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  375. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  376. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  377. package/out/zero-client/src/client/zero.js +32 -58
  378. package/out/zero-client/src/client/zero.js.map +1 -1
  379. package/out/zero-client/src/util/nanoid.js.map +1 -1
  380. package/out/zero-client/src/util/socket.d.ts +3 -0
  381. package/out/zero-client/src/util/socket.d.ts.map +1 -0
  382. package/out/zero-client/src/util/socket.js +8 -0
  383. package/out/zero-client/src/util/socket.js.map +1 -0
  384. package/out/zero-protocol/src/analyze-query-result.js +0 -3
  385. package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
  386. package/out/zero-protocol/src/application-error.js.map +1 -1
  387. package/out/zero-protocol/src/ast.js.map +1 -1
  388. package/out/zero-protocol/src/change-desired-queries.js +0 -1
  389. package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
  390. package/out/zero-protocol/src/client-schema.js.map +1 -1
  391. package/out/zero-protocol/src/close-connection.js.map +1 -1
  392. package/out/zero-protocol/src/connect.js +0 -7
  393. package/out/zero-protocol/src/connect.js.map +1 -1
  394. package/out/zero-protocol/src/custom-queries.js.map +1 -1
  395. package/out/zero-protocol/src/data.js.map +1 -1
  396. package/out/zero-protocol/src/delete-clients.js.map +1 -1
  397. package/out/zero-protocol/src/down.js.map +1 -1
  398. package/out/zero-protocol/src/error.js +0 -7
  399. package/out/zero-protocol/src/error.js.map +1 -1
  400. package/out/zero-protocol/src/inspect-down.js.map +1 -1
  401. package/out/zero-protocol/src/inspect-up.js +0 -1
  402. package/out/zero-protocol/src/inspect-up.js.map +1 -1
  403. package/out/zero-protocol/src/mutate-server.js.map +1 -1
  404. package/out/zero-protocol/src/mutation-id.js.map +1 -1
  405. package/out/zero-protocol/src/mutation.js.map +1 -1
  406. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  407. package/out/zero-protocol/src/ping.js.map +1 -1
  408. package/out/zero-protocol/src/poke.js +0 -4
  409. package/out/zero-protocol/src/poke.js.map +1 -1
  410. package/out/zero-protocol/src/pong.js.map +1 -1
  411. package/out/zero-protocol/src/primary-key.js.map +1 -1
  412. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  413. package/out/zero-protocol/src/pull.js.map +1 -1
  414. package/out/zero-protocol/src/push.js +0 -16
  415. package/out/zero-protocol/src/push.js.map +1 -1
  416. package/out/zero-protocol/src/queries-patch.js.map +1 -1
  417. package/out/zero-protocol/src/query-hash.js.map +1 -1
  418. package/out/zero-protocol/src/query-server.js.map +1 -1
  419. package/out/zero-protocol/src/row-patch.js.map +1 -1
  420. package/out/zero-protocol/src/up.js.map +1 -1
  421. package/out/zero-protocol/src/update-auth.js.map +1 -1
  422. package/out/zero-protocol/src/version.js.map +1 -1
  423. package/out/zero-react/src/use-connection-state.js.map +1 -1
  424. package/out/zero-react/src/use-query.js.map +1 -1
  425. package/out/zero-react/src/use-zero-online.js.map +1 -1
  426. package/out/zero-react/src/zero-provider.js.map +1 -1
  427. package/out/zero-schema/src/builder/relationship-builder.js.map +1 -1
  428. package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
  429. package/out/zero-schema/src/builder/table-builder.js.map +1 -1
  430. package/out/zero-schema/src/compiled-permissions.js.map +1 -1
  431. package/out/zero-schema/src/name-mapper.js.map +1 -1
  432. package/out/zero-schema/src/permissions.js.map +1 -1
  433. package/out/zero-schema/src/schema-config.js.map +1 -1
  434. package/out/zero-server/src/adapters/drizzle.js.map +1 -1
  435. package/out/zero-server/src/adapters/kysely.js.map +1 -1
  436. package/out/zero-server/src/adapters/pg.js.map +1 -1
  437. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  438. package/out/zero-server/src/adapters/prisma.js.map +1 -1
  439. package/out/zero-server/src/custom.js +1 -2
  440. package/out/zero-server/src/custom.js.map +1 -1
  441. package/out/zero-server/src/logging.js.map +1 -1
  442. package/out/zero-server/src/pg-query-executor.js.map +1 -1
  443. package/out/zero-server/src/process-mutations.js.map +1 -1
  444. package/out/zero-server/src/push-processor.js.map +1 -1
  445. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  446. package/out/zero-server/src/schema.js.map +1 -1
  447. package/out/zero-server/src/zql-database.js.map +1 -1
  448. package/out/zero-solid/src/solid-view.js.map +1 -1
  449. package/out/zero-solid/src/use-connection-state.js.map +1 -1
  450. package/out/zero-solid/src/use-query.js.map +1 -1
  451. package/out/zero-solid/src/use-zero-online.js.map +1 -1
  452. package/out/zero-solid/src/use-zero.js.map +1 -1
  453. package/out/zero-types/src/format.js.map +1 -1
  454. package/out/zero-types/src/name-mapper.js.map +1 -1
  455. package/out/zql/src/builder/builder.js.map +1 -1
  456. package/out/zql/src/builder/debug-delegate.d.ts +0 -5
  457. package/out/zql/src/builder/debug-delegate.d.ts.map +1 -1
  458. package/out/zql/src/builder/debug-delegate.js +1 -10
  459. package/out/zql/src/builder/debug-delegate.js.map +1 -1
  460. package/out/zql/src/builder/filter.js.map +1 -1
  461. package/out/zql/src/builder/like.js.map +1 -1
  462. package/out/zql/src/error.js.map +1 -1
  463. package/out/zql/src/ivm/array-view.js.map +1 -1
  464. package/out/zql/src/ivm/cap.js.map +1 -1
  465. package/out/zql/src/ivm/change.js.map +1 -1
  466. package/out/zql/src/ivm/constraint.js +1 -1
  467. package/out/zql/src/ivm/constraint.js.map +1 -1
  468. package/out/zql/src/ivm/data.js.map +1 -1
  469. package/out/zql/src/ivm/exists.js.map +1 -1
  470. package/out/zql/src/ivm/fan-in.js.map +1 -1
  471. package/out/zql/src/ivm/fan-out.js.map +1 -1
  472. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  473. package/out/zql/src/ivm/filter-push.js.map +1 -1
  474. package/out/zql/src/ivm/filter.js.map +1 -1
  475. package/out/zql/src/ivm/flipped-join.d.ts +8 -4
  476. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  477. package/out/zql/src/ivm/flipped-join.js +63 -59
  478. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  479. package/out/zql/src/ivm/join-utils.js.map +1 -1
  480. package/out/zql/src/ivm/join.js.map +1 -1
  481. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  482. package/out/zql/src/ivm/memory-source.js.map +1 -1
  483. package/out/zql/src/ivm/memory-storage.js.map +1 -1
  484. package/out/zql/src/ivm/operator.d.ts +1 -1
  485. package/out/zql/src/ivm/operator.js.map +1 -1
  486. package/out/zql/src/ivm/push-accumulated.js.map +1 -1
  487. package/out/zql/src/ivm/schema.d.ts +8 -0
  488. package/out/zql/src/ivm/schema.d.ts.map +1 -1
  489. package/out/zql/src/ivm/skip-yields.js.map +1 -1
  490. package/out/zql/src/ivm/skip.js.map +1 -1
  491. package/out/zql/src/ivm/source.js.map +1 -1
  492. package/out/zql/src/ivm/stream.js.map +1 -1
  493. package/out/zql/src/ivm/take.js.map +1 -1
  494. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  495. package/out/zql/src/ivm/union-fan-out.js.map +1 -1
  496. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  497. package/out/zql/src/mutate/crud.js.map +1 -1
  498. package/out/zql/src/mutate/custom.js.map +1 -1
  499. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  500. package/out/zql/src/mutate/mutator.js.map +1 -1
  501. package/out/zql/src/planner/planner-builder.js.map +1 -1
  502. package/out/zql/src/planner/planner-connection.js.map +1 -1
  503. package/out/zql/src/planner/planner-constraint.js.map +1 -1
  504. package/out/zql/src/planner/planner-debug.js.map +1 -1
  505. package/out/zql/src/planner/planner-fan-in.js.map +1 -1
  506. package/out/zql/src/planner/planner-fan-out.js.map +1 -1
  507. package/out/zql/src/planner/planner-graph.js.map +1 -1
  508. package/out/zql/src/planner/planner-join.d.ts.map +1 -1
  509. package/out/zql/src/planner/planner-join.js +1 -2
  510. package/out/zql/src/planner/planner-join.js.map +1 -1
  511. package/out/zql/src/planner/planner-node.js.map +1 -1
  512. package/out/zql/src/planner/planner-source.js.map +1 -1
  513. package/out/zql/src/planner/planner-terminus.js.map +1 -1
  514. package/out/zql/src/query/complete-ordering.js.map +1 -1
  515. package/out/zql/src/query/create-builder.js.map +1 -1
  516. package/out/zql/src/query/error.js.map +1 -1
  517. package/out/zql/src/query/escape-like.js.map +1 -1
  518. package/out/zql/src/query/expression.js.map +1 -1
  519. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  520. package/out/zql/src/query/metrics-delegate.js.map +1 -1
  521. package/out/zql/src/query/named.js.map +1 -1
  522. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  523. package/out/zql/src/query/query-impl.js +1 -1
  524. package/out/zql/src/query/query-impl.js.map +1 -1
  525. package/out/zql/src/query/query-internals.js.map +1 -1
  526. package/out/zql/src/query/query-registry.js.map +1 -1
  527. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  528. package/out/zql/src/query/static-query.js.map +1 -1
  529. package/out/zql/src/query/ttl.js.map +1 -1
  530. package/out/zql/src/query/validate-input.js.map +1 -1
  531. package/out/zqlite/src/database-storage.js.map +1 -1
  532. package/out/zqlite/src/db.js.map +1 -1
  533. package/out/zqlite/src/explain-queries.js.map +1 -1
  534. package/out/zqlite/src/internal/sql-inline.js.map +1 -1
  535. package/out/zqlite/src/internal/sql.js.map +1 -1
  536. package/out/zqlite/src/internal/statement-cache.js.map +1 -1
  537. package/out/zqlite/src/query-builder.js.map +1 -1
  538. package/out/zqlite/src/query-delegate.js.map +1 -1
  539. package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
  540. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  541. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  542. package/out/zqlite/src/table-source.d.ts.map +1 -1
  543. package/out/zqlite/src/table-source.js +6 -6
  544. package/out/zqlite/src/table-source.js.map +1 -1
  545. package/package.json +26 -42
  546. package/out/shared/src/ring-buffer.d.ts +0 -32
  547. package/out/shared/src/ring-buffer.d.ts.map +0 -1
  548. package/out/shared/src/ring-buffer.js +0 -109
  549. package/out/shared/src/ring-buffer.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"runnable-query-impl.js","names":["#delegate"],"sources":["../../../../../zql/src/query/runnable-query-impl.ts"],"sourcesContent":["import type {AST, System} from '../../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {defaultFormat} from '../ivm/default-format.ts';\nimport type {Format, ViewFactory} from '../ivm/view.ts';\nimport type {CustomQueryID} from './named.ts';\nimport type {QueryDelegate} from './query-delegate.ts';\nimport {QueryImpl} from './query-impl.ts';\nimport type {\n HumanReadable,\n MaterializeOptions,\n PreloadOptions,\n PullRow,\n Query,\n RunOptions,\n} from './query.ts';\nimport type {TTL} from './ttl.ts';\nimport type {TypedView} from './typed-view.ts';\n\nexport function newRunnableQuery<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n>(\n delegate: QueryDelegate,\n schema: TSchema,\n table: TTable,\n): Query<TTable, TSchema> {\n return new RunnableQueryImpl(\n delegate,\n schema,\n table,\n {table},\n defaultFormat,\n undefined,\n );\n}\n\nexport class RunnableQueryImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn = PullRow<TTable, TSchema>,\n>\n extends QueryImpl<TTable, TSchema, TReturn>\n implements Query<TTable, TSchema, TReturn>\n{\n readonly #delegate: QueryDelegate;\n\n constructor(\n delegate: QueryDelegate,\n schema: TSchema,\n tableName: TTable,\n ast: AST = {table: tableName},\n format: Format = defaultFormat,\n system: System = 'client',\n customQueryID?: CustomQueryID,\n currentJunction?: string,\n ) {\n super(\n schema,\n tableName,\n ast,\n format,\n system,\n customQueryID,\n currentJunction,\n (tableName, ast, format, customQueryID, currentJunction) =>\n new RunnableQueryImpl(\n delegate,\n schema,\n tableName,\n ast,\n format,\n system,\n customQueryID,\n currentJunction,\n ),\n );\n this.#delegate = delegate;\n }\n\n override run(options?: RunOptions): Promise<HumanReadable<TReturn>> {\n return this.#delegate.run(this, options);\n }\n\n override preload(options?: PreloadOptions): {\n cleanup: () => void;\n complete: Promise<void>;\n } {\n return this.#delegate.preload(this, options);\n }\n\n override materialize(ttl?: TTL): TypedView<HumanReadable<TReturn>>;\n override materialize<T>(\n factory: ViewFactory<TTable, TSchema, TReturn, T>,\n ttl?: TTL,\n ): T;\n override materialize<T>(\n factory?: unknown,\n ttl?: unknown,\n ): T | TypedView<HumanReadable<TReturn>> {\n let actualFactory: ViewFactory<TTable, TSchema, TReturn, T> | undefined;\n let options: MaterializeOptions | undefined;\n\n if (typeof factory === 'function') {\n actualFactory = factory as ViewFactory<TTable, TSchema, TReturn, T>;\n options = {ttl: ttl as TTL | undefined};\n } else {\n actualFactory = undefined;\n options = {ttl: factory as TTL | undefined};\n }\n\n return this.#delegate.materialize(this, actualFactory, options);\n }\n}\n"],"mappings":";;;AAkBA,SAAgB,iBAId,UACA,QACA,OACwB;CACxB,OAAO,IAAI,kBACT,UACA,QACA,OACA,EAAC,MAAK,GACN,eACA,KAAA,CACF;AACF;AAEA,IAAa,oBAAb,MAAa,0BAKH,UAEV;CACE;CAEA,YACE,UACA,QACA,WACA,MAAW,EAAC,OAAO,UAAS,GAC5B,SAAiB,eACjB,SAAiB,UACjB,eACA,iBACA;EACA,MACE,QACA,WACA,KACA,QACA,QACA,eACA,kBACC,WAAW,KAAK,QAAQ,eAAe,oBACtC,IAAI,kBACF,UACA,QACA,WACA,KACA,QACA,QACA,eACA,eACF,CACJ;EACA,KAAKA,YAAY;CACnB;CAEA,IAAa,SAAuD;EAClE,OAAO,KAAKA,UAAU,IAAI,MAAM,OAAO;CACzC;CAEA,QAAiB,SAGf;EACA,OAAO,KAAKA,UAAU,QAAQ,MAAM,OAAO;CAC7C;CAOA,YACE,SACA,KACuC;EACvC,IAAI;EACJ,IAAI;EAEJ,IAAI,OAAO,YAAY,YAAY;GACjC,gBAAgB;GAChB,UAAU,EAAM,IAAsB;EACxC,OAAO;GACL,gBAAgB,KAAA;GAChB,UAAU,EAAC,KAAK,QAA0B;EAC5C;EAEA,OAAO,KAAKA,UAAU,YAAY,MAAM,eAAe,OAAO;CAChE;AACF"}
1
+ {"version":3,"file":"runnable-query-impl.js","names":["#delegate"],"sources":["../../../../../zql/src/query/runnable-query-impl.ts"],"sourcesContent":["import type {AST, System} from '../../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {defaultFormat} from '../ivm/default-format.ts';\nimport type {Format, ViewFactory} from '../ivm/view.ts';\nimport type {CustomQueryID} from './named.ts';\nimport type {QueryDelegate} from './query-delegate.ts';\nimport {QueryImpl} from './query-impl.ts';\nimport type {\n HumanReadable,\n MaterializeOptions,\n PreloadOptions,\n PullRow,\n Query,\n RunOptions,\n} from './query.ts';\nimport type {TTL} from './ttl.ts';\nimport type {TypedView} from './typed-view.ts';\n\nexport function newRunnableQuery<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n>(\n delegate: QueryDelegate,\n schema: TSchema,\n table: TTable,\n): Query<TTable, TSchema> {\n return new RunnableQueryImpl(\n delegate,\n schema,\n table,\n {table},\n defaultFormat,\n undefined,\n );\n}\n\nexport class RunnableQueryImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn = PullRow<TTable, TSchema>,\n>\n extends QueryImpl<TTable, TSchema, TReturn>\n implements Query<TTable, TSchema, TReturn>\n{\n readonly #delegate: QueryDelegate;\n\n constructor(\n delegate: QueryDelegate,\n schema: TSchema,\n tableName: TTable,\n ast: AST = {table: tableName},\n format: Format = defaultFormat,\n system: System = 'client',\n customQueryID?: CustomQueryID,\n currentJunction?: string,\n ) {\n super(\n schema,\n tableName,\n ast,\n format,\n system,\n customQueryID,\n currentJunction,\n (tableName, ast, format, customQueryID, currentJunction) =>\n new RunnableQueryImpl(\n delegate,\n schema,\n tableName,\n ast,\n format,\n system,\n customQueryID,\n currentJunction,\n ),\n );\n this.#delegate = delegate;\n }\n\n override run(options?: RunOptions): Promise<HumanReadable<TReturn>> {\n return this.#delegate.run(this, options);\n }\n\n override preload(options?: PreloadOptions): {\n cleanup: () => void;\n complete: Promise<void>;\n } {\n return this.#delegate.preload(this, options);\n }\n\n override materialize(ttl?: TTL): TypedView<HumanReadable<TReturn>>;\n override materialize<T>(\n factory: ViewFactory<TTable, TSchema, TReturn, T>,\n ttl?: TTL,\n ): T;\n override materialize<T>(\n factory?: unknown,\n ttl?: unknown,\n ): T | TypedView<HumanReadable<TReturn>> {\n let actualFactory: ViewFactory<TTable, TSchema, TReturn, T> | undefined;\n let options: MaterializeOptions | undefined;\n\n if (typeof factory === 'function') {\n actualFactory = factory as ViewFactory<TTable, TSchema, TReturn, T>;\n options = {ttl: ttl as TTL | undefined};\n } else {\n actualFactory = undefined;\n options = {ttl: factory as TTL | undefined};\n }\n\n return this.#delegate.materialize(this, actualFactory, options);\n }\n}\n"],"mappings":";;;AAkBA,SAAgB,iBAId,UACA,QACA,OACwB;AACxB,QAAO,IAAI,kBACT,UACA,QACA,OACA,EAAC,OAAM,EACP,eACA,KAAA,EACD;;AAGH,IAAa,oBAAb,MAAa,0BAKH,UAEV;CACE;CAEA,YACE,UACA,QACA,WACA,MAAW,EAAC,OAAO,WAAU,EAC7B,SAAiB,eACjB,SAAiB,UACjB,eACA,iBACA;AACA,QACE,QACA,WACA,KACA,QACA,QACA,eACA,kBACC,WAAW,KAAK,QAAQ,eAAe,oBACtC,IAAI,kBACF,UACA,QACA,WACA,KACA,QACA,QACA,eACA,gBACD,CACJ;AACD,QAAA,WAAiB;;CAGnB,IAAa,SAAuD;AAClE,SAAO,MAAA,SAAe,IAAI,MAAM,QAAQ;;CAG1C,QAAiB,SAGf;AACA,SAAO,MAAA,SAAe,QAAQ,MAAM,QAAQ;;CAQ9C,YACE,SACA,KACuC;EACvC,IAAI;EACJ,IAAI;AAEJ,MAAI,OAAO,YAAY,YAAY;AACjC,mBAAgB;AAChB,aAAU,EAAM,KAAuB;SAClC;AACL,mBAAgB,KAAA;AAChB,aAAU,EAAC,KAAK,SAA2B;;AAG7C,SAAO,MAAA,SAAe,YAAY,MAAM,eAAe,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"static-query.js","names":[],"sources":["../../../../../zql/src/query/static-query.ts"],"sourcesContent":["import type {Schema} from '../../../zero-types/src/schema.ts';\nimport {defaultFormat} from '../ivm/default-format.ts';\nimport {newQueryImpl, type QueryImpl} from './query-impl.ts';\nimport type {PullRow, Query} from './query.ts';\n\nexport function newStaticQuery<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn = PullRow<TTable, TSchema>,\n>(schema: TSchema, tableName: TTable): Query<TTable, TSchema, TReturn> {\n return newQueryImpl(\n schema,\n tableName,\n {table: tableName},\n defaultFormat,\n 'permissions',\n );\n}\n\nexport function newExpressionBuilder<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n>(schema: TSchema, tableName: TTable) {\n const q = newStaticQuery(schema, tableName);\n return (q as QueryImpl<TTable, TSchema>).expressionBuilder();\n}\n"],"mappings":";;;AAKA,SAAgB,eAId,QAAiB,WAAoD;CACrE,OAAO,aACL,QACA,WACA,EAAC,OAAO,UAAS,GACjB,eACA,aACF;AACF;AAEA,SAAgB,qBAGd,QAAiB,WAAmB;CAEpC,OADU,eAAe,QAAQ,SACzB,EAAiC,kBAAkB;AAC7D"}
1
+ {"version":3,"file":"static-query.js","names":[],"sources":["../../../../../zql/src/query/static-query.ts"],"sourcesContent":["import type {Schema} from '../../../zero-types/src/schema.ts';\nimport {defaultFormat} from '../ivm/default-format.ts';\nimport {newQueryImpl, type QueryImpl} from './query-impl.ts';\nimport type {PullRow, Query} from './query.ts';\n\nexport function newStaticQuery<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn = PullRow<TTable, TSchema>,\n>(schema: TSchema, tableName: TTable): Query<TTable, TSchema, TReturn> {\n return newQueryImpl(\n schema,\n tableName,\n {table: tableName},\n defaultFormat,\n 'permissions',\n );\n}\n\nexport function newExpressionBuilder<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n>(schema: TSchema, tableName: TTable) {\n const q = newStaticQuery(schema, tableName);\n return (q as QueryImpl<TTable, TSchema>).expressionBuilder();\n}\n"],"mappings":";;;AAKA,SAAgB,eAId,QAAiB,WAAoD;AACrE,QAAO,aACL,QACA,WACA,EAAC,OAAO,WAAU,EAClB,eACA,cACD;;AAGH,SAAgB,qBAGd,QAAiB,WAAmB;AAEpC,QADU,eAAe,QAAQ,UAAU,CACF,mBAAmB"}
@@ -1 +1 @@
1
- {"version":3,"file":"ttl.js","names":[],"sources":["../../../../../zql/src/query/ttl.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\n\nexport type TimeUnit = 's' | 'm' | 'h' | 'd' | 'y';\n\n/**\n * Time To Live. This is used for query expiration.\n * - `forever` means the query will never expire.\n * - `none` means the query will expire immediately.\n * - A number means the query will expire after that many milliseconds.\n * - A negative number means the query will never expire, this is same as 'forever'.\n * - A string like `1s` means the query will expire after that many seconds.\n * - A string like `1m` means the query will expire after that many minutes.\n * - A string like `1h` means the query will expire after that many hours.\n * - A string like `1d` means the query will expire after that many days.\n * - A string like `1y` means the query will expire after that many years.\n */\nexport type TTL = `${number}${TimeUnit}` | 'forever' | 'none' | number;\n\nexport const DEFAULT_TTL: TTL = '5m';\nexport const DEFAULT_TTL_MS = 1_000 * 60 * 5;\n\nexport const DEFAULT_PRELOAD_TTL: TTL = 'none';\nexport const DEFAULT_PRELOAD_TTL_MS = 0;\n\nexport const MAX_TTL: TTL = '10m';\nexport const MAX_TTL_MS = 1_000 * 60 * 10;\n\nconst multiplier = {\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n y: 365 * 24 * 60 * 60 * 1000,\n} as const;\n\nexport function parseTTL(ttl: TTL): number {\n if (typeof ttl === 'number') {\n return Number.isNaN(ttl) ? 0 : !Number.isFinite(ttl) || ttl < 0 ? -1 : ttl;\n }\n if (ttl === 'none') {\n return 0;\n }\n if (ttl === 'forever') {\n return -1;\n }\n const multi = multiplier[ttl.at(-1) as TimeUnit];\n return Number(ttl.slice(0, -1)) * multi;\n}\n\nexport function compareTTL(a: TTL, b: TTL): number {\n const ap = parseTTL(a);\n const bp = parseTTL(b);\n if (ap === -1 && bp !== -1) {\n return 1;\n }\n if (ap !== -1 && bp === -1) {\n return -1;\n }\n return ap - bp;\n}\n\nexport function normalizeTTL(ttl: TTL): TTL {\n if (typeof ttl === 'string') {\n return ttl;\n }\n\n if (ttl < 0) {\n return 'forever';\n }\n\n if (ttl === 0) {\n return 'none';\n }\n\n let shortest = ttl.toString();\n const lengthOfNumber = shortest.length;\n for (const unit of ['y', 'd', 'h', 'm', 's'] as const) {\n const multi = multiplier[unit];\n const value = ttl / multi;\n const candidate = `${value}${unit}`;\n if (candidate.length < shortest.length) {\n shortest = candidate;\n }\n }\n\n return (shortest.length < lengthOfNumber ? shortest : ttl) as TTL;\n}\n\nexport function clampTTL(ttl: TTL, lc?: Pick<LogContext, 'warn'>): number {\n const parsedTTL = parseTTL(ttl);\n if (parsedTTL === -1 || parsedTTL > 10 * 60 * 1000) {\n // 10 minutes in milliseconds\n lc?.warn?.(`TTL (${ttl}) is too high, clamping to ${MAX_TTL}`);\n return parseTTL(MAX_TTL);\n }\n return parsedTTL;\n}\n"],"mappings":";AAmBA,IAAa,iBAAiB,MAAQ,KAAK;AAM3C,IAAa,aAAa,MAAQ,KAAK;AAEvC,IAAM,aAAa;CACjB,GAAG;CACH,GAAG,KAAK;CACR,GAAG,OAAU;CACb,GAAG,OAAU,KAAK;CAClB,GAAG,MAAM,KAAK,KAAK,KAAK;AAC1B;AAEA,SAAgB,SAAS,KAAkB;CACzC,IAAI,OAAO,QAAQ,UACjB,OAAO,OAAO,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,KAAK;CAEzE,IAAI,QAAQ,QACV,OAAO;CAET,IAAI,QAAQ,WACV,OAAO;CAET,MAAM,QAAQ,WAAW,IAAI,GAAG,EAAE;CAClC,OAAO,OAAO,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI;AACpC;AAEA,SAAgB,WAAW,GAAQ,GAAgB;CACjD,MAAM,KAAK,SAAS,CAAC;CACrB,MAAM,KAAK,SAAS,CAAC;CACrB,IAAI,OAAO,MAAM,OAAO,IACtB,OAAO;CAET,IAAI,OAAO,MAAM,OAAO,IACtB,OAAO;CAET,OAAO,KAAK;AACd;AAEA,SAAgB,aAAa,KAAe;CAC1C,IAAI,OAAO,QAAQ,UACjB,OAAO;CAGT,IAAI,MAAM,GACR,OAAO;CAGT,IAAI,QAAQ,GACV,OAAO;CAGT,IAAI,WAAW,IAAI,SAAS;CAC5B,MAAM,iBAAiB,SAAS;CAChC,KAAK,MAAM,QAAQ;EAAC;EAAK;EAAK;EAAK;EAAK;CAAG,GAAY;EAGrD,MAAM,YAAY,GADJ,MADA,WAAW,QAEI;EAC7B,IAAI,UAAU,SAAS,SAAS,QAC9B,WAAW;CAEf;CAEA,OAAQ,SAAS,SAAS,iBAAiB,WAAW;AACxD;AAEA,SAAgB,SAAS,KAAU,IAAuC;CACxE,MAAM,YAAY,SAAS,GAAG;CAC9B,IAAI,cAAc,MAAM,YAAY,MAAU,KAAM;EAElD,IAAI,OAAO,QAAQ,IAAI,+BAAsC;EAC7D,OAAO,SAAA,KAAgB;CACzB;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"ttl.js","names":[],"sources":["../../../../../zql/src/query/ttl.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\n\nexport type TimeUnit = 's' | 'm' | 'h' | 'd' | 'y';\n\n/**\n * Time To Live. This is used for query expiration.\n * - `forever` means the query will never expire.\n * - `none` means the query will expire immediately.\n * - A number means the query will expire after that many milliseconds.\n * - A negative number means the query will never expire, this is same as 'forever'.\n * - A string like `1s` means the query will expire after that many seconds.\n * - A string like `1m` means the query will expire after that many minutes.\n * - A string like `1h` means the query will expire after that many hours.\n * - A string like `1d` means the query will expire after that many days.\n * - A string like `1y` means the query will expire after that many years.\n */\nexport type TTL = `${number}${TimeUnit}` | 'forever' | 'none' | number;\n\nexport const DEFAULT_TTL: TTL = '5m';\nexport const DEFAULT_TTL_MS = 1_000 * 60 * 5;\n\nexport const DEFAULT_PRELOAD_TTL: TTL = 'none';\nexport const DEFAULT_PRELOAD_TTL_MS = 0;\n\nexport const MAX_TTL: TTL = '10m';\nexport const MAX_TTL_MS = 1_000 * 60 * 10;\n\nconst multiplier = {\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n y: 365 * 24 * 60 * 60 * 1000,\n} as const;\n\nexport function parseTTL(ttl: TTL): number {\n if (typeof ttl === 'number') {\n return Number.isNaN(ttl) ? 0 : !Number.isFinite(ttl) || ttl < 0 ? -1 : ttl;\n }\n if (ttl === 'none') {\n return 0;\n }\n if (ttl === 'forever') {\n return -1;\n }\n const multi = multiplier[ttl.at(-1) as TimeUnit];\n return Number(ttl.slice(0, -1)) * multi;\n}\n\nexport function compareTTL(a: TTL, b: TTL): number {\n const ap = parseTTL(a);\n const bp = parseTTL(b);\n if (ap === -1 && bp !== -1) {\n return 1;\n }\n if (ap !== -1 && bp === -1) {\n return -1;\n }\n return ap - bp;\n}\n\nexport function normalizeTTL(ttl: TTL): TTL {\n if (typeof ttl === 'string') {\n return ttl;\n }\n\n if (ttl < 0) {\n return 'forever';\n }\n\n if (ttl === 0) {\n return 'none';\n }\n\n let shortest = ttl.toString();\n const lengthOfNumber = shortest.length;\n for (const unit of ['y', 'd', 'h', 'm', 's'] as const) {\n const multi = multiplier[unit];\n const value = ttl / multi;\n const candidate = `${value}${unit}`;\n if (candidate.length < shortest.length) {\n shortest = candidate;\n }\n }\n\n return (shortest.length < lengthOfNumber ? shortest : ttl) as TTL;\n}\n\nexport function clampTTL(ttl: TTL, lc?: Pick<LogContext, 'warn'>): number {\n const parsedTTL = parseTTL(ttl);\n if (parsedTTL === -1 || parsedTTL > 10 * 60 * 1000) {\n // 10 minutes in milliseconds\n lc?.warn?.(`TTL (${ttl}) is too high, clamping to ${MAX_TTL}`);\n return parseTTL(MAX_TTL);\n }\n return parsedTTL;\n}\n"],"mappings":";AAmBA,IAAa,iBAAiB,MAAQ,KAAK;AAM3C,IAAa,aAAa,MAAQ,KAAK;AAEvC,IAAM,aAAa;CACjB,GAAG;CACH,GAAG,KAAK;CACR,GAAG,OAAU;CACb,GAAG,OAAU,KAAK;CAClB,GAAG,MAAM,KAAK,KAAK,KAAK;CACzB;AAED,SAAgB,SAAS,KAAkB;AACzC,KAAI,OAAO,QAAQ,SACjB,QAAO,OAAO,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,SAAS,IAAI,IAAI,MAAM,IAAI,KAAK;AAEzE,KAAI,QAAQ,OACV,QAAO;AAET,KAAI,QAAQ,UACV,QAAO;CAET,MAAM,QAAQ,WAAW,IAAI,GAAG,GAAG;AACnC,QAAO,OAAO,IAAI,MAAM,GAAG,GAAG,CAAC,GAAG;;AAGpC,SAAgB,WAAW,GAAQ,GAAgB;CACjD,MAAM,KAAK,SAAS,EAAE;CACtB,MAAM,KAAK,SAAS,EAAE;AACtB,KAAI,OAAO,MAAM,OAAO,GACtB,QAAO;AAET,KAAI,OAAO,MAAM,OAAO,GACtB,QAAO;AAET,QAAO,KAAK;;AAGd,SAAgB,aAAa,KAAe;AAC1C,KAAI,OAAO,QAAQ,SACjB,QAAO;AAGT,KAAI,MAAM,EACR,QAAO;AAGT,KAAI,QAAQ,EACV,QAAO;CAGT,IAAI,WAAW,IAAI,UAAU;CAC7B,MAAM,iBAAiB,SAAS;AAChC,MAAK,MAAM,QAAQ;EAAC;EAAK;EAAK;EAAK;EAAK;EAAI,EAAW;EAGrD,MAAM,YAAY,GADJ,MADA,WAAW,QAEI;AAC7B,MAAI,UAAU,SAAS,SAAS,OAC9B,YAAW;;AAIf,QAAQ,SAAS,SAAS,iBAAiB,WAAW;;AAGxD,SAAgB,SAAS,KAAU,IAAuC;CACxE,MAAM,YAAY,SAAS,IAAI;AAC/B,KAAI,cAAc,MAAM,YAAY,MAAU,KAAM;AAElD,MAAI,OAAO,QAAQ,IAAI,gCAAuC;AAC9D,SAAO,SAAA,MAAiB;;AAE1B,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"validate-input.js","names":[],"sources":["../../../../../zql/src/query/validate-input.ts"],"sourcesContent":["import type {StandardSchemaV1} from '@standard-schema/spec';\n\nexport class InputValidationError extends Error {\n readonly result: StandardSchemaV1.FailureResult;\n\n // will get picked up by `getErrorDetails` when mapping to an `ApplicationError`\n readonly details: {\n type: 'InputValidationError';\n result: StandardSchemaV1.FailureResult;\n };\n\n constructor(message: string, result: StandardSchemaV1.FailureResult) {\n super(message);\n this.name = 'InputValidationError';\n this.result = result;\n this.details = {type: 'InputValidationError', result};\n }\n}\n\n/**\n * Validates input using a StandardSchema validator if provided.\n * This is shared validation logic used by both defineQuery and defineMutator.\n *\n * @param name - The name of the query or mutator (for error messages)\n * @param input - The input value to validate\n * @param validator - Optional StandardSchema validator\n * @param kind - Type of definition ('query' or 'mutator') for error messages\n * @returns The validated output (either transformed by validator or input as-is)\n * @throws Error if validation fails or if an async validator is used\n * @internal\n */\nexport function validateInput<TInput, TOutput>(\n name: string,\n input: TInput,\n validator: StandardSchemaV1<TInput, TOutput> | undefined,\n kind: 'query' | 'mutator',\n): TOutput {\n if (!validator) {\n // No validator, so input and output are the same\n return input as unknown as TOutput;\n }\n\n const result = validator['~standard'].validate(input);\n if (result instanceof Promise) {\n throw new Error(\n `Async validators are not supported. ${titleCase(kind)} name ${name}`,\n );\n }\n if (result.issues) {\n throw new InputValidationError(\n `Validation failed for ${kind} ${name}: ${result.issues\n .map(issue => issue.message)\n .join(', ')}`,\n result,\n );\n }\n return result.value;\n}\n\nfunction titleCase(kind: string): string {\n return kind.charAt(0).toUpperCase() + kind.slice(1);\n}\n"],"mappings":";AAEA,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CAGA;CAKA,YAAY,SAAiB,QAAwC;EACnE,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,UAAU;GAAC,MAAM;GAAwB;EAAM;CACtD;AACF;;;;;;;;;;;;;AAcA,SAAgB,cACd,MACA,OACA,WACA,MACS;CACT,IAAI,CAAC,WAEH,OAAO;CAGT,MAAM,SAAS,UAAU,aAAa,SAAS,KAAK;CACpD,IAAI,kBAAkB,SACpB,MAAM,IAAI,MACR,uCAAuC,UAAU,IAAI,EAAE,QAAQ,MACjE;CAEF,IAAI,OAAO,QACT,MAAM,IAAI,qBACR,yBAAyB,KAAK,GAAG,KAAK,IAAI,OAAO,OAC9C,KAAI,UAAS,MAAM,OAAO,EAC1B,KAAK,IAAI,KACZ,MACF;CAEF,OAAO,OAAO;AAChB;AAEA,SAAS,UAAU,MAAsB;CACvC,OAAO,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC;AACpD"}
1
+ {"version":3,"file":"validate-input.js","names":[],"sources":["../../../../../zql/src/query/validate-input.ts"],"sourcesContent":["import type {StandardSchemaV1} from '@standard-schema/spec';\n\nexport class InputValidationError extends Error {\n readonly result: StandardSchemaV1.FailureResult;\n\n // will get picked up by `getErrorDetails` when mapping to an `ApplicationError`\n readonly details: {\n type: 'InputValidationError';\n result: StandardSchemaV1.FailureResult;\n };\n\n constructor(message: string, result: StandardSchemaV1.FailureResult) {\n super(message);\n this.name = 'InputValidationError';\n this.result = result;\n this.details = {type: 'InputValidationError', result};\n }\n}\n\n/**\n * Validates input using a StandardSchema validator if provided.\n * This is shared validation logic used by both defineQuery and defineMutator.\n *\n * @param name - The name of the query or mutator (for error messages)\n * @param input - The input value to validate\n * @param validator - Optional StandardSchema validator\n * @param kind - Type of definition ('query' or 'mutator') for error messages\n * @returns The validated output (either transformed by validator or input as-is)\n * @throws Error if validation fails or if an async validator is used\n * @internal\n */\nexport function validateInput<TInput, TOutput>(\n name: string,\n input: TInput,\n validator: StandardSchemaV1<TInput, TOutput> | undefined,\n kind: 'query' | 'mutator',\n): TOutput {\n if (!validator) {\n // No validator, so input and output are the same\n return input as unknown as TOutput;\n }\n\n const result = validator['~standard'].validate(input);\n if (result instanceof Promise) {\n throw new Error(\n `Async validators are not supported. ${titleCase(kind)} name ${name}`,\n );\n }\n if (result.issues) {\n throw new InputValidationError(\n `Validation failed for ${kind} ${name}: ${result.issues\n .map(issue => issue.message)\n .join(', ')}`,\n result,\n );\n }\n return result.value;\n}\n\nfunction titleCase(kind: string): string {\n return kind.charAt(0).toUpperCase() + kind.slice(1);\n}\n"],"mappings":";AAEA,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CAGA;CAKA,YAAY,SAAiB,QAAwC;AACnE,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,OAAK,UAAU;GAAC,MAAM;GAAwB;GAAO;;;;;;;;;;;;;;;AAgBzD,SAAgB,cACd,MACA,OACA,WACA,MACS;AACT,KAAI,CAAC,UAEH,QAAO;CAGT,MAAM,SAAS,UAAU,aAAa,SAAS,MAAM;AACrD,KAAI,kBAAkB,QACpB,OAAM,IAAI,MACR,uCAAuC,UAAU,KAAK,CAAC,QAAQ,OAChE;AAEH,KAAI,OAAO,OACT,OAAM,IAAI,qBACR,yBAAyB,KAAK,GAAG,KAAK,IAAI,OAAO,OAC9C,KAAI,UAAS,MAAM,QAAQ,CAC3B,KAAK,KAAK,IACb,OACD;AAEH,QAAO,OAAO;;AAGhB,SAAS,UAAU,MAAsB;AACvC,QAAO,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"database-storage.js","names":["#stmts","#options","#db","#checkpoint","#maybeCheckpoint","#numWrites","#scan","#get","#set","#del"],"sources":["../../../../zqlite/src/database-storage.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {JSONValue} from '../../shared/src/json.ts';\nimport type {Storage} from '../../zql/src/ivm/operator.ts';\nimport type {Stream} from '../../zql/src/ivm/stream.ts';\nimport type {Statement} from './db.ts';\nimport {Database} from './db.ts';\n\nexport interface ClientGroupStorage {\n /** Creates a {@link Storage} instance for a single operator. */\n createStorage(): Storage;\n\n /** Deletes all storage for the client group. */\n destroy(): void;\n}\n\ntype Statements = {\n get: Statement;\n set: Statement;\n del: Statement;\n scan: Statement;\n clear: Statement;\n commit: Statement;\n begin: Statement;\n};\n\n// Exported for testing.\nexport const CREATE_STORAGE_TABLE = `\n CREATE TABLE storage (\n clientGroupID TEXT,\n op NUMBER,\n key TEXT,\n val TEXT,\n PRIMARY KEY(clientGroupID, op, key)\n )\n `;\n\nconst defaultOptions = {\n commitInterval: 5_000,\n compactionThresholdBytes: 50 * 1024 * 1024,\n};\n\nexport class DatabaseStorage {\n static create(\n lc: LogContext,\n path: string,\n options = defaultOptions,\n ): DatabaseStorage {\n // SQLite is used for ephemeral storage (i.e. similar to RAM) that can spill to\n // disk to avoid consuming too much memory. Each worker thread gets its own\n // database (file) and acts as the single reader/writer of the DB, so\n // `locking_mode` is set to `EXCLUSIVE` for performance. Similarly, since\n // durability is not important, `synchronous` is set to `OFF` for performance.\n const db = new Database(lc, path);\n db.unsafeMode(true); // Allows journal_mode = OFF\n db.pragma('locking_mode = EXCLUSIVE');\n db.pragma('foreign_keys = OFF');\n db.pragma('journal_mode = OFF');\n db.pragma('synchronous = OFF');\n db.pragma('auto_vacuum = INCREMENTAL');\n\n db.prepare(CREATE_STORAGE_TABLE).run();\n lc.debug?.(`Created DatabaseStorage backed by ${path}`);\n return new DatabaseStorage(db, options);\n }\n\n readonly #stmts: Statements;\n readonly #options: typeof defaultOptions;\n readonly #db: Database;\n #numWrites = 0;\n\n constructor(db: Database, options = defaultOptions) {\n this.#stmts = {\n get: db.prepare(`\n SELECT val FROM storage WHERE\n clientGroupID = ? AND op = ? AND key = ?\n `),\n set: db.prepare(`\n INSERT INTO storage (clientGroupID, op, key, val)\n VALUES(?, ?, ?, ?)\n ON CONFLICT(clientGroupID, op, key) \n DO \n UPDATE SET val = excluded.val\n `),\n del: db.prepare(`\n DELETE FROM storage WHERE\n clientGroupID = ? AND op = ? AND key = ?\n `),\n scan: db.prepare(`\n SELECT key, val FROM storage WHERE\n clientGroupID = ? AND op = ? AND key >= ?\n `),\n clear: db.prepare(`\n DELETE FROM storage WHERE clientGroupID = ?\n `),\n commit: db.prepare('COMMIT'),\n begin: db.prepare('BEGIN'),\n };\n this.#stmts.begin.run();\n this.#options = options;\n this.#db = db;\n }\n\n close() {\n this.#checkpoint();\n this.#db.close();\n }\n\n #get(\n cgID: string,\n opID: number,\n key: string,\n def?: JSONValue,\n ): JSONValue | undefined {\n this.#maybeCheckpoint();\n const row = this.#stmts.get.get<{val: string}>(cgID, opID, key);\n return row ? JSON.parse(row.val) : def;\n }\n\n #set(cgID: string, opID: number, key: string, val: JSONValue) {\n this.#maybeCheckpoint();\n this.#stmts.set.run(cgID, opID, key, JSON.stringify(val));\n }\n\n #del(cgID: string, opID: number, key: string) {\n this.#maybeCheckpoint();\n this.#stmts.del.run(cgID, opID, key);\n }\n\n /**\n * We don't need to commit every single write to the DB\n * since we're not concerned with durability.\n * Waiting on commits can be expensive, so we commit\n * every `COMMIT_INTERVAL` writes.\n */\n #maybeCheckpoint() {\n if (++this.#numWrites >= this.#options.commitInterval) {\n this.#checkpoint();\n }\n }\n\n #checkpoint() {\n this.#stmts.commit.run();\n this.#stmts.begin.run();\n this.#numWrites = 0;\n }\n\n *#scan(\n cgID: string,\n opID: number,\n opts: {prefix: string} = {prefix: ''},\n ): Stream<[string, JSONValue]> {\n const {prefix} = opts;\n for (const {key, val} of this.#stmts.scan.iterate<{\n key: string;\n val: string;\n }>(cgID, opID, prefix)) {\n if (!key.startsWith(prefix)) {\n return;\n }\n yield [key, JSON.parse(val)];\n }\n }\n\n createClientGroupStorage(cgID: string): ClientGroupStorage {\n const destroy = () => {\n this.#stmts.clear.run(cgID);\n this.#checkpoint();\n this.#db.compact(this.#options.compactionThresholdBytes);\n };\n this.#stmts.clear.run(cgID);\n\n let nextOpID = 1;\n return {\n createStorage: () => {\n const opID = nextOpID++;\n return {\n get: (key, def?) => this.#get(cgID, opID, key, def),\n set: (key, val) => this.#set(cgID, opID, key, val),\n del: key => this.#del(cgID, opID, key),\n scan: opts => this.#scan(cgID, opID, opts),\n };\n },\n\n destroy,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"database-storage.js","names":["#stmts","#options","#db","#checkpoint","#maybeCheckpoint","#numWrites","#scan","#get","#set","#del"],"sources":["../../../../zqlite/src/database-storage.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {JSONValue} from '../../shared/src/json.ts';\nimport type {Storage} from '../../zql/src/ivm/operator.ts';\nimport type {Stream} from '../../zql/src/ivm/stream.ts';\nimport type {Statement} from './db.ts';\nimport {Database} from './db.ts';\n\nexport interface ClientGroupStorage {\n /** Creates a {@link Storage} instance for a single operator. */\n createStorage(): Storage;\n\n /** Deletes all storage for the client group. */\n destroy(): void;\n}\n\ntype Statements = {\n get: Statement;\n set: Statement;\n del: Statement;\n scan: Statement;\n clear: Statement;\n commit: Statement;\n begin: Statement;\n};\n\n// Exported for testing.\nexport const CREATE_STORAGE_TABLE = `\n CREATE TABLE storage (\n clientGroupID TEXT,\n op NUMBER,\n key TEXT,\n val TEXT,\n PRIMARY KEY(clientGroupID, op, key)\n )\n `;\n\nconst defaultOptions = {\n commitInterval: 5_000,\n compactionThresholdBytes: 50 * 1024 * 1024,\n};\n\nexport class DatabaseStorage {\n static create(\n lc: LogContext,\n path: string,\n options = defaultOptions,\n ): DatabaseStorage {\n // SQLite is used for ephemeral storage (i.e. similar to RAM) that can spill to\n // disk to avoid consuming too much memory. Each worker thread gets its own\n // database (file) and acts as the single reader/writer of the DB, so\n // `locking_mode` is set to `EXCLUSIVE` for performance. Similarly, since\n // durability is not important, `synchronous` is set to `OFF` for performance.\n const db = new Database(lc, path);\n db.unsafeMode(true); // Allows journal_mode = OFF\n db.pragma('locking_mode = EXCLUSIVE');\n db.pragma('foreign_keys = OFF');\n db.pragma('journal_mode = OFF');\n db.pragma('synchronous = OFF');\n db.pragma('auto_vacuum = INCREMENTAL');\n\n db.prepare(CREATE_STORAGE_TABLE).run();\n lc.debug?.(`Created DatabaseStorage backed by ${path}`);\n return new DatabaseStorage(db, options);\n }\n\n readonly #stmts: Statements;\n readonly #options: typeof defaultOptions;\n readonly #db: Database;\n #numWrites = 0;\n\n constructor(db: Database, options = defaultOptions) {\n this.#stmts = {\n get: db.prepare(`\n SELECT val FROM storage WHERE\n clientGroupID = ? AND op = ? AND key = ?\n `),\n set: db.prepare(`\n INSERT INTO storage (clientGroupID, op, key, val)\n VALUES(?, ?, ?, ?)\n ON CONFLICT(clientGroupID, op, key) \n DO \n UPDATE SET val = excluded.val\n `),\n del: db.prepare(`\n DELETE FROM storage WHERE\n clientGroupID = ? AND op = ? AND key = ?\n `),\n scan: db.prepare(`\n SELECT key, val FROM storage WHERE\n clientGroupID = ? AND op = ? AND key >= ?\n `),\n clear: db.prepare(`\n DELETE FROM storage WHERE clientGroupID = ?\n `),\n commit: db.prepare('COMMIT'),\n begin: db.prepare('BEGIN'),\n };\n this.#stmts.begin.run();\n this.#options = options;\n this.#db = db;\n }\n\n close() {\n this.#checkpoint();\n this.#db.close();\n }\n\n #get(\n cgID: string,\n opID: number,\n key: string,\n def?: JSONValue,\n ): JSONValue | undefined {\n this.#maybeCheckpoint();\n const row = this.#stmts.get.get<{val: string}>(cgID, opID, key);\n return row ? JSON.parse(row.val) : def;\n }\n\n #set(cgID: string, opID: number, key: string, val: JSONValue) {\n this.#maybeCheckpoint();\n this.#stmts.set.run(cgID, opID, key, JSON.stringify(val));\n }\n\n #del(cgID: string, opID: number, key: string) {\n this.#maybeCheckpoint();\n this.#stmts.del.run(cgID, opID, key);\n }\n\n /**\n * We don't need to commit every single write to the DB\n * since we're not concerned with durability.\n * Waiting on commits can be expensive, so we commit\n * every `COMMIT_INTERVAL` writes.\n */\n #maybeCheckpoint() {\n if (++this.#numWrites >= this.#options.commitInterval) {\n this.#checkpoint();\n }\n }\n\n #checkpoint() {\n this.#stmts.commit.run();\n this.#stmts.begin.run();\n this.#numWrites = 0;\n }\n\n *#scan(\n cgID: string,\n opID: number,\n opts: {prefix: string} = {prefix: ''},\n ): Stream<[string, JSONValue]> {\n const {prefix} = opts;\n for (const {key, val} of this.#stmts.scan.iterate<{\n key: string;\n val: string;\n }>(cgID, opID, prefix)) {\n if (!key.startsWith(prefix)) {\n return;\n }\n yield [key, JSON.parse(val)];\n }\n }\n\n createClientGroupStorage(cgID: string): ClientGroupStorage {\n const destroy = () => {\n this.#stmts.clear.run(cgID);\n this.#checkpoint();\n this.#db.compact(this.#options.compactionThresholdBytes);\n };\n this.#stmts.clear.run(cgID);\n\n let nextOpID = 1;\n return {\n createStorage: () => {\n const opID = nextOpID++;\n return {\n get: (key, def?) => this.#get(cgID, opID, key, def),\n set: (key, val) => this.#set(cgID, opID, key, val),\n del: key => this.#del(cgID, opID, key),\n scan: opts => this.#scan(cgID, opID, opts),\n };\n },\n\n destroy,\n };\n }\n}\n"],"mappings":";;AA0BA,IAAa,uBAAuB;;;;;;;;;AAUpC,IAAM,iBAAiB;CACrB,gBAAgB;CAChB,0BAA0B,KAAK,OAAO;CACvC;AAED,IAAa,kBAAb,MAAa,gBAAgB;CAC3B,OAAO,OACL,IACA,MACA,UAAU,gBACO;EAMjB,MAAM,KAAK,IAAI,SAAS,IAAI,KAAK;AACjC,KAAG,WAAW,KAAK;AACnB,KAAG,OAAO,2BAA2B;AACrC,KAAG,OAAO,qBAAqB;AAC/B,KAAG,OAAO,qBAAqB;AAC/B,KAAG,OAAO,oBAAoB;AAC9B,KAAG,OAAO,4BAA4B;AAEtC,KAAG,QAAQ,qBAAqB,CAAC,KAAK;AACtC,KAAG,QAAQ,qCAAqC,OAAO;AACvD,SAAO,IAAI,gBAAgB,IAAI,QAAQ;;CAGzC;CACA;CACA;CACA,aAAa;CAEb,YAAY,IAAc,UAAU,gBAAgB;AAClD,QAAA,QAAc;GACZ,KAAK,GAAG,QAAQ;;;QAGd;GACF,KAAK,GAAG,QAAQ;;;;;;QAMd;GACF,KAAK,GAAG,QAAQ;;;QAGd;GACF,MAAM,GAAG,QAAQ;;;QAGf;GACF,OAAO,GAAG,QAAQ;;QAEhB;GACF,QAAQ,GAAG,QAAQ,SAAS;GAC5B,OAAO,GAAG,QAAQ,QAAQ;GAC3B;AACD,QAAA,MAAY,MAAM,KAAK;AACvB,QAAA,UAAgB;AAChB,QAAA,KAAW;;CAGb,QAAQ;AACN,QAAA,YAAkB;AAClB,QAAA,GAAS,OAAO;;CAGlB,KACE,MACA,MACA,KACA,KACuB;AACvB,QAAA,iBAAuB;EACvB,MAAM,MAAM,MAAA,MAAY,IAAI,IAAmB,MAAM,MAAM,IAAI;AAC/D,SAAO,MAAM,KAAK,MAAM,IAAI,IAAI,GAAG;;CAGrC,KAAK,MAAc,MAAc,KAAa,KAAgB;AAC5D,QAAA,iBAAuB;AACvB,QAAA,MAAY,IAAI,IAAI,MAAM,MAAM,KAAK,KAAK,UAAU,IAAI,CAAC;;CAG3D,KAAK,MAAc,MAAc,KAAa;AAC5C,QAAA,iBAAuB;AACvB,QAAA,MAAY,IAAI,IAAI,MAAM,MAAM,IAAI;;;;;;;;CAStC,mBAAmB;AACjB,MAAI,EAAE,MAAA,aAAmB,MAAA,QAAc,eACrC,OAAA,YAAkB;;CAItB,cAAc;AACZ,QAAA,MAAY,OAAO,KAAK;AACxB,QAAA,MAAY,MAAM,KAAK;AACvB,QAAA,YAAkB;;CAGpB,EAAA,KACE,MACA,MACA,OAAyB,EAAC,QAAQ,IAAG,EACR;EAC7B,MAAM,EAAC,WAAU;AACjB,OAAK,MAAM,EAAC,KAAK,SAAQ,MAAA,MAAY,KAAK,QAGvC,MAAM,MAAM,OAAO,EAAE;AACtB,OAAI,CAAC,IAAI,WAAW,OAAO,CACzB;AAEF,SAAM,CAAC,KAAK,KAAK,MAAM,IAAI,CAAC;;;CAIhC,yBAAyB,MAAkC;EACzD,MAAM,gBAAgB;AACpB,SAAA,MAAY,MAAM,IAAI,KAAK;AAC3B,SAAA,YAAkB;AAClB,SAAA,GAAS,QAAQ,MAAA,QAAc,yBAAyB;;AAE1D,QAAA,MAAY,MAAM,IAAI,KAAK;EAE3B,IAAI,WAAW;AACf,SAAO;GACL,qBAAqB;IACnB,MAAM,OAAO;AACb,WAAO;KACL,MAAM,KAAK,QAAS,MAAA,IAAU,MAAM,MAAM,KAAK,IAAI;KACnD,MAAM,KAAK,QAAQ,MAAA,IAAU,MAAM,MAAM,KAAK,IAAI;KAClD,MAAK,QAAO,MAAA,IAAU,MAAM,MAAM,IAAI;KACtC,OAAM,SAAQ,MAAA,KAAW,MAAM,MAAM,KAAK;KAC3C;;GAGH;GACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","names":["#db","#threshold","#lc","#pageSize","#run","#bytes","#stmt","#attrs","#it","#start","#sqliteRowTimeSum","#log"],"sources":["../../../../zqlite/src/db.ts"],"sourcesContent":["import {trace, type Attributes} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport SQLite3Database, {\n SqliteError,\n type RunResult,\n type Statement as SQLite3Statement,\n} from '@rocicorp/zero-sqlite3';\nimport {manualSpan} from '../../otel/src/span.ts';\nimport {version} from '../../otel/src/version.ts';\n\nconst tracer = trace.getTracer('view-syncer', version);\n\n// https://www.sqlite.org/pragma.html#pragma_auto_vacuum\nconst AUTO_VACUUM_INCREMENTAL = 2;\n\nconst MB = 1024 * 1024;\n\nfunction mb(bytes: number): string {\n return (bytes / MB).toFixed(2);\n}\n\nexport class Database implements Disposable {\n readonly #db: SQLite3Database.Database;\n readonly #threshold: number;\n readonly #lc: LogContext;\n readonly #pageSize: number;\n\n constructor(\n lc: LogContext,\n path: string,\n options?: SQLite3Database.Options,\n slowQueryThreshold = 100,\n ) {\n try {\n this.#lc = lc.withContext('class', 'Database').withContext('path', path);\n this.#db = new SQLite3Database(path, options);\n this.#threshold = slowQueryThreshold;\n\n const [{page_size: pageSize}] = this.pragma<{page_size: number}>(\n 'page_size',\n );\n this.#pageSize = pageSize;\n } catch (cause) {\n throw new DatabaseInitError(String(cause), {cause});\n }\n }\n\n prepare(sql: string): Statement {\n return this.#run(\n 'prepare',\n sql,\n () =>\n new Statement(\n this.#lc.withContext('sql', sql),\n {class: 'Statement', sql},\n this.#db.prepare(sql),\n this.#threshold,\n ),\n );\n }\n\n exec(sql: string): void {\n this.#run('exec', sql, () => this.#db.exec(sql));\n }\n\n pragma<T = unknown>(sql: string): T[] {\n return this.#run<T[]>('pragma', sql, () => this.#db.pragma(sql) as T[]);\n }\n\n #bytes(pages: number) {\n return pages * this.#pageSize;\n }\n\n compact(freeableBytesThreshold: number) {\n const [{freelist_count: freelistCount}] = this.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const freeable = this.#bytes(freelistCount);\n if (freeable < freeableBytesThreshold) {\n this.#lc.debug?.(\n `Not compacting ${this.#db.name}: ${mb(freeable)} freeable MB`,\n );\n return;\n }\n const [{auto_vacuum: autoVacuumMode}] = this.pragma<{auto_vacuum: number}>(\n 'auto_vacuum',\n );\n if (autoVacuumMode !== AUTO_VACUUM_INCREMENTAL) {\n this.#lc.warn?.(\n `Cannot compact ${mb(freeable)} MB of ` +\n `${this.#db.name} because AUTO_VACUUM mode is ${autoVacuumMode}.`,\n );\n return;\n }\n const start = Date.now();\n const [{page_count: pageCountBefore}] = this.pragma<{page_count: number}>(\n 'page_count',\n );\n\n this.pragma('incremental_vacuum');\n\n const [{page_count: pageCountAfter}] = this.pragma<{page_count: number}>(\n 'page_count',\n );\n\n this.#lc.info?.(\n `Compacted ${this.#db.name} from ` +\n `${mb(this.#bytes(pageCountBefore))} MB to ` +\n `${mb(this.#bytes(pageCountAfter))} MB ` +\n `(${Date.now() - start} ms)`,\n );\n }\n\n unsafeMode(unsafe: boolean) {\n this.#db.unsafeMode(unsafe);\n }\n\n #run<T>(method: string, sql: string, fn: () => T): T {\n const start = performance.now();\n try {\n return fn();\n } catch (e) {\n if (e instanceof SqliteError) {\n e.message += `: ${sql}`;\n }\n throw e;\n } finally {\n logIfSlow(\n this.#lc.withContext('method', method),\n performance.now() - start,\n {method},\n this.#threshold,\n );\n }\n }\n\n close(): void {\n const start = Date.now();\n if (!this.#db.readonly) {\n try {\n this.#db.pragma('optimize');\n const elapsed = Date.now() - start;\n if (elapsed > 2) {\n this.#lc.debug?.(`PRAGMA optimized (${elapsed} ms)`);\n }\n } catch (e) {\n this.#lc.warn?.('error running PRAGMA optimize', e);\n }\n }\n this.#db.close();\n }\n\n transaction<T>(fn: () => T): T {\n return this.#db.transaction(fn)();\n }\n\n get name() {\n return this.#db.name;\n }\n\n get inTransaction() {\n return this.#db.inTransaction;\n }\n\n [Symbol.dispose](): void {\n this.close();\n }\n}\n\nexport class Statement {\n readonly #stmt: SQLite3Statement;\n readonly #lc: LogContext;\n readonly #threshold: number;\n readonly #attrs: Attributes;\n readonly scanStatus: SQLite3Statement['scanStatusV2'];\n readonly scanStatusReset: SQLite3Statement['scanStatusReset'];\n\n constructor(\n lc: LogContext,\n attrs: Attributes,\n stmt: SQLite3Statement,\n threshold: number,\n ) {\n this.#lc = lc.withContext('class', 'Statement');\n this.#attrs = attrs;\n this.#stmt = stmt;\n this.#threshold = threshold;\n this.scanStatus = this.#stmt.scanStatusV2.bind(this.#stmt);\n this.scanStatusReset = this.#stmt.scanStatusReset.bind(this.#stmt);\n }\n\n safeIntegers(useBigInt: boolean): this {\n this.#stmt.safeIntegers(useBigInt);\n return this;\n }\n\n run(...params: unknown[]): RunResult {\n const start = performance.now();\n const ret = this.#stmt.run(...params);\n logIfSlow(\n this.#lc.withContext('method', 'run'),\n performance.now() - start,\n {...this.#attrs, method: 'run'},\n this.#threshold,\n );\n return ret;\n }\n\n get<T>(...params: unknown[]): T {\n const start = performance.now();\n const ret = this.#stmt.get(...params);\n logIfSlow(\n this.#lc.withContext('method', 'get'),\n performance.now() - start,\n {...this.#attrs, method: 'get'},\n this.#threshold,\n );\n return ret as T;\n }\n\n all<T>(...params: unknown[]): T[] {\n const start = performance.now();\n const ret = this.#stmt.all(...params);\n logIfSlow(\n this.#lc.withContext('method', 'all'),\n performance.now() - start,\n {...this.#attrs, method: 'all'},\n this.#threshold,\n );\n return ret as T[];\n }\n\n iterate<T>(...params: unknown[]): IterableIterator<T> {\n return new LoggingIterableIterator(\n this.#lc.withContext('method', 'iterate'),\n this.#attrs,\n this.#stmt.iterate(...params),\n this.#threshold,\n ) as IterableIterator<T>;\n }\n}\n\nclass LoggingIterableIterator<T> implements IterableIterator<T> {\n readonly #lc: LogContext;\n readonly #it: IterableIterator<T>;\n readonly #threshold: number;\n readonly #attrs: Attributes;\n #start: number;\n #sqliteRowTimeSum: number;\n\n constructor(\n lc: LogContext,\n attrs: Attributes,\n it: IterableIterator<T>,\n slowQueryThreshold: number,\n ) {\n this.#lc = lc;\n this.#attrs = attrs;\n this.#it = it;\n this.#start = performance.now();\n this.#threshold = slowQueryThreshold;\n this.#sqliteRowTimeSum = 0;\n }\n\n next(): IteratorResult<T> {\n const start = performance.now();\n const ret = this.#it.next();\n const elapsed = performance.now() - start;\n this.#sqliteRowTimeSum += elapsed;\n if (ret.done) {\n this.#log();\n }\n return ret;\n }\n\n #log() {\n logIfSlow(\n this.#lc.withContext('type', 'total'),\n performance.now() - this.#start,\n {...this.#attrs, type: 'total', method: 'iterate'},\n this.#threshold,\n );\n logIfSlow(\n this.#lc.withContext('type', 'sqlite'),\n this.#sqliteRowTimeSum,\n {...this.#attrs, type: 'sqlite', method: 'iterate'},\n this.#threshold,\n );\n }\n\n [Symbol.iterator](): IterableIterator<T> {\n return this;\n }\n\n return(): IteratorResult<T> {\n this.#log();\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return this.#it.return?.() as any;\n }\n\n throw(e: unknown): IteratorResult<T> {\n this.#log();\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return this.#it.throw?.(e) as any;\n }\n}\n\nfunction logIfSlow(\n lc: LogContext,\n elapsed: number,\n attrs: Attributes,\n threshold: number,\n): void {\n if (elapsed >= threshold) {\n for (const [key, value] of Object.entries(attrs)) {\n lc = lc.withContext(key, value);\n }\n lc.warn?.('Slow SQLite query', elapsed);\n manualSpan(tracer, 'db.slow-query', elapsed, attrs);\n }\n}\n\n/**\n * An error indicating that the Database failed to open. This essentially\n * wraps the TypeError thrown by the better-sqlite3 package with something\n * more specific.\n */\nexport class DatabaseInitError extends Error {}\n"],"mappings":"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"}
1
+ {"version":3,"file":"db.js","names":["#db","#threshold","#lc","#pageSize","#run","#bytes","#stmt","#attrs","#it","#start","#sqliteRowTimeSum","#log"],"sources":["../../../../zqlite/src/db.ts"],"sourcesContent":["import {trace, type Attributes} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport SQLite3Database, {\n SqliteError,\n type RunResult,\n type Statement as SQLite3Statement,\n} from '@rocicorp/zero-sqlite3';\nimport {manualSpan} from '../../otel/src/span.ts';\nimport {version} from '../../otel/src/version.ts';\n\nconst tracer = trace.getTracer('view-syncer', version);\n\n// https://www.sqlite.org/pragma.html#pragma_auto_vacuum\nconst AUTO_VACUUM_INCREMENTAL = 2;\n\nconst MB = 1024 * 1024;\n\nfunction mb(bytes: number): string {\n return (bytes / MB).toFixed(2);\n}\n\nexport class Database implements Disposable {\n readonly #db: SQLite3Database.Database;\n readonly #threshold: number;\n readonly #lc: LogContext;\n readonly #pageSize: number;\n\n constructor(\n lc: LogContext,\n path: string,\n options?: SQLite3Database.Options,\n slowQueryThreshold = 100,\n ) {\n try {\n this.#lc = lc.withContext('class', 'Database').withContext('path', path);\n this.#db = new SQLite3Database(path, options);\n this.#threshold = slowQueryThreshold;\n\n const [{page_size: pageSize}] = this.pragma<{page_size: number}>(\n 'page_size',\n );\n this.#pageSize = pageSize;\n } catch (cause) {\n throw new DatabaseInitError(String(cause), {cause});\n }\n }\n\n prepare(sql: string): Statement {\n return this.#run(\n 'prepare',\n sql,\n () =>\n new Statement(\n this.#lc.withContext('sql', sql),\n {class: 'Statement', sql},\n this.#db.prepare(sql),\n this.#threshold,\n ),\n );\n }\n\n exec(sql: string): void {\n this.#run('exec', sql, () => this.#db.exec(sql));\n }\n\n pragma<T = unknown>(sql: string): T[] {\n return this.#run<T[]>('pragma', sql, () => this.#db.pragma(sql) as T[]);\n }\n\n #bytes(pages: number) {\n return pages * this.#pageSize;\n }\n\n compact(freeableBytesThreshold: number) {\n const [{freelist_count: freelistCount}] = this.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const freeable = this.#bytes(freelistCount);\n if (freeable < freeableBytesThreshold) {\n this.#lc.debug?.(\n `Not compacting ${this.#db.name}: ${mb(freeable)} freeable MB`,\n );\n return;\n }\n const [{auto_vacuum: autoVacuumMode}] = this.pragma<{auto_vacuum: number}>(\n 'auto_vacuum',\n );\n if (autoVacuumMode !== AUTO_VACUUM_INCREMENTAL) {\n this.#lc.warn?.(\n `Cannot compact ${mb(freeable)} MB of ` +\n `${this.#db.name} because AUTO_VACUUM mode is ${autoVacuumMode}.`,\n );\n return;\n }\n const start = Date.now();\n const [{page_count: pageCountBefore}] = this.pragma<{page_count: number}>(\n 'page_count',\n );\n\n this.pragma('incremental_vacuum');\n\n const [{page_count: pageCountAfter}] = this.pragma<{page_count: number}>(\n 'page_count',\n );\n\n this.#lc.info?.(\n `Compacted ${this.#db.name} from ` +\n `${mb(this.#bytes(pageCountBefore))} MB to ` +\n `${mb(this.#bytes(pageCountAfter))} MB ` +\n `(${Date.now() - start} ms)`,\n );\n }\n\n unsafeMode(unsafe: boolean) {\n this.#db.unsafeMode(unsafe);\n }\n\n #run<T>(method: string, sql: string, fn: () => T): T {\n const start = performance.now();\n try {\n return fn();\n } catch (e) {\n if (e instanceof SqliteError) {\n e.message += `: ${sql}`;\n }\n throw e;\n } finally {\n logIfSlow(\n this.#lc.withContext('method', method),\n performance.now() - start,\n {method},\n this.#threshold,\n );\n }\n }\n\n close(): void {\n const start = Date.now();\n if (!this.#db.readonly) {\n try {\n this.#db.pragma('optimize');\n const elapsed = Date.now() - start;\n if (elapsed > 2) {\n this.#lc.debug?.(`PRAGMA optimized (${elapsed} ms)`);\n }\n } catch (e) {\n this.#lc.warn?.('error running PRAGMA optimize', e);\n }\n }\n this.#db.close();\n }\n\n transaction<T>(fn: () => T): T {\n return this.#db.transaction(fn)();\n }\n\n get name() {\n return this.#db.name;\n }\n\n get inTransaction() {\n return this.#db.inTransaction;\n }\n\n [Symbol.dispose](): void {\n this.close();\n }\n}\n\nexport class Statement {\n readonly #stmt: SQLite3Statement;\n readonly #lc: LogContext;\n readonly #threshold: number;\n readonly #attrs: Attributes;\n readonly scanStatus: SQLite3Statement['scanStatusV2'];\n readonly scanStatusReset: SQLite3Statement['scanStatusReset'];\n\n constructor(\n lc: LogContext,\n attrs: Attributes,\n stmt: SQLite3Statement,\n threshold: number,\n ) {\n this.#lc = lc.withContext('class', 'Statement');\n this.#attrs = attrs;\n this.#stmt = stmt;\n this.#threshold = threshold;\n this.scanStatus = this.#stmt.scanStatusV2.bind(this.#stmt);\n this.scanStatusReset = this.#stmt.scanStatusReset.bind(this.#stmt);\n }\n\n safeIntegers(useBigInt: boolean): this {\n this.#stmt.safeIntegers(useBigInt);\n return this;\n }\n\n run(...params: unknown[]): RunResult {\n const start = performance.now();\n const ret = this.#stmt.run(...params);\n logIfSlow(\n this.#lc.withContext('method', 'run'),\n performance.now() - start,\n {...this.#attrs, method: 'run'},\n this.#threshold,\n );\n return ret;\n }\n\n get<T>(...params: unknown[]): T {\n const start = performance.now();\n const ret = this.#stmt.get(...params);\n logIfSlow(\n this.#lc.withContext('method', 'get'),\n performance.now() - start,\n {...this.#attrs, method: 'get'},\n this.#threshold,\n );\n return ret as T;\n }\n\n all<T>(...params: unknown[]): T[] {\n const start = performance.now();\n const ret = this.#stmt.all(...params);\n logIfSlow(\n this.#lc.withContext('method', 'all'),\n performance.now() - start,\n {...this.#attrs, method: 'all'},\n this.#threshold,\n );\n return ret as T[];\n }\n\n iterate<T>(...params: unknown[]): IterableIterator<T> {\n return new LoggingIterableIterator(\n this.#lc.withContext('method', 'iterate'),\n this.#attrs,\n this.#stmt.iterate(...params),\n this.#threshold,\n ) as IterableIterator<T>;\n }\n}\n\nclass LoggingIterableIterator<T> implements IterableIterator<T> {\n readonly #lc: LogContext;\n readonly #it: IterableIterator<T>;\n readonly #threshold: number;\n readonly #attrs: Attributes;\n #start: number;\n #sqliteRowTimeSum: number;\n\n constructor(\n lc: LogContext,\n attrs: Attributes,\n it: IterableIterator<T>,\n slowQueryThreshold: number,\n ) {\n this.#lc = lc;\n this.#attrs = attrs;\n this.#it = it;\n this.#start = performance.now();\n this.#threshold = slowQueryThreshold;\n this.#sqliteRowTimeSum = 0;\n }\n\n next(): IteratorResult<T> {\n const start = performance.now();\n const ret = this.#it.next();\n const elapsed = performance.now() - start;\n this.#sqliteRowTimeSum += elapsed;\n if (ret.done) {\n this.#log();\n }\n return ret;\n }\n\n #log() {\n logIfSlow(\n this.#lc.withContext('type', 'total'),\n performance.now() - this.#start,\n {...this.#attrs, type: 'total', method: 'iterate'},\n this.#threshold,\n );\n logIfSlow(\n this.#lc.withContext('type', 'sqlite'),\n this.#sqliteRowTimeSum,\n {...this.#attrs, type: 'sqlite', method: 'iterate'},\n this.#threshold,\n );\n }\n\n [Symbol.iterator](): IterableIterator<T> {\n return this;\n }\n\n return(): IteratorResult<T> {\n this.#log();\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return this.#it.return?.() as any;\n }\n\n throw(e: unknown): IteratorResult<T> {\n this.#log();\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return this.#it.throw?.(e) as any;\n }\n}\n\nfunction logIfSlow(\n lc: LogContext,\n elapsed: number,\n attrs: Attributes,\n threshold: number,\n): void {\n if (elapsed >= threshold) {\n for (const [key, value] of Object.entries(attrs)) {\n lc = lc.withContext(key, value);\n }\n lc.warn?.('Slow SQLite query', elapsed);\n manualSpan(tracer, 'db.slow-query', elapsed, attrs);\n }\n}\n\n/**\n * An error indicating that the Database failed to open. This essentially\n * wraps the TypeError thrown by the better-sqlite3 package with something\n * more specific.\n */\nexport class DatabaseInitError extends Error {}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"explain-queries.js","names":[],"sources":["../../../../zqlite/src/explain-queries.ts"],"sourcesContent":["import type {RowCountsBySource} from '../../zero-protocol/src/analyze-query-result.ts';\nimport type {Database} from './db.ts';\n\nexport function explainQueries(counts: RowCountsBySource, db: Database) {\n const plans: Record<string, string[]> = {};\n for (const querySet of Object.values(counts)) {\n const queries = Object.keys(querySet);\n for (const query of queries) {\n const plan = db\n // we should be more intelligent about value replacement.\n // Different values result in different plans. E.g., picking a value at the start\n // of an index will result in `scan` vs `search`. The scan is fine in that case.\n .prepare(`EXPLAIN QUERY PLAN ${query.replaceAll('?', \"'sdfse'\")}`)\n .all<{detail: string}>()\n .map(r => r.detail);\n plans[query] = plan;\n }\n }\n\n return plans;\n}\n"],"mappings":";AAGA,SAAgB,eAAe,QAA2B,IAAc;CACtE,MAAM,QAAkC,CAAC;CACzC,KAAK,MAAM,YAAY,OAAO,OAAO,MAAM,GAAG;EAC5C,MAAM,UAAU,OAAO,KAAK,QAAQ;EACpC,KAAK,MAAM,SAAS,SAQlB,MAAM,SAPO,GAIV,QAAQ,sBAAsB,MAAM,WAAW,KAAK,SAAS,GAAG,EAChE,IAAsB,EACtB,KAAI,MAAK,EAAE,MACC;CAEnB;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"explain-queries.js","names":[],"sources":["../../../../zqlite/src/explain-queries.ts"],"sourcesContent":["import type {RowCountsBySource} from '../../zero-protocol/src/analyze-query-result.ts';\nimport type {Database} from './db.ts';\n\nexport function explainQueries(counts: RowCountsBySource, db: Database) {\n const plans: Record<string, string[]> = {};\n for (const querySet of Object.values(counts)) {\n const queries = Object.keys(querySet);\n for (const query of queries) {\n const plan = db\n // we should be more intelligent about value replacement.\n // Different values result in different plans. E.g., picking a value at the start\n // of an index will result in `scan` vs `search`. The scan is fine in that case.\n .prepare(`EXPLAIN QUERY PLAN ${query.replaceAll('?', \"'sdfse'\")}`)\n .all<{detail: string}>()\n .map(r => r.detail);\n plans[query] = plan;\n }\n }\n\n return plans;\n}\n"],"mappings":";AAGA,SAAgB,eAAe,QAA2B,IAAc;CACtE,MAAM,QAAkC,EAAE;AAC1C,MAAK,MAAM,YAAY,OAAO,OAAO,OAAO,EAAE;EAC5C,MAAM,UAAU,OAAO,KAAK,SAAS;AACrC,OAAK,MAAM,SAAS,QAQlB,OAAM,SAPO,GAIV,QAAQ,sBAAsB,MAAM,WAAW,KAAK,UAAU,GAAG,CACjE,KAAuB,CACvB,KAAI,MAAK,EAAE,OAAO;;AAKzB,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"sql-inline.js","names":[],"sources":["../../../../../zqlite/src/internal/sql-inline.ts"],"sourcesContent":["import {escapeSQLiteIdentifier} from '@databases/escape-identifier';\nimport type {FormatConfig} from '@databases/sql';\nimport type {SQLQuery} from '@databases/sql';\n\n/**\n * Escapes a SQLite string value by doubling single quotes.\n * SQLite uses single quotes for string literals and doubles them for escaping.\n */\nfunction escapeSQLiteString(str: string): string {\n return `'${str.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Formats a value for inline inclusion in SQL (not as a placeholder).\n *\n * WARNING: This should ONLY be used for cost estimation in the query planner,\n * not for user-facing queries. All production queries must use parameterized\n * statements to prevent SQL injection.\n *\n * @param value The value to inline into SQL\n * @returns SQL literal representation of the value\n */\nfunction inlineValue(value: unknown): string {\n if (value === null) {\n return 'NULL';\n }\n if (typeof value === 'string') {\n return escapeSQLiteString(value);\n }\n if (typeof value === 'number') {\n return String(value);\n }\n if (typeof value === 'boolean') {\n // SQLite uses 1 and 0 for booleans\n return value ? '1' : '0';\n }\n if (Array.isArray(value)) {\n // For arrays, use JSON representation (same as query-builder.ts does for IN clauses)\n return escapeSQLiteString(JSON.stringify(value));\n }\n // For objects/other JSON types\n return escapeSQLiteString(JSON.stringify(value));\n}\n\n/**\n * Format configuration that inlines values directly into SQL instead of using placeholders.\n *\n * This is used ONLY for cost estimation in the SQLite cost model, where we want SQLite's\n * query planner to see actual values to make better decisions about index usage and query plans.\n *\n * Production code must use the standard parameterized format from sql.ts.\n */\nconst sqliteInlineFormat: FormatConfig = {\n escapeIdentifier: str => escapeSQLiteIdentifier(str),\n formatValue: value =>\n // undefined is our signal to use a placeholder\n // IMPORTANT. Changing this will break the planner as it will assume `NULL`\n // for constraints!\n value === undefined\n ? {\n placeholder: '?',\n value,\n }\n : {\n placeholder: inlineValue(value),\n value: undefined, // No binding needed since value is inlined\n },\n};\n\n/**\n * Compiles a SQL query with values inlined directly into the SQL string.\n *\n * WARNING: This should ONLY be used for cost estimation in the query planner.\n * Never use this for user-facing queries - always use the standard `compile()`\n * function from sql.ts which uses parameterized queries.\n *\n * @param sql The SQL query to compile\n * @returns SQL string with values inlined\n */\nexport function compileInline(sql: SQLQuery): string {\n return sql.format(sqliteInlineFormat).text;\n}\n"],"mappings":";;;;;;AAQA,SAAS,mBAAmB,KAAqB;CAC/C,OAAO,IAAI,IAAI,QAAQ,MAAM,IAAI,EAAE;AACrC;;;;;;;;;;;AAYA,SAAS,YAAY,OAAwB;CAC3C,IAAI,UAAU,MACZ,OAAO;CAET,IAAI,OAAO,UAAU,UACnB,OAAO,mBAAmB,KAAK;CAEjC,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,KAAK;CAErB,IAAI,OAAO,UAAU,WAEnB,OAAO,QAAQ,MAAM;CAEvB,IAAI,MAAM,QAAQ,KAAK,GAErB,OAAO,mBAAmB,KAAK,UAAU,KAAK,CAAC;CAGjD,OAAO,mBAAmB,KAAK,UAAU,KAAK,CAAC;AACjD;;;;;;;;;AAUA,IAAM,qBAAmC;CACvC,mBAAkB,QAAO,uBAAuB,GAAG;CACnD,cAAa,UAIX,UAAU,KAAA,IACN;EACE,aAAa;EACb;CACF,IACA;EACE,aAAa,YAAY,KAAK;EAC9B,OAAO,KAAA;CACT;AACR;;;;;;;;;;;AAYA,SAAgB,cAAc,KAAuB;CACnD,OAAO,IAAI,OAAO,kBAAkB,EAAE;AACxC"}
1
+ {"version":3,"file":"sql-inline.js","names":[],"sources":["../../../../../zqlite/src/internal/sql-inline.ts"],"sourcesContent":["import {escapeSQLiteIdentifier} from '@databases/escape-identifier';\nimport type {FormatConfig} from '@databases/sql';\nimport type {SQLQuery} from '@databases/sql';\n\n/**\n * Escapes a SQLite string value by doubling single quotes.\n * SQLite uses single quotes for string literals and doubles them for escaping.\n */\nfunction escapeSQLiteString(str: string): string {\n return `'${str.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Formats a value for inline inclusion in SQL (not as a placeholder).\n *\n * WARNING: This should ONLY be used for cost estimation in the query planner,\n * not for user-facing queries. All production queries must use parameterized\n * statements to prevent SQL injection.\n *\n * @param value The value to inline into SQL\n * @returns SQL literal representation of the value\n */\nfunction inlineValue(value: unknown): string {\n if (value === null) {\n return 'NULL';\n }\n if (typeof value === 'string') {\n return escapeSQLiteString(value);\n }\n if (typeof value === 'number') {\n return String(value);\n }\n if (typeof value === 'boolean') {\n // SQLite uses 1 and 0 for booleans\n return value ? '1' : '0';\n }\n if (Array.isArray(value)) {\n // For arrays, use JSON representation (same as query-builder.ts does for IN clauses)\n return escapeSQLiteString(JSON.stringify(value));\n }\n // For objects/other JSON types\n return escapeSQLiteString(JSON.stringify(value));\n}\n\n/**\n * Format configuration that inlines values directly into SQL instead of using placeholders.\n *\n * This is used ONLY for cost estimation in the SQLite cost model, where we want SQLite's\n * query planner to see actual values to make better decisions about index usage and query plans.\n *\n * Production code must use the standard parameterized format from sql.ts.\n */\nconst sqliteInlineFormat: FormatConfig = {\n escapeIdentifier: str => escapeSQLiteIdentifier(str),\n formatValue: value =>\n // undefined is our signal to use a placeholder\n // IMPORTANT. Changing this will break the planner as it will assume `NULL`\n // for constraints!\n value === undefined\n ? {\n placeholder: '?',\n value,\n }\n : {\n placeholder: inlineValue(value),\n value: undefined, // No binding needed since value is inlined\n },\n};\n\n/**\n * Compiles a SQL query with values inlined directly into the SQL string.\n *\n * WARNING: This should ONLY be used for cost estimation in the query planner.\n * Never use this for user-facing queries - always use the standard `compile()`\n * function from sql.ts which uses parameterized queries.\n *\n * @param sql The SQL query to compile\n * @returns SQL string with values inlined\n */\nexport function compileInline(sql: SQLQuery): string {\n return sql.format(sqliteInlineFormat).text;\n}\n"],"mappings":";;;;;;AAQA,SAAS,mBAAmB,KAAqB;AAC/C,QAAO,IAAI,IAAI,QAAQ,MAAM,KAAK,CAAC;;;;;;;;;;;;AAarC,SAAS,YAAY,OAAwB;AAC3C,KAAI,UAAU,KACZ,QAAO;AAET,KAAI,OAAO,UAAU,SACnB,QAAO,mBAAmB,MAAM;AAElC,KAAI,OAAO,UAAU,SACnB,QAAO,OAAO,MAAM;AAEtB,KAAI,OAAO,UAAU,UAEnB,QAAO,QAAQ,MAAM;AAEvB,KAAI,MAAM,QAAQ,MAAM,CAEtB,QAAO,mBAAmB,KAAK,UAAU,MAAM,CAAC;AAGlD,QAAO,mBAAmB,KAAK,UAAU,MAAM,CAAC;;;;;;;;;;AAWlD,IAAM,qBAAmC;CACvC,mBAAkB,QAAO,uBAAuB,IAAI;CACpD,cAAa,UAIX,UAAU,KAAA,IACN;EACE,aAAa;EACb;EACD,GACD;EACE,aAAa,YAAY,MAAM;EAC/B,OAAO,KAAA;EACR;CACR;;;;;;;;;;;AAYD,SAAgB,cAAc,KAAuB;AACnD,QAAO,IAAI,OAAO,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sql.js","names":[],"sources":["../../../../../zqlite/src/internal/sql.ts"],"sourcesContent":["import {escapeSQLiteIdentifier} from '@databases/escape-identifier';\nimport type {FormatConfig, SQLQuery} from '@databases/sql';\nimport sql from '@databases/sql';\n\nconst sqliteFormat: FormatConfig = {\n escapeIdentifier: str => escapeSQLiteIdentifier(str),\n formatValue: value => ({placeholder: '?', value}),\n};\n\nexport function compile(sql: SQLQuery): string {\n return sql.format(sqliteFormat).text;\n}\n\nexport function format(sql: SQLQuery) {\n return sql.format(sqliteFormat);\n}\n\nexport {sql};\n"],"mappings":";;;AAIA,IAAM,eAA6B;CACjC,mBAAkB,QAAO,uBAAuB,GAAG;CACnD,cAAa,WAAU;EAAC,aAAa;EAAK;CAAK;AACjD;AAEA,SAAgB,QAAQ,KAAuB;CAC7C,OAAO,IAAI,OAAO,YAAY,EAAE;AAClC;AAEA,SAAgB,OAAO,KAAe;CACpC,OAAO,IAAI,OAAO,YAAY;AAChC"}
1
+ {"version":3,"file":"sql.js","names":[],"sources":["../../../../../zqlite/src/internal/sql.ts"],"sourcesContent":["import {escapeSQLiteIdentifier} from '@databases/escape-identifier';\nimport type {FormatConfig, SQLQuery} from '@databases/sql';\nimport sql from '@databases/sql';\n\nconst sqliteFormat: FormatConfig = {\n escapeIdentifier: str => escapeSQLiteIdentifier(str),\n formatValue: value => ({placeholder: '?', value}),\n};\n\nexport function compile(sql: SQLQuery): string {\n return sql.format(sqliteFormat).text;\n}\n\nexport function format(sql: SQLQuery) {\n return sql.format(sqliteFormat);\n}\n\nexport {sql};\n"],"mappings":";;;AAIA,IAAM,eAA6B;CACjC,mBAAkB,QAAO,uBAAuB,IAAI;CACpD,cAAa,WAAU;EAAC,aAAa;EAAK;EAAM;CACjD;AAED,SAAgB,QAAQ,KAAuB;AAC7C,QAAO,IAAI,OAAO,aAAa,CAAC;;AAGlC,SAAgB,OAAO,KAAe;AACpC,QAAO,IAAI,OAAO,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"file":"statement-cache.js","names":["#db","#size","#cache"],"sources":["../../../../../zqlite/src/internal/statement-cache.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {Database, Statement} from '../db.ts';\n\nexport type CachedStatementMap = Map<string, Statement[]>;\nexport type CachedStatement = {\n sql: string;\n statement: Statement;\n};\n/**\n * SQLite statement preparation isn't cheap as it involves evaluating possible\n * query plans and picking the best one (in addition to parsing the SQL).\n *\n * This statement cache prevents the need to re-prepare the same statement\n * multiple times.\n *\n * One extra wrinkle is that a single statement cannot be used by multiple\n * callers at the same time. As in, we can't `iterate` the same statement\n * many times concurrently.\n *\n * Given that, statements are removed from the cache while in use.\n * - `get` removes the statement from the cache\n * - `return` adds it back.\n *\n * If a request for the same sql is made while a\n * statement is gotten, a new statement will be prepared.\n * Both statements can be returned to the cache even though they both\n * serve the same SQL. Having both copies returned to the cache allows\n * the cache to serve multiple callers concurrently in the future.\n *\n * It is not an error to fail to call `return` on a statement.\n * Failing to call return will only prevent the statement from being reused\n * by other callers. It will not cause a resource leak.\n */\nexport class StatementCache {\n #cache: CachedStatementMap = new Map<string, Statement[]>();\n readonly #db: Database;\n #size: number = 0;\n\n /**\n * The db connection used to prepare the statement.\n * It is an error to use a statement prepared on one connection with another connection.\n * @param db\n */\n constructor(db: Database) {\n this.#db = db;\n }\n\n // the number of statements in the cache\n get size() {\n return this.#size;\n }\n\n drop(n: number) {\n assert(n >= 0, 'Cannot drop a negative number of items');\n assert(n <= this.#size, 'Cannot drop more items than are in the cache');\n\n let remaining = n;\n for (const [sql, statements] of this.#cache.entries()) {\n if (remaining >= statements.length) {\n this.#cache.delete(sql);\n remaining -= statements.length;\n this.#size -= statements.length;\n } else {\n statements.splice(0, remaining);\n this.#size -= remaining;\n break;\n }\n }\n }\n\n /**\n * Prepares a statement for the given sql unless one is already cached.\n * If one is cached, it is removed from the cache and returned.\n *\n * Since `get` removes the item from the cache it is not an error to fail to call\n * `return`. The gotten statement will be correctly garbage collected.\n *\n * When a gotten statement is not returned, future calls to\n * `get` with the same `sql` will prepare a new statement.\n *\n * @param sql\n * @returns\n */\n get(sql: string): CachedStatement {\n sql = normalizeWhitespace(sql);\n const statements = this.#cache.get(sql);\n if (statements && statements.length > 0) {\n const statement = statements.pop()!;\n this.#size--;\n if (statements.length === 0) {\n this.#cache.delete(sql);\n }\n return {sql, statement};\n }\n const statement = this.#db.prepare(sql);\n return {sql, statement};\n }\n\n /**\n * Handles `get` and `return` for the caller by invoking them before\n * and after the callback.\n */\n use<T>(sql: string, cb: (statement: CachedStatement) => T) {\n const statement = this.get(sql);\n try {\n return cb(statement);\n } finally {\n this.return(statement);\n }\n }\n\n /**\n * Add a statement back to the cache so someone else can use it later.\n * @param statement\n */\n return(statement: CachedStatement) {\n const {sql} = statement;\n if (!this.#cache.has(sql)) {\n this.#cache.set(sql, []);\n }\n const statements = this.#cache.get(sql);\n if (statements) {\n statements.push(statement.statement);\n this.#size++;\n }\n }\n}\n\nfunction normalizeWhitespace(sql: string) {\n return sql.replaceAll(/\\s+/g, ' ');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,iBAAb,MAA4B;CAC1B,yBAA6B,IAAI,IAAyB;CAC1D;CACA,QAAgB;;;;;;CAOhB,YAAY,IAAc;EACxB,KAAKA,MAAM;CACb;CAGA,IAAI,OAAO;EACT,OAAO,KAAKC;CACd;CAEA,KAAK,GAAW;EACd,OAAO,KAAK,GAAG,wCAAwC;EACvD,OAAO,KAAK,KAAKA,OAAO,8CAA8C;EAEtE,IAAI,YAAY;EAChB,KAAK,MAAM,CAAC,KAAK,eAAe,KAAKC,OAAO,QAAQ,GAClD,IAAI,aAAa,WAAW,QAAQ;GAClC,KAAKA,OAAO,OAAO,GAAG;GACtB,aAAa,WAAW;GACxB,KAAKD,SAAS,WAAW;EAC3B,OAAO;GACL,WAAW,OAAO,GAAG,SAAS;GAC9B,KAAKA,SAAS;GACd;EACF;CAEJ;;;;;;;;;;;;;;CAeA,IAAI,KAA8B;EAChC,MAAM,oBAAoB,GAAG;EAC7B,MAAM,aAAa,KAAKC,OAAO,IAAI,GAAG;EACtC,IAAI,cAAc,WAAW,SAAS,GAAG;GACvC,MAAM,YAAY,WAAW,IAAI;GACjC,KAAKD;GACL,IAAI,WAAW,WAAW,GACxB,KAAKC,OAAO,OAAO,GAAG;GAExB,OAAO;IAAC;IAAK;GAAS;EACxB;EACA,MAAM,YAAY,KAAKF,IAAI,QAAQ,GAAG;EACtC,OAAO;GAAC;GAAK;EAAS;CACxB;;;;;CAMA,IAAO,KAAa,IAAuC;EACzD,MAAM,YAAY,KAAK,IAAI,GAAG;EAC9B,IAAI;GACF,OAAO,GAAG,SAAS;EACrB,UAAU;GACR,KAAK,OAAO,SAAS;EACvB;CACF;;;;;CAMA,OAAO,WAA4B;EACjC,MAAM,EAAC,QAAO;EACd,IAAI,CAAC,KAAKE,OAAO,IAAI,GAAG,GACtB,KAAKA,OAAO,IAAI,KAAK,CAAC,CAAC;EAEzB,MAAM,aAAa,KAAKA,OAAO,IAAI,GAAG;EACtC,IAAI,YAAY;GACd,WAAW,KAAK,UAAU,SAAS;GACnC,KAAKD;EACP;CACF;AACF;AAEA,SAAS,oBAAoB,KAAa;CACxC,OAAO,IAAI,WAAW,QAAQ,GAAG;AACnC"}
1
+ {"version":3,"file":"statement-cache.js","names":["#db","#size","#cache"],"sources":["../../../../../zqlite/src/internal/statement-cache.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {Database, Statement} from '../db.ts';\n\nexport type CachedStatementMap = Map<string, Statement[]>;\nexport type CachedStatement = {\n sql: string;\n statement: Statement;\n};\n/**\n * SQLite statement preparation isn't cheap as it involves evaluating possible\n * query plans and picking the best one (in addition to parsing the SQL).\n *\n * This statement cache prevents the need to re-prepare the same statement\n * multiple times.\n *\n * One extra wrinkle is that a single statement cannot be used by multiple\n * callers at the same time. As in, we can't `iterate` the same statement\n * many times concurrently.\n *\n * Given that, statements are removed from the cache while in use.\n * - `get` removes the statement from the cache\n * - `return` adds it back.\n *\n * If a request for the same sql is made while a\n * statement is gotten, a new statement will be prepared.\n * Both statements can be returned to the cache even though they both\n * serve the same SQL. Having both copies returned to the cache allows\n * the cache to serve multiple callers concurrently in the future.\n *\n * It is not an error to fail to call `return` on a statement.\n * Failing to call return will only prevent the statement from being reused\n * by other callers. It will not cause a resource leak.\n */\nexport class StatementCache {\n #cache: CachedStatementMap = new Map<string, Statement[]>();\n readonly #db: Database;\n #size: number = 0;\n\n /**\n * The db connection used to prepare the statement.\n * It is an error to use a statement prepared on one connection with another connection.\n * @param db\n */\n constructor(db: Database) {\n this.#db = db;\n }\n\n // the number of statements in the cache\n get size() {\n return this.#size;\n }\n\n drop(n: number) {\n assert(n >= 0, 'Cannot drop a negative number of items');\n assert(n <= this.#size, 'Cannot drop more items than are in the cache');\n\n let remaining = n;\n for (const [sql, statements] of this.#cache.entries()) {\n if (remaining >= statements.length) {\n this.#cache.delete(sql);\n remaining -= statements.length;\n this.#size -= statements.length;\n } else {\n statements.splice(0, remaining);\n this.#size -= remaining;\n break;\n }\n }\n }\n\n /**\n * Prepares a statement for the given sql unless one is already cached.\n * If one is cached, it is removed from the cache and returned.\n *\n * Since `get` removes the item from the cache it is not an error to fail to call\n * `return`. The gotten statement will be correctly garbage collected.\n *\n * When a gotten statement is not returned, future calls to\n * `get` with the same `sql` will prepare a new statement.\n *\n * @param sql\n * @returns\n */\n get(sql: string): CachedStatement {\n sql = normalizeWhitespace(sql);\n const statements = this.#cache.get(sql);\n if (statements && statements.length > 0) {\n const statement = statements.pop()!;\n this.#size--;\n if (statements.length === 0) {\n this.#cache.delete(sql);\n }\n return {sql, statement};\n }\n const statement = this.#db.prepare(sql);\n return {sql, statement};\n }\n\n /**\n * Handles `get` and `return` for the caller by invoking them before\n * and after the callback.\n */\n use<T>(sql: string, cb: (statement: CachedStatement) => T) {\n const statement = this.get(sql);\n try {\n return cb(statement);\n } finally {\n this.return(statement);\n }\n }\n\n /**\n * Add a statement back to the cache so someone else can use it later.\n * @param statement\n */\n return(statement: CachedStatement) {\n const {sql} = statement;\n if (!this.#cache.has(sql)) {\n this.#cache.set(sql, []);\n }\n const statements = this.#cache.get(sql);\n if (statements) {\n statements.push(statement.statement);\n this.#size++;\n }\n }\n}\n\nfunction normalizeWhitespace(sql: string) {\n return sql.replaceAll(/\\s+/g, ' ');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,iBAAb,MAA4B;CAC1B,yBAA6B,IAAI,KAA0B;CAC3D;CACA,QAAgB;;;;;;CAOhB,YAAY,IAAc;AACxB,QAAA,KAAW;;CAIb,IAAI,OAAO;AACT,SAAO,MAAA;;CAGT,KAAK,GAAW;AACd,SAAO,KAAK,GAAG,yCAAyC;AACxD,SAAO,KAAK,MAAA,MAAY,+CAA+C;EAEvE,IAAI,YAAY;AAChB,OAAK,MAAM,CAAC,KAAK,eAAe,MAAA,MAAY,SAAS,CACnD,KAAI,aAAa,WAAW,QAAQ;AAClC,SAAA,MAAY,OAAO,IAAI;AACvB,gBAAa,WAAW;AACxB,SAAA,QAAc,WAAW;SACpB;AACL,cAAW,OAAO,GAAG,UAAU;AAC/B,SAAA,QAAc;AACd;;;;;;;;;;;;;;;;CAkBN,IAAI,KAA8B;AAChC,QAAM,oBAAoB,IAAI;EAC9B,MAAM,aAAa,MAAA,MAAY,IAAI,IAAI;AACvC,MAAI,cAAc,WAAW,SAAS,GAAG;GACvC,MAAM,YAAY,WAAW,KAAK;AAClC,SAAA;AACA,OAAI,WAAW,WAAW,EACxB,OAAA,MAAY,OAAO,IAAI;AAEzB,UAAO;IAAC;IAAK;IAAU;;EAEzB,MAAM,YAAY,MAAA,GAAS,QAAQ,IAAI;AACvC,SAAO;GAAC;GAAK;GAAU;;;;;;CAOzB,IAAO,KAAa,IAAuC;EACzD,MAAM,YAAY,KAAK,IAAI,IAAI;AAC/B,MAAI;AACF,UAAO,GAAG,UAAU;YACZ;AACR,QAAK,OAAO,UAAU;;;;;;;CAQ1B,OAAO,WAA4B;EACjC,MAAM,EAAC,QAAO;AACd,MAAI,CAAC,MAAA,MAAY,IAAI,IAAI,CACvB,OAAA,MAAY,IAAI,KAAK,EAAE,CAAC;EAE1B,MAAM,aAAa,MAAA,MAAY,IAAI,IAAI;AACvC,MAAI,YAAY;AACd,cAAW,KAAK,UAAU,UAAU;AACpC,SAAA;;;;AAKN,SAAS,oBAAoB,KAAa;AACxC,QAAO,IAAI,WAAW,QAAQ,IAAI"}
@@ -1 +1 @@
1
- {"version":3,"file":"query-builder.js","names":[],"sources":["../../../../zqlite/src/query-builder.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport {assert} from '../../shared/src/asserts.ts';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport type {Constraint} from '../../zql/src/ivm/constraint.ts';\nimport type {MultiConstraint, Start} from '../../zql/src/ivm/operator.ts';\nimport {sql} from './internal/sql.ts';\n\n/**\n * Condition type without correlated subqueries.\n * This matches the output of transformFilters from zql/builder/filter.ts\n */\nexport type NoSubqueryCondition = Exclude<\n Condition,\n {type: 'correlatedSubquery'}\n>;\n\nexport function buildSelectQuery(\n tableName: string,\n columns: Record<string, SchemaValue>,\n constraint: Constraint | undefined,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n reverse: boolean | undefined,\n start: Start | undefined,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n) {\n let query = sql`SELECT ${sql.join(\n Object.keys(columns).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(tableName)}`;\n const constraints: SQLQuery[] = constraintsToSQL(constraint, columns);\n\n if (multiConstraints) {\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n constraints.push(multiConstraintToSQL(mc, columns));\n }\n }\n }\n\n if (start) {\n assert(order !== undefined, 'start requires ordering');\n constraints.push(gatherStartConstraints(start, reverse, order, columns));\n }\n\n if (filters) {\n constraints.push(filtersToSQL(filters));\n }\n\n if (constraints.length > 0) {\n query = sql`${query} WHERE ${sql.join(constraints, sql` AND `)}`;\n }\n\n if (order && order.length > 0) {\n return sql`${query} ${orderByToSQL(order, !!reverse)}`;\n }\n return query;\n}\n\nexport function constraintsToSQL(\n constraint: Constraint | undefined,\n columns: Record<string, SchemaValue>,\n) {\n if (!constraint) {\n return [];\n }\n\n const constraints: SQLQuery[] = [];\n for (const [key, value] of Object.entries(constraint)) {\n constraints.push(\n sql`${sql.ident(key)} = ${toSQLiteType(value, columns[key].type)}`,\n );\n }\n\n return constraints;\n}\n\n/**\n * Builds a single batched IN clause from a `MultiConstraint`. All entries\n * are assumed to share the same shape (the keys of the first entry);\n * FlippedJoin derives them from the same parentKey for all children.\n *\n * Single-column form: `col IN (?, ?, ?)`\n * Compound form: `(a, b) IN (VALUES (?, ?), (?, ?), …)`\n *\n * NOTE: SQLite optimizes `col IN (literal-list)` using the column's index;\n * verified via EXPLAIN QUERY PLAN — see query-builder.test.ts.\n */\nexport function multiConstraintToSQL(\n multiConstraint: MultiConstraint,\n columns: Record<string, SchemaValue>,\n): SQLQuery {\n assert(multiConstraint.length > 0, 'multiConstraint must be non-empty');\n // All entries share the same keys; pull the column list from the first.\n const keys = Object.keys(multiConstraint[0]);\n assert(keys.length > 0, 'multiConstraint entries must have at least one key');\n // Subsequent entries must share the first entry's shape — the SQL form\n // is `(col_a, col_b, …) IN VALUES (…)`, with one binding per key per\n // entry. Heterogeneous keys would silently produce incorrect bindings.\n for (let i = 1; i < multiConstraint.length; i++) {\n const entry = multiConstraint[i];\n assert(\n Object.keys(entry).length === keys.length && keys.every(k => k in entry),\n () =>\n `multiConstraint entries must share the same keys (entry 0: [${keys.join(\n ',',\n )}], entry ${i}: [${Object.keys(entry).join(',')}])`,\n );\n }\n\n if (keys.length === 1) {\n const key = keys[0];\n const colType = columns[key].type;\n return sql`${sql.ident(key)} IN (${sql.join(\n multiConstraint.map(c => sql`${toSQLiteType(c[key], colType)}`),\n sql`,`,\n )})`;\n }\n\n // Compound: `(col_a, col_b, …) IN (VALUES (?, ?, …), …)`\n const colList = sql`(${sql.join(\n keys.map(k => sql.ident(k)),\n sql`,`,\n )})`;\n const rows = multiConstraint.map(\n c =>\n sql`(${sql.join(\n keys.map(k => sql`${toSQLiteType(c[k], columns[k].type)}`),\n sql`,`,\n )})`,\n );\n return sql`${colList} IN (VALUES ${sql.join(rows, sql`,`)})`;\n}\n\nexport function orderByToSQL(order: Ordering, reverse: boolean): SQLQuery {\n if (reverse) {\n return sql`ORDER BY ${sql.join(\n order.map(\n s =>\n sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(\n s[1] === 'asc' ? 'desc' : 'asc',\n )}`,\n ),\n sql`, `,\n )}`;\n } else {\n return sql`ORDER BY ${sql.join(\n order.map(\n s => sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(s[1])}`,\n ),\n sql`, `,\n )}`;\n }\n}\n\n/**\n * Converts filters (conditions) to SQL WHERE clause.\n * This applies all filters present in the AST for a query to the source.\n */\nexport function filtersToSQL(filters: NoSubqueryCondition): SQLQuery {\n switch (filters.type) {\n case 'simple':\n return simpleConditionToSQL(filters);\n case 'and':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` AND `,\n )})`\n : sql`TRUE`;\n case 'or':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` OR `,\n )})`\n : sql`FALSE`;\n }\n}\n\nfunction simpleConditionToSQL(filter: SimpleCondition): SQLQuery {\n const {op} = filter;\n if (op === 'IN' || op === 'NOT IN') {\n switch (filter.right.type) {\n case 'literal':\n return sql`${valuePositionToSQL(\n filter.left,\n )} ${sql.__dangerous__rawValue(\n filter.op,\n )} (SELECT value FROM json_each(${JSON.stringify(\n filter.right.value,\n )}))`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n }\n return sql`${valuePositionToSQL(filter.left)} ${sql.__dangerous__rawValue(\n // SQLite's LIKE operator is case-insensitive by default, so we\n // convert ILIKE to LIKE and NOT ILIKE to NOT LIKE.\n filter.op === 'ILIKE'\n ? 'LIKE'\n : filter.op === 'NOT ILIKE'\n ? 'NOT LIKE'\n : filter.op,\n )} ${valuePositionToSQL(filter.right)}`;\n}\n\nfunction valuePositionToSQL(value: ValuePosition): SQLQuery {\n switch (value.type) {\n case 'column':\n return sql.ident(value.name);\n case 'literal':\n return sql`${toSQLiteType(value.value, getJsType(value.value))}`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n}\n\nfunction getJsType(value: unknown): ValueType {\n if (value === null) {\n return 'null';\n }\n return typeof value === 'string'\n ? 'string'\n : typeof value === 'number'\n ? 'number'\n : typeof value === 'boolean'\n ? 'boolean'\n : 'json';\n}\n\nexport function toSQLiteType(v: unknown, type: ValueType): unknown {\n switch (type) {\n case 'boolean':\n return v === null ? null : v ? 1 : 0;\n case 'number':\n case 'string':\n case 'null':\n return v;\n case 'json':\n return JSON.stringify(v);\n }\n}\n\nfunction nullableAwareEquality(\n field: string,\n value: unknown,\n columnType: SchemaValue,\n): SQLQuery {\n // Use = instead of IS for non-nullable columns to enable better\n // index usage in SQLite.\n return columnType.optional === true\n ? sql`${sql.ident(field)} IS ${value}`\n : sql`${sql.ident(field)} = ${value}`;\n}\n\nfunction nullableAwareRangeComparison(\n field: string,\n value: unknown,\n operator: '>' | '<',\n columnType: SchemaValue,\n): SQLQuery {\n // For non-nullable columns, skip IS NULL checks to avoid breaking\n // SQLite's MULTI-INDEX OR optimization, which falls back to a full\n // table scan when any OR branch involves NULL.\n // See: https://github.com/rocicorp/mono/pull/5542\n const comparison = sql`${sql.ident(field)} ${sql.__dangerous__rawValue(\n operator,\n )} ${value}`;\n if (columnType.optional !== true) {\n return comparison;\n }\n\n return operator === '>'\n ? sql`(${value} IS NULL OR ${comparison})`\n : sql`(${sql.ident(field)} IS NULL OR ${comparison})`;\n}\n\n/**\n * The ordering could be complex such as:\n * `ORDER BY a ASC, b DESC, c ASC`\n *\n * In those cases, we need to encode the constraints as various\n * `OR` clauses.\n *\n * E.g.,\n *\n * to get the row after (a = 1, b = 2, c = 3) would be:\n *\n * `WHERE a > 1 OR (a = 1 AND b < 2) OR (a = 1 AND b = 2 AND c > 3)`\n *\n * - after vs before flips the comparison operators.\n * - inclusive adds a final `OR` clause for the exact match.\n */\nfunction gatherStartConstraints(\n start: Start,\n reverse: boolean | undefined,\n order: Ordering,\n columnTypes: Record<string, SchemaValue>,\n): SQLQuery {\n const constraints: SQLQuery[] = [];\n const {row: from, basis} = start;\n\n for (let i = 0; i < order.length; i++) {\n const group: SQLQuery[] = [];\n const [iField, iDirection] = order[i];\n for (let j = 0; j <= i; j++) {\n if (j === i) {\n const columnType = columnTypes[iField];\n const constraintValue = toSQLiteType(from[iField], columnType.type);\n const operator =\n iDirection === 'asc' ? (reverse ? '<' : '>') : reverse ? '>' : '<';\n group.push(\n nullableAwareRangeComparison(\n iField,\n constraintValue,\n operator,\n columnType,\n ),\n );\n } else {\n const [jField] = order[j];\n const columnType = columnTypes[jField];\n const value = toSQLiteType(from[jField], columnType.type);\n group.push(nullableAwareEquality(jField, value, columnType));\n }\n }\n constraints.push(sql`(${sql.join(group, sql` AND `)})`);\n }\n\n if (basis === 'at') {\n constraints.push(\n sql`(${sql.join(\n order.map(([field]) => {\n const columnType = columnTypes[field];\n const value = toSQLiteType(from[field], columnType.type);\n return nullableAwareEquality(field, value, columnType);\n }),\n sql` AND `,\n )})`,\n );\n }\n\n return sql`(${sql.join(constraints, sql` OR `)})`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"query-builder.js","names":[],"sources":["../../../../zqlite/src/query-builder.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport {assert} from '../../shared/src/asserts.ts';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport type {Constraint} from '../../zql/src/ivm/constraint.ts';\nimport type {MultiConstraint, Start} from '../../zql/src/ivm/operator.ts';\nimport {sql} from './internal/sql.ts';\n\n/**\n * Condition type without correlated subqueries.\n * This matches the output of transformFilters from zql/builder/filter.ts\n */\nexport type NoSubqueryCondition = Exclude<\n Condition,\n {type: 'correlatedSubquery'}\n>;\n\nexport function buildSelectQuery(\n tableName: string,\n columns: Record<string, SchemaValue>,\n constraint: Constraint | undefined,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n reverse: boolean | undefined,\n start: Start | undefined,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n) {\n let query = sql`SELECT ${sql.join(\n Object.keys(columns).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(tableName)}`;\n const constraints: SQLQuery[] = constraintsToSQL(constraint, columns);\n\n if (multiConstraints) {\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n constraints.push(multiConstraintToSQL(mc, columns));\n }\n }\n }\n\n if (start) {\n assert(order !== undefined, 'start requires ordering');\n constraints.push(gatherStartConstraints(start, reverse, order, columns));\n }\n\n if (filters) {\n constraints.push(filtersToSQL(filters));\n }\n\n if (constraints.length > 0) {\n query = sql`${query} WHERE ${sql.join(constraints, sql` AND `)}`;\n }\n\n if (order && order.length > 0) {\n return sql`${query} ${orderByToSQL(order, !!reverse)}`;\n }\n return query;\n}\n\nexport function constraintsToSQL(\n constraint: Constraint | undefined,\n columns: Record<string, SchemaValue>,\n) {\n if (!constraint) {\n return [];\n }\n\n const constraints: SQLQuery[] = [];\n for (const [key, value] of Object.entries(constraint)) {\n constraints.push(\n sql`${sql.ident(key)} = ${toSQLiteType(value, columns[key].type)}`,\n );\n }\n\n return constraints;\n}\n\n/**\n * Builds a single batched IN clause from a `MultiConstraint`. All entries\n * are assumed to share the same shape (the keys of the first entry);\n * FlippedJoin derives them from the same parentKey for all children.\n *\n * Single-column form: `col IN (?, ?, ?)`\n * Compound form: `(a, b) IN (VALUES (?, ?), (?, ?), …)`\n *\n * NOTE: SQLite optimizes `col IN (literal-list)` using the column's index;\n * verified via EXPLAIN QUERY PLAN — see query-builder.test.ts.\n */\nexport function multiConstraintToSQL(\n multiConstraint: MultiConstraint,\n columns: Record<string, SchemaValue>,\n): SQLQuery {\n assert(multiConstraint.length > 0, 'multiConstraint must be non-empty');\n // All entries share the same keys; pull the column list from the first.\n const keys = Object.keys(multiConstraint[0]);\n assert(keys.length > 0, 'multiConstraint entries must have at least one key');\n // Subsequent entries must share the first entry's shape — the SQL form\n // is `(col_a, col_b, …) IN VALUES (…)`, with one binding per key per\n // entry. Heterogeneous keys would silently produce incorrect bindings.\n for (let i = 1; i < multiConstraint.length; i++) {\n const entry = multiConstraint[i];\n assert(\n Object.keys(entry).length === keys.length && keys.every(k => k in entry),\n () =>\n `multiConstraint entries must share the same keys (entry 0: [${keys.join(\n ',',\n )}], entry ${i}: [${Object.keys(entry).join(',')}])`,\n );\n }\n\n if (keys.length === 1) {\n const key = keys[0];\n const colType = columns[key].type;\n return sql`${sql.ident(key)} IN (${sql.join(\n multiConstraint.map(c => sql`${toSQLiteType(c[key], colType)}`),\n sql`,`,\n )})`;\n }\n\n // Compound: `(col_a, col_b, …) IN (VALUES (?, ?, …), …)`\n const colList = sql`(${sql.join(\n keys.map(k => sql.ident(k)),\n sql`,`,\n )})`;\n const rows = multiConstraint.map(\n c =>\n sql`(${sql.join(\n keys.map(k => sql`${toSQLiteType(c[k], columns[k].type)}`),\n sql`,`,\n )})`,\n );\n return sql`${colList} IN (VALUES ${sql.join(rows, sql`,`)})`;\n}\n\nexport function orderByToSQL(order: Ordering, reverse: boolean): SQLQuery {\n if (reverse) {\n return sql`ORDER BY ${sql.join(\n order.map(\n s =>\n sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(\n s[1] === 'asc' ? 'desc' : 'asc',\n )}`,\n ),\n sql`, `,\n )}`;\n } else {\n return sql`ORDER BY ${sql.join(\n order.map(\n s => sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(s[1])}`,\n ),\n sql`, `,\n )}`;\n }\n}\n\n/**\n * Converts filters (conditions) to SQL WHERE clause.\n * This applies all filters present in the AST for a query to the source.\n */\nexport function filtersToSQL(filters: NoSubqueryCondition): SQLQuery {\n switch (filters.type) {\n case 'simple':\n return simpleConditionToSQL(filters);\n case 'and':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` AND `,\n )})`\n : sql`TRUE`;\n case 'or':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` OR `,\n )})`\n : sql`FALSE`;\n }\n}\n\nfunction simpleConditionToSQL(filter: SimpleCondition): SQLQuery {\n const {op} = filter;\n if (op === 'IN' || op === 'NOT IN') {\n switch (filter.right.type) {\n case 'literal':\n return sql`${valuePositionToSQL(\n filter.left,\n )} ${sql.__dangerous__rawValue(\n filter.op,\n )} (SELECT value FROM json_each(${JSON.stringify(\n filter.right.value,\n )}))`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n }\n return sql`${valuePositionToSQL(filter.left)} ${sql.__dangerous__rawValue(\n // SQLite's LIKE operator is case-insensitive by default, so we\n // convert ILIKE to LIKE and NOT ILIKE to NOT LIKE.\n filter.op === 'ILIKE'\n ? 'LIKE'\n : filter.op === 'NOT ILIKE'\n ? 'NOT LIKE'\n : filter.op,\n )} ${valuePositionToSQL(filter.right)}`;\n}\n\nfunction valuePositionToSQL(value: ValuePosition): SQLQuery {\n switch (value.type) {\n case 'column':\n return sql.ident(value.name);\n case 'literal':\n return sql`${toSQLiteType(value.value, getJsType(value.value))}`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n}\n\nfunction getJsType(value: unknown): ValueType {\n if (value === null) {\n return 'null';\n }\n return typeof value === 'string'\n ? 'string'\n : typeof value === 'number'\n ? 'number'\n : typeof value === 'boolean'\n ? 'boolean'\n : 'json';\n}\n\nexport function toSQLiteType(v: unknown, type: ValueType): unknown {\n switch (type) {\n case 'boolean':\n return v === null ? null : v ? 1 : 0;\n case 'number':\n case 'string':\n case 'null':\n return v;\n case 'json':\n return JSON.stringify(v);\n }\n}\n\nfunction nullableAwareEquality(\n field: string,\n value: unknown,\n columnType: SchemaValue,\n): SQLQuery {\n // Use = instead of IS for non-nullable columns to enable better\n // index usage in SQLite.\n return columnType.optional === true\n ? sql`${sql.ident(field)} IS ${value}`\n : sql`${sql.ident(field)} = ${value}`;\n}\n\nfunction nullableAwareRangeComparison(\n field: string,\n value: unknown,\n operator: '>' | '<',\n columnType: SchemaValue,\n): SQLQuery {\n // For non-nullable columns, skip IS NULL checks to avoid breaking\n // SQLite's MULTI-INDEX OR optimization, which falls back to a full\n // table scan when any OR branch involves NULL.\n // See: https://github.com/rocicorp/mono/pull/5542\n const comparison = sql`${sql.ident(field)} ${sql.__dangerous__rawValue(\n operator,\n )} ${value}`;\n if (columnType.optional !== true) {\n return comparison;\n }\n\n return operator === '>'\n ? sql`(${value} IS NULL OR ${comparison})`\n : sql`(${sql.ident(field)} IS NULL OR ${comparison})`;\n}\n\n/**\n * The ordering could be complex such as:\n * `ORDER BY a ASC, b DESC, c ASC`\n *\n * In those cases, we need to encode the constraints as various\n * `OR` clauses.\n *\n * E.g.,\n *\n * to get the row after (a = 1, b = 2, c = 3) would be:\n *\n * `WHERE a > 1 OR (a = 1 AND b < 2) OR (a = 1 AND b = 2 AND c > 3)`\n *\n * - after vs before flips the comparison operators.\n * - inclusive adds a final `OR` clause for the exact match.\n */\nfunction gatherStartConstraints(\n start: Start,\n reverse: boolean | undefined,\n order: Ordering,\n columnTypes: Record<string, SchemaValue>,\n): SQLQuery {\n const constraints: SQLQuery[] = [];\n const {row: from, basis} = start;\n\n for (let i = 0; i < order.length; i++) {\n const group: SQLQuery[] = [];\n const [iField, iDirection] = order[i];\n for (let j = 0; j <= i; j++) {\n if (j === i) {\n const columnType = columnTypes[iField];\n const constraintValue = toSQLiteType(from[iField], columnType.type);\n const operator =\n iDirection === 'asc' ? (reverse ? '<' : '>') : reverse ? '>' : '<';\n group.push(\n nullableAwareRangeComparison(\n iField,\n constraintValue,\n operator,\n columnType,\n ),\n );\n } else {\n const [jField] = order[j];\n const columnType = columnTypes[jField];\n const value = toSQLiteType(from[jField], columnType.type);\n group.push(nullableAwareEquality(jField, value, columnType));\n }\n }\n constraints.push(sql`(${sql.join(group, sql` AND `)})`);\n }\n\n if (basis === 'at') {\n constraints.push(\n sql`(${sql.join(\n order.map(([field]) => {\n const columnType = columnTypes[field];\n const value = toSQLiteType(from[field], columnType.type);\n return nullableAwareEquality(field, value, columnType);\n }),\n sql` AND `,\n )})`,\n );\n }\n\n return sql`(${sql.join(constraints, sql` OR `)})`;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"query-delegate.js","names":["#lc","#db","#schema","#sources","#logConfig","#commitObservers"],"sources":["../../../../zqlite/src/query-delegate.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {LogConfig} from '../../otel/src/log-options.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {Source} from '../../zql/src/ivm/source.ts';\nimport {QueryDelegateBase} from '../../zql/src/query/query-delegate-base.ts';\nimport type {CommitListener} from '../../zql/src/query/query-delegate.ts';\nimport type {Database} from './db.ts';\nimport {TableSource} from './table-source.ts';\n\nexport class QueryDelegateImpl extends QueryDelegateBase {\n readonly #lc: LogContext;\n readonly #db: Database;\n readonly #schema: Schema;\n readonly #sources: Map<string, Source> = new Map();\n readonly #logConfig: LogConfig;\n readonly defaultQueryComplete = true;\n readonly #commitObservers = new Set<() => void>();\n\n constructor(\n lc: LogContext,\n db: Database,\n schema: Schema,\n logConfig?: LogConfig,\n ) {\n super();\n this.#lc = lc.withContext('class', 'QueryDelegateImpl');\n this.#db = db;\n this.#schema = schema;\n this.#logConfig = logConfig ?? {\n format: 'text',\n ivmSampling: 0,\n level: 'info',\n slowHydrateThreshold: 0,\n slowRowThreshold: 0,\n };\n }\n\n getSource(tableName: string): Source {\n let source = this.#sources.get(tableName);\n if (source) {\n return source;\n }\n\n const tableSchema = this.#schema.tables[tableName];\n\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n this.#db,\n tableName,\n tableSchema.columns,\n tableSchema.primaryKey,\n );\n\n this.#sources.set(tableName, source);\n return source;\n }\n\n onTransactionCommit(cb: CommitListener) {\n this.#commitObservers.add(cb);\n return () => {\n this.#commitObservers.delete(cb);\n };\n }\n override batchViewUpdates<T>(applyViewUpdates: () => T): T {\n const ret = applyViewUpdates();\n for (const observer of this.#commitObservers) {\n observer();\n }\n return ret;\n }\n}\n"],"mappings":";;;AASA,IAAa,oBAAb,cAAuC,kBAAkB;CACvD;CACA;CACA;CACA,2BAAyC,IAAI,IAAI;CACjD;CACA,uBAAgC;CAChC,mCAA4B,IAAI,IAAgB;CAEhD,YACE,IACA,IACA,QACA,WACA;EACA,MAAM;EACN,KAAKA,MAAM,GAAG,YAAY,SAAS,mBAAmB;EACtD,KAAKC,MAAM;EACX,KAAKC,UAAU;EACf,KAAKE,aAAa,aAAa;GAC7B,QAAQ;GACR,aAAa;GACb,OAAO;GACP,sBAAsB;GACtB,kBAAkB;EACpB;CACF;CAEA,UAAU,WAA2B;EACnC,IAAI,SAAS,KAAKD,SAAS,IAAI,SAAS;EACxC,IAAI,QACF,OAAO;EAGT,MAAM,cAAc,KAAKD,QAAQ,OAAO;EAExC,SAAS,IAAI,YACX,KAAKF,KACL,KAAKI,YACL,KAAKH,KACL,WACA,YAAY,SACZ,YAAY,UACd;EAEA,KAAKE,SAAS,IAAI,WAAW,MAAM;EACnC,OAAO;CACT;CAEA,oBAAoB,IAAoB;EACtC,KAAKE,iBAAiB,IAAI,EAAE;EAC5B,aAAa;GACX,KAAKA,iBAAiB,OAAO,EAAE;EACjC;CACF;CACA,iBAA6B,kBAA8B;EACzD,MAAM,MAAM,iBAAiB;EAC7B,KAAK,MAAM,YAAY,KAAKA,kBAC1B,SAAS;EAEX,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"query-delegate.js","names":["#lc","#db","#schema","#sources","#logConfig","#commitObservers"],"sources":["../../../../zqlite/src/query-delegate.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {LogConfig} from '../../otel/src/log-options.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {Source} from '../../zql/src/ivm/source.ts';\nimport {QueryDelegateBase} from '../../zql/src/query/query-delegate-base.ts';\nimport type {CommitListener} from '../../zql/src/query/query-delegate.ts';\nimport type {Database} from './db.ts';\nimport {TableSource} from './table-source.ts';\n\nexport class QueryDelegateImpl extends QueryDelegateBase {\n readonly #lc: LogContext;\n readonly #db: Database;\n readonly #schema: Schema;\n readonly #sources: Map<string, Source> = new Map();\n readonly #logConfig: LogConfig;\n readonly defaultQueryComplete = true;\n readonly #commitObservers = new Set<() => void>();\n\n constructor(\n lc: LogContext,\n db: Database,\n schema: Schema,\n logConfig?: LogConfig,\n ) {\n super();\n this.#lc = lc.withContext('class', 'QueryDelegateImpl');\n this.#db = db;\n this.#schema = schema;\n this.#logConfig = logConfig ?? {\n format: 'text',\n ivmSampling: 0,\n level: 'info',\n slowHydrateThreshold: 0,\n slowRowThreshold: 0,\n };\n }\n\n getSource(tableName: string): Source {\n let source = this.#sources.get(tableName);\n if (source) {\n return source;\n }\n\n const tableSchema = this.#schema.tables[tableName];\n\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n this.#db,\n tableName,\n tableSchema.columns,\n tableSchema.primaryKey,\n );\n\n this.#sources.set(tableName, source);\n return source;\n }\n\n onTransactionCommit(cb: CommitListener) {\n this.#commitObservers.add(cb);\n return () => {\n this.#commitObservers.delete(cb);\n };\n }\n override batchViewUpdates<T>(applyViewUpdates: () => T): T {\n const ret = applyViewUpdates();\n for (const observer of this.#commitObservers) {\n observer();\n }\n return ret;\n }\n}\n"],"mappings":";;;AASA,IAAa,oBAAb,cAAuC,kBAAkB;CACvD;CACA;CACA;CACA,2BAAyC,IAAI,KAAK;CAClD;CACA,uBAAgC;CAChC,mCAA4B,IAAI,KAAiB;CAEjD,YACE,IACA,IACA,QACA,WACA;AACA,SAAO;AACP,QAAA,KAAW,GAAG,YAAY,SAAS,oBAAoB;AACvD,QAAA,KAAW;AACX,QAAA,SAAe;AACf,QAAA,YAAkB,aAAa;GAC7B,QAAQ;GACR,aAAa;GACb,OAAO;GACP,sBAAsB;GACtB,kBAAkB;GACnB;;CAGH,UAAU,WAA2B;EACnC,IAAI,SAAS,MAAA,QAAc,IAAI,UAAU;AACzC,MAAI,OACF,QAAO;EAGT,MAAM,cAAc,MAAA,OAAa,OAAO;AAExC,WAAS,IAAI,YACX,MAAA,IACA,MAAA,WACA,MAAA,IACA,WACA,YAAY,SACZ,YAAY,WACb;AAED,QAAA,QAAc,IAAI,WAAW,OAAO;AACpC,SAAO;;CAGT,oBAAoB,IAAoB;AACtC,QAAA,gBAAsB,IAAI,GAAG;AAC7B,eAAa;AACX,SAAA,gBAAsB,OAAO,GAAG;;;CAGpC,iBAA6B,kBAA8B;EACzD,MAAM,MAAM,kBAAkB;AAC9B,OAAK,MAAM,YAAY,MAAA,gBACrB,WAAU;AAEZ,SAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-scalar-subqueries.js","names":[],"sources":["../../../../zqlite/src/resolve-scalar-subqueries.ts"],"sourcesContent":["import type {\n AST,\n Condition,\n CorrelatedSubqueryCondition,\n LiteralValue,\n SimpleCondition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {PrimaryKey} from '../../zero-protocol/src/primary-key.ts';\n\ntype TableSpecWithUniqueKeys = {\n tableSpec: {\n uniqueKeys: PrimaryKey[];\n };\n};\n\nexport type CompanionSubquery = {\n /** The original scalar subquery AST (the subquery table query). */\n ast: AST;\n /** The field in the subquery row whose value was resolved. */\n childField: string;\n /** The resolved value, `null` if a row matched but the field was `NULL`,\n * or `undefined` if no row matched. */\n resolvedValue: LiteralValue | null | undefined;\n};\n\nexport type ResolveResult = {\n ast: AST;\n companions: CompanionSubquery[];\n};\n\n/**\n * Callback that executes a scalar subquery and returns the value of\n * `childField` from the (at most one) matching row, or `undefined`\n * if no rows match.\n */\nexport type ScalarExecutor = (\n subqueryAST: AST,\n childField: string,\n) => LiteralValue | null | undefined;\n\n/**\n * Resolves \"simple\" scalar subqueries by calling the provided executor\n * and replacing them with literal conditions. A scalar subquery is simple\n * when all columns of at least one unique index on the subquery table are\n * equality-constrained by literal values in the subquery's WHERE clause\n * (using only AND conjunctions).\n *\n * Non-simple scalar subqueries are left untouched for the existing\n * EXISTS rewrite in buildPipelineInternal.\n *\n * Returns the resolved AST and a list of companion subquery ASTs whose\n * rows need to be synced to the client for the EXISTS rewrite to work.\n */\nexport function resolveSimpleScalarSubqueries(\n ast: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n): ResolveResult {\n const companions: CompanionSubquery[] = [];\n const resolved = resolveASTRecursive(ast, tableSpecs, execute, companions);\n return {ast: resolved, companions};\n}\n\nfunction resolveASTRecursive(\n ast: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): AST {\n const where = ast.where\n ? resolveCondition(ast.where, tableSpecs, execute, companions)\n : undefined;\n\n const related = ast.related?.map(r => ({\n ...r,\n subquery: resolveASTRecursive(r.subquery, tableSpecs, execute, companions),\n }));\n\n if (where !== ast.where || related !== ast.related) {\n return {...ast, where, related};\n }\n return ast;\n}\n\nfunction resolveCondition(\n condition: Condition,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): Condition {\n switch (condition.type) {\n case 'correlatedSubquery':\n if (condition.scalar) {\n return resolveScalarSubquery(\n condition,\n tableSpecs,\n execute,\n companions,\n );\n }\n // Non-scalar correlated subquery: recurse into its subquery\n {\n const resolvedSubquery = resolveASTRecursive(\n condition.related.subquery,\n tableSpecs,\n execute,\n companions,\n );\n if (resolvedSubquery !== condition.related.subquery) {\n return {\n ...condition,\n related: {...condition.related, subquery: resolvedSubquery},\n };\n }\n return condition;\n }\n case 'and':\n case 'or': {\n const resolved = condition.conditions.map(c =>\n resolveCondition(c, tableSpecs, execute, companions),\n );\n if (resolved.every((c, i) => c === condition.conditions[i])) {\n return condition;\n }\n return {type: condition.type, conditions: resolved};\n }\n default:\n return condition;\n }\n}\n\nfunction resolveScalarSubquery(\n condition: CorrelatedSubqueryCondition,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): Condition {\n const parentField = condition.related.correlation.parentField[0];\n const childField = condition.related.correlation.childField[0];\n\n // Recursively resolve any scalar subqueries nested in the\n // subquery's own WHERE (and related) before evaluating this one.\n const subquery = resolveASTRecursive(\n condition.related.subquery,\n tableSpecs,\n execute,\n companions,\n );\n\n if (!isSimpleSubquery(subquery, tableSpecs)) {\n // Return with the (possibly partially-resolved) subquery.\n if (subquery !== condition.related.subquery) {\n return {\n ...condition,\n related: {...condition.related, subquery},\n };\n }\n return condition;\n }\n\n const value = execute(subquery, childField);\n\n // Record the companion subquery AST so its rows are synced to the client.\n // The client rewrites scalar subqueries to EXISTS and needs those rows.\n companions.push({\n ast: subquery,\n childField,\n resolvedValue: value,\n });\n\n if (value === undefined || value === null) {\n // No rows or NULL value — both x = NULL and x != NULL are false in SQL\n return ALWAYS_FALSE;\n }\n\n const op = condition.op === 'EXISTS' ? '=' : 'IS NOT';\n return {\n type: 'simple',\n op,\n left: {type: 'column', name: parentField},\n right: {type: 'literal', value},\n } satisfies SimpleCondition;\n}\n\nconst ALWAYS_FALSE: SimpleCondition = {\n type: 'simple',\n op: '=',\n left: {type: 'literal', value: 1},\n right: {type: 'literal', value: 0},\n};\n\n/**\n * Checks if the subquery is guaranteed to return at most one deterministic row.\n *\n * This is true when all columns of at least one unique index on the subquery\n * table are equality-constrained by literal values in the WHERE clause\n * (using only AND conjunctions).\n */\nexport function isSimpleSubquery(\n subquery: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n): boolean {\n const spec = tableSpecs.get(subquery.table);\n if (!spec) {\n return false;\n }\n\n if (!subquery.where) {\n return false;\n }\n\n const constraints = extractLiteralEqualityConstraints(subquery.where);\n if (constraints.size === 0) {\n return false;\n }\n\n const {uniqueKeys} = spec.tableSpec;\n return uniqueKeys.some(key => key.every(col => constraints.has(col)));\n}\n\n/**\n * Extracts column=literal equality constraints from a condition tree,\n * only following AND conjunctions (not OR).\n */\nexport function extractLiteralEqualityConstraints(\n condition: Condition,\n): Map<string, LiteralValue> {\n const constraints = new Map<string, LiteralValue>();\n collectConstraints(condition, constraints);\n return constraints;\n}\n\nfunction collectConstraints(\n condition: Condition,\n constraints: Map<string, LiteralValue>,\n): void {\n switch (condition.type) {\n case 'simple':\n if (condition.op === '=') {\n if (\n condition.left.type === 'column' &&\n condition.right.type === 'literal'\n ) {\n constraints.set(condition.left.name, condition.right.value);\n }\n }\n break;\n case 'and':\n for (const c of condition.conditions) {\n collectConstraints(c, constraints);\n }\n break;\n // OR, correlatedSubquery (non-scalar) — don't contribute constraints\n default:\n break;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolve-scalar-subqueries.js","names":[],"sources":["../../../../zqlite/src/resolve-scalar-subqueries.ts"],"sourcesContent":["import type {\n AST,\n Condition,\n CorrelatedSubqueryCondition,\n LiteralValue,\n SimpleCondition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {PrimaryKey} from '../../zero-protocol/src/primary-key.ts';\n\ntype TableSpecWithUniqueKeys = {\n tableSpec: {\n uniqueKeys: PrimaryKey[];\n };\n};\n\nexport type CompanionSubquery = {\n /** The original scalar subquery AST (the subquery table query). */\n ast: AST;\n /** The field in the subquery row whose value was resolved. */\n childField: string;\n /** The resolved value, `null` if a row matched but the field was `NULL`,\n * or `undefined` if no row matched. */\n resolvedValue: LiteralValue | null | undefined;\n};\n\nexport type ResolveResult = {\n ast: AST;\n companions: CompanionSubquery[];\n};\n\n/**\n * Callback that executes a scalar subquery and returns the value of\n * `childField` from the (at most one) matching row, or `undefined`\n * if no rows match.\n */\nexport type ScalarExecutor = (\n subqueryAST: AST,\n childField: string,\n) => LiteralValue | null | undefined;\n\n/**\n * Resolves \"simple\" scalar subqueries by calling the provided executor\n * and replacing them with literal conditions. A scalar subquery is simple\n * when all columns of at least one unique index on the subquery table are\n * equality-constrained by literal values in the subquery's WHERE clause\n * (using only AND conjunctions).\n *\n * Non-simple scalar subqueries are left untouched for the existing\n * EXISTS rewrite in buildPipelineInternal.\n *\n * Returns the resolved AST and a list of companion subquery ASTs whose\n * rows need to be synced to the client for the EXISTS rewrite to work.\n */\nexport function resolveSimpleScalarSubqueries(\n ast: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n): ResolveResult {\n const companions: CompanionSubquery[] = [];\n const resolved = resolveASTRecursive(ast, tableSpecs, execute, companions);\n return {ast: resolved, companions};\n}\n\nfunction resolveASTRecursive(\n ast: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): AST {\n const where = ast.where\n ? resolveCondition(ast.where, tableSpecs, execute, companions)\n : undefined;\n\n const related = ast.related?.map(r => ({\n ...r,\n subquery: resolveASTRecursive(r.subquery, tableSpecs, execute, companions),\n }));\n\n if (where !== ast.where || related !== ast.related) {\n return {...ast, where, related};\n }\n return ast;\n}\n\nfunction resolveCondition(\n condition: Condition,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): Condition {\n switch (condition.type) {\n case 'correlatedSubquery':\n if (condition.scalar) {\n return resolveScalarSubquery(\n condition,\n tableSpecs,\n execute,\n companions,\n );\n }\n // Non-scalar correlated subquery: recurse into its subquery\n {\n const resolvedSubquery = resolveASTRecursive(\n condition.related.subquery,\n tableSpecs,\n execute,\n companions,\n );\n if (resolvedSubquery !== condition.related.subquery) {\n return {\n ...condition,\n related: {...condition.related, subquery: resolvedSubquery},\n };\n }\n return condition;\n }\n case 'and':\n case 'or': {\n const resolved = condition.conditions.map(c =>\n resolveCondition(c, tableSpecs, execute, companions),\n );\n if (resolved.every((c, i) => c === condition.conditions[i])) {\n return condition;\n }\n return {type: condition.type, conditions: resolved};\n }\n default:\n return condition;\n }\n}\n\nfunction resolveScalarSubquery(\n condition: CorrelatedSubqueryCondition,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): Condition {\n const parentField = condition.related.correlation.parentField[0];\n const childField = condition.related.correlation.childField[0];\n\n // Recursively resolve any scalar subqueries nested in the\n // subquery's own WHERE (and related) before evaluating this one.\n const subquery = resolveASTRecursive(\n condition.related.subquery,\n tableSpecs,\n execute,\n companions,\n );\n\n if (!isSimpleSubquery(subquery, tableSpecs)) {\n // Return with the (possibly partially-resolved) subquery.\n if (subquery !== condition.related.subquery) {\n return {\n ...condition,\n related: {...condition.related, subquery},\n };\n }\n return condition;\n }\n\n const value = execute(subquery, childField);\n\n // Record the companion subquery AST so its rows are synced to the client.\n // The client rewrites scalar subqueries to EXISTS and needs those rows.\n companions.push({\n ast: subquery,\n childField,\n resolvedValue: value,\n });\n\n if (value === undefined || value === null) {\n // No rows or NULL value — both x = NULL and x != NULL are false in SQL\n return ALWAYS_FALSE;\n }\n\n const op = condition.op === 'EXISTS' ? '=' : 'IS NOT';\n return {\n type: 'simple',\n op,\n left: {type: 'column', name: parentField},\n right: {type: 'literal', value},\n } satisfies SimpleCondition;\n}\n\nconst ALWAYS_FALSE: SimpleCondition = {\n type: 'simple',\n op: '=',\n left: {type: 'literal', value: 1},\n right: {type: 'literal', value: 0},\n};\n\n/**\n * Checks if the subquery is guaranteed to return at most one deterministic row.\n *\n * This is true when all columns of at least one unique index on the subquery\n * table are equality-constrained by literal values in the WHERE clause\n * (using only AND conjunctions).\n */\nexport function isSimpleSubquery(\n subquery: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n): boolean {\n const spec = tableSpecs.get(subquery.table);\n if (!spec) {\n return false;\n }\n\n if (!subquery.where) {\n return false;\n }\n\n const constraints = extractLiteralEqualityConstraints(subquery.where);\n if (constraints.size === 0) {\n return false;\n }\n\n const {uniqueKeys} = spec.tableSpec;\n return uniqueKeys.some(key => key.every(col => constraints.has(col)));\n}\n\n/**\n * Extracts column=literal equality constraints from a condition tree,\n * only following AND conjunctions (not OR).\n */\nexport function extractLiteralEqualityConstraints(\n condition: Condition,\n): Map<string, LiteralValue> {\n const constraints = new Map<string, LiteralValue>();\n collectConstraints(condition, constraints);\n return constraints;\n}\n\nfunction collectConstraints(\n condition: Condition,\n constraints: Map<string, LiteralValue>,\n): void {\n switch (condition.type) {\n case 'simple':\n if (condition.op === '=') {\n if (\n condition.left.type === 'column' &&\n condition.right.type === 'literal'\n ) {\n constraints.set(condition.left.name, condition.right.value);\n }\n }\n break;\n case 'and':\n for (const c of condition.conditions) {\n collectConstraints(c, constraints);\n }\n break;\n // OR, correlatedSubquery (non-scalar) — don't contribute constraints\n default:\n break;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-cost-model.js","names":[],"sources":["../../../../zqlite/src/sqlite-cost-model.ts"],"sourcesContent":["import SQLite3Database from '@rocicorp/zero-sqlite3';\nimport {assert} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {Condition, Ordering} from '../../zero-protocol/src/ast.ts';\nimport type {SchemaValue} from '../../zero-types/src/schema-value.ts';\nimport type {\n ConnectionCostModel,\n CostModelCost,\n} from '../../zql/src/planner/planner-connection.ts';\nimport type {PlannerConstraint} from '../../zql/src/planner/planner-constraint.ts';\nimport type {Database, Statement} from './db.ts';\nimport {compileInline} from './internal/sql-inline.ts';\nimport {buildSelectQuery, type NoSubqueryCondition} from './query-builder.ts';\nimport {SQLiteStatFanout} from './sqlite-stat-fanout.ts';\n\n/**\n * Loop information returned by SQLite's scanstatus API.\n */\ninterface ScanstatusLoop {\n /** Unique identifier for this loop */\n selectId: number;\n /** Parent loop ID, or 0 for root loops */\n parentId: number;\n /** Estimated rows emitted per turn of parent loop */\n est: number;\n /** EXPLAIN text for this loop to determine: b-tree vs list subquery */\n explain: string;\n}\n\n/**\n * Creates a SQLite-based cost model for query planning.\n * Uses SQLite's scanstatus API to estimate query costs based on the actual\n * SQLite query planner's analysis.\n *\n * @param db Database instance for preparing statements\n * @param tableSpecs Map of table names to their table specs with ZQL schemas\n * @returns ConnectionCostModel function for use with the planner\n */\nexport function createSQLiteCostModel(\n db: Database,\n tableSpecs: Map<string, {zqlSpec: Record<string, SchemaValue>}>,\n): ConnectionCostModel {\n const fanoutEstimator = new SQLiteStatFanout(db);\n return (\n tableName: string,\n sort: Ordering,\n filters: Condition | undefined,\n constraint: PlannerConstraint | undefined,\n ): CostModelCost => {\n // Transform filters to remove correlated subqueries\n // The cost model can't handle correlated subqueries, so we estimate cost\n // without them. This is conservative - actual cost may be higher.\n const noSubqueryFilters = filters\n ? removeCorrelatedSubqueries(filters)\n : undefined;\n\n // Build the SQL query using the same logic as actual queries\n const {zqlSpec} = must(tableSpecs.get(tableName));\n\n const query = buildSelectQuery(\n tableName,\n zqlSpec,\n constraint,\n noSubqueryFilters,\n sort,\n undefined, // reverse is undefined here\n undefined, // start is undefined here\n );\n\n // Use compileInline to inline actual values into the SQL for cost estimation.\n // This allows SQLite's query planner to see real values and make better decisions\n // about index usage and query plans. This is safe here because it's only used for\n // cost estimation, not for executing user-facing queries (which use parameterized\n // queries via the standard compile() function).\n const sql = compileInline(query);\n\n // Prepare statement to get scanstatus information\n const stmt = db.prepare(sql);\n\n // Get scanstatus loops from the prepared statement\n const loops = getScanstatusLoops(stmt);\n\n // Scanstatus should always be available - if we get no loops, something is wrong\n assert(\n loops.length > 0,\n `Expected scanstatus to return at least one loop for query: ${sql}`,\n );\n\n const ret = estimateCost(loops, (columns: string[]) =>\n fanoutEstimator.getFanout(tableName, columns),\n );\n\n return ret;\n };\n}\n\n/**\n * Removes correlated subqueries from conditions.\n * The cost model estimates cost without correlated subqueries since\n * they can't be included in the scanstatus query.\n */\nfunction removeCorrelatedSubqueries(\n condition: Condition,\n): NoSubqueryCondition | undefined {\n switch (condition.type) {\n case 'correlatedSubquery':\n // Remove subqueries - we can't estimate their cost via scanstatus\n return undefined;\n case 'simple':\n return condition;\n case 'and': {\n const filtered = condition.conditions\n .map(c => removeCorrelatedSubqueries(c))\n .filter((c): c is NoSubqueryCondition => c !== undefined);\n if (filtered.length === 0) return undefined;\n if (filtered.length === 1) return filtered[0];\n return {type: 'and', conditions: filtered};\n }\n case 'or': {\n const filtered = condition.conditions\n .map(c => removeCorrelatedSubqueries(c))\n .filter((c): c is NoSubqueryCondition => c !== undefined);\n if (filtered.length === 0) return undefined;\n if (filtered.length === 1) return filtered[0];\n return {type: 'or', conditions: filtered};\n }\n }\n}\n\n/**\n * Gets scanstatus loop information from a prepared statement.\n * Iterates through all query elements and extracts loop statistics.\n *\n * Uses SQLITE_SCANSTAT_COMPLEX flag (1) to get all loops including sorting operations.\n *\n * @param stmt Prepared statement to get scanstatus from\n * @returns Array of loop information, or empty array if scanstatus unavailable\n */\nfunction getScanstatusLoops(stmt: Statement): ScanstatusLoop[] {\n const loops: ScanstatusLoop[] = [];\n\n // Iterate through query elements by incrementing idx until we get undefined\n // which indicates we've reached the end\n for (let idx = 0; ; idx++) {\n const selectId = stmt.scanStatus(\n idx,\n SQLite3Database.SQLITE_SCANSTAT_SELECTID,\n 1,\n );\n\n if (selectId === undefined) {\n break;\n }\n\n loops.push({\n selectId: must(selectId),\n parentId: must(\n stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_PARENTID, 1),\n ),\n explain: must(\n stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_EXPLAIN, 1),\n ),\n est: must(stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_EST, 1)),\n });\n }\n\n return loops.sort((a, b) => a.selectId - b.selectId);\n}\n\n/**\n * Estimates the cost of a query based on scanstats from sqlite3_stmt_scanstatus_v2\n */\nfunction estimateCost(\n scanstats: ScanstatusLoop[],\n fanout: CostModelCost['fanout'],\n): CostModelCost {\n // Sort by selectId to process in execution order\n const sorted = scanstats.toSorted((a, b) => a.selectId - b.selectId);\n\n let totalRows = 0;\n let totalCost = 0;\n\n // Identify if there are multiple top-level (parentId=0) operations\n // If so, the first is typically the scan, and subsequent ones are sorts\n const topLevelOps = sorted.filter(s => s.parentId === 0);\n\n // We only consider top level ops since ZQL queries are single-table when hitting SQLite.\n // We do have a nested op in the case of `WHERE x IN (:arg)` but it is negligible\n // assuming :arg is small.\n let firstLoop = true;\n for (const op of topLevelOps) {\n if (firstLoop) {\n // First top-level op is the main scan\n // and determines the total number of rows output.\n totalRows = op.est;\n firstLoop = false;\n } else {\n if (op.explain.includes('ORDER BY')) {\n totalCost += btreeCost(totalRows);\n }\n }\n }\n\n return {\n rows: totalRows,\n startupCost: totalCost,\n fanout,\n };\n}\n\nexport function btreeCost(rows: number): number {\n // B-Tree construction is ~O(n log n) so we estimate the cost as such.\n // We divide the cost by 10 because sorting in SQLite is ~10x faster\n // than bringing the data into JS and sorting there.\n return (rows * Math.log2(rows)) / 10;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAsCA,SAAgB,sBACd,IACA,YACqB;CACrB,MAAM,kBAAkB,IAAI,iBAAiB,EAAE;CAC/C,QACE,WACA,MACA,SACA,eACkB;EAIlB,MAAM,oBAAoB,UACtB,2BAA2B,OAAO,IAClC,KAAA;EAGJ,MAAM,EAAC,YAAW,KAAK,WAAW,IAAI,SAAS,CAAC;EAiBhD,MAAM,MAAM,cAfE,iBACZ,WACA,SACA,YACA,mBACA,MACA,KAAA,GACA,KAAA,CAQwB,CAAK;EAM/B,MAAM,QAAQ,mBAHD,GAAG,QAAQ,GAGS,CAAI;EAGrC,OACE,MAAM,SAAS,GACf,8DAA8D,KAChE;EAMA,OAJY,aAAa,QAAQ,YAC/B,gBAAgB,UAAU,WAAW,OAAO,CAGvC;CACT;AACF;;;;;;AAOA,SAAS,2BACP,WACiC;CACjC,QAAQ,UAAU,MAAlB;EACE,KAAK,sBAEH;EACF,KAAK,UACH,OAAO;EACT,KAAK,OAAO;GACV,MAAM,WAAW,UAAU,WACxB,KAAI,MAAK,2BAA2B,CAAC,CAAC,EACtC,QAAQ,MAAgC,MAAM,KAAA,CAAS;GAC1D,IAAI,SAAS,WAAW,GAAG,OAAO,KAAA;GAClC,IAAI,SAAS,WAAW,GAAG,OAAO,SAAS;GAC3C,OAAO;IAAC,MAAM;IAAO,YAAY;GAAQ;EAC3C;EACA,KAAK,MAAM;GACT,MAAM,WAAW,UAAU,WACxB,KAAI,MAAK,2BAA2B,CAAC,CAAC,EACtC,QAAQ,MAAgC,MAAM,KAAA,CAAS;GAC1D,IAAI,SAAS,WAAW,GAAG,OAAO,KAAA;GAClC,IAAI,SAAS,WAAW,GAAG,OAAO,SAAS;GAC3C,OAAO;IAAC,MAAM;IAAM,YAAY;GAAQ;EAC1C;CACF;AACF;;;;;;;;;;AAWA,SAAS,mBAAmB,MAAmC;CAC7D,MAAM,QAA0B,CAAC;CAIjC,KAAK,IAAI,MAAM,IAAK,OAAO;EACzB,MAAM,WAAW,KAAK,WACpB,KACA,gBAAgB,0BAChB,CACF;EAEA,IAAI,aAAa,KAAA,GACf;EAGF,MAAM,KAAK;GACT,UAAU,KAAK,QAAQ;GACvB,UAAU,KACR,KAAK,WAAW,KAAK,gBAAgB,0BAA0B,CAAC,CAClE;GACA,SAAS,KACP,KAAK,WAAW,KAAK,gBAAgB,yBAAyB,CAAC,CACjE;GACA,KAAK,KAAK,KAAK,WAAW,KAAK,gBAAgB,qBAAqB,CAAC,CAAC;EACxE,CAAC;CACH;CAEA,OAAO,MAAM,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACrD;;;;AAKA,SAAS,aACP,WACA,QACe;CAEf,MAAM,SAAS,UAAU,UAAU,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CAEnE,IAAI,YAAY;CAChB,IAAI,YAAY;CAIhB,MAAM,cAAc,OAAO,QAAO,MAAK,EAAE,aAAa,CAAC;CAKvD,IAAI,YAAY;CAChB,KAAK,MAAM,MAAM,aACf,IAAI,WAAW;EAGb,YAAY,GAAG;EACf,YAAY;CACd,OACE,IAAI,GAAG,QAAQ,SAAS,UAAU,GAChC,aAAa,UAAU,SAAS;CAKtC,OAAO;EACL,MAAM;EACN,aAAa;EACb;CACF;AACF;AAEA,SAAgB,UAAU,MAAsB;CAI9C,OAAQ,OAAO,KAAK,KAAK,IAAI,IAAK;AACpC"}
1
+ {"version":3,"file":"sqlite-cost-model.js","names":[],"sources":["../../../../zqlite/src/sqlite-cost-model.ts"],"sourcesContent":["import SQLite3Database from '@rocicorp/zero-sqlite3';\nimport {assert} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {Condition, Ordering} from '../../zero-protocol/src/ast.ts';\nimport type {SchemaValue} from '../../zero-types/src/schema-value.ts';\nimport type {\n ConnectionCostModel,\n CostModelCost,\n} from '../../zql/src/planner/planner-connection.ts';\nimport type {PlannerConstraint} from '../../zql/src/planner/planner-constraint.ts';\nimport type {Database, Statement} from './db.ts';\nimport {compileInline} from './internal/sql-inline.ts';\nimport {buildSelectQuery, type NoSubqueryCondition} from './query-builder.ts';\nimport {SQLiteStatFanout} from './sqlite-stat-fanout.ts';\n\n/**\n * Loop information returned by SQLite's scanstatus API.\n */\ninterface ScanstatusLoop {\n /** Unique identifier for this loop */\n selectId: number;\n /** Parent loop ID, or 0 for root loops */\n parentId: number;\n /** Estimated rows emitted per turn of parent loop */\n est: number;\n /** EXPLAIN text for this loop to determine: b-tree vs list subquery */\n explain: string;\n}\n\n/**\n * Creates a SQLite-based cost model for query planning.\n * Uses SQLite's scanstatus API to estimate query costs based on the actual\n * SQLite query planner's analysis.\n *\n * @param db Database instance for preparing statements\n * @param tableSpecs Map of table names to their table specs with ZQL schemas\n * @returns ConnectionCostModel function for use with the planner\n */\nexport function createSQLiteCostModel(\n db: Database,\n tableSpecs: Map<string, {zqlSpec: Record<string, SchemaValue>}>,\n): ConnectionCostModel {\n const fanoutEstimator = new SQLiteStatFanout(db);\n return (\n tableName: string,\n sort: Ordering,\n filters: Condition | undefined,\n constraint: PlannerConstraint | undefined,\n ): CostModelCost => {\n // Transform filters to remove correlated subqueries\n // The cost model can't handle correlated subqueries, so we estimate cost\n // without them. This is conservative - actual cost may be higher.\n const noSubqueryFilters = filters\n ? removeCorrelatedSubqueries(filters)\n : undefined;\n\n // Build the SQL query using the same logic as actual queries\n const {zqlSpec} = must(tableSpecs.get(tableName));\n\n const query = buildSelectQuery(\n tableName,\n zqlSpec,\n constraint,\n noSubqueryFilters,\n sort,\n undefined, // reverse is undefined here\n undefined, // start is undefined here\n );\n\n // Use compileInline to inline actual values into the SQL for cost estimation.\n // This allows SQLite's query planner to see real values and make better decisions\n // about index usage and query plans. This is safe here because it's only used for\n // cost estimation, not for executing user-facing queries (which use parameterized\n // queries via the standard compile() function).\n const sql = compileInline(query);\n\n // Prepare statement to get scanstatus information\n const stmt = db.prepare(sql);\n\n // Get scanstatus loops from the prepared statement\n const loops = getScanstatusLoops(stmt);\n\n // Scanstatus should always be available - if we get no loops, something is wrong\n assert(\n loops.length > 0,\n `Expected scanstatus to return at least one loop for query: ${sql}`,\n );\n\n const ret = estimateCost(loops, (columns: string[]) =>\n fanoutEstimator.getFanout(tableName, columns),\n );\n\n return ret;\n };\n}\n\n/**\n * Removes correlated subqueries from conditions.\n * The cost model estimates cost without correlated subqueries since\n * they can't be included in the scanstatus query.\n */\nfunction removeCorrelatedSubqueries(\n condition: Condition,\n): NoSubqueryCondition | undefined {\n switch (condition.type) {\n case 'correlatedSubquery':\n // Remove subqueries - we can't estimate their cost via scanstatus\n return undefined;\n case 'simple':\n return condition;\n case 'and': {\n const filtered = condition.conditions\n .map(c => removeCorrelatedSubqueries(c))\n .filter((c): c is NoSubqueryCondition => c !== undefined);\n if (filtered.length === 0) return undefined;\n if (filtered.length === 1) return filtered[0];\n return {type: 'and', conditions: filtered};\n }\n case 'or': {\n const filtered = condition.conditions\n .map(c => removeCorrelatedSubqueries(c))\n .filter((c): c is NoSubqueryCondition => c !== undefined);\n if (filtered.length === 0) return undefined;\n if (filtered.length === 1) return filtered[0];\n return {type: 'or', conditions: filtered};\n }\n }\n}\n\n/**\n * Gets scanstatus loop information from a prepared statement.\n * Iterates through all query elements and extracts loop statistics.\n *\n * Uses SQLITE_SCANSTAT_COMPLEX flag (1) to get all loops including sorting operations.\n *\n * @param stmt Prepared statement to get scanstatus from\n * @returns Array of loop information, or empty array if scanstatus unavailable\n */\nfunction getScanstatusLoops(stmt: Statement): ScanstatusLoop[] {\n const loops: ScanstatusLoop[] = [];\n\n // Iterate through query elements by incrementing idx until we get undefined\n // which indicates we've reached the end\n for (let idx = 0; ; idx++) {\n const selectId = stmt.scanStatus(\n idx,\n SQLite3Database.SQLITE_SCANSTAT_SELECTID,\n 1,\n );\n\n if (selectId === undefined) {\n break;\n }\n\n loops.push({\n selectId: must(selectId),\n parentId: must(\n stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_PARENTID, 1),\n ),\n explain: must(\n stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_EXPLAIN, 1),\n ),\n est: must(stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_EST, 1)),\n });\n }\n\n return loops.sort((a, b) => a.selectId - b.selectId);\n}\n\n/**\n * Estimates the cost of a query based on scanstats from sqlite3_stmt_scanstatus_v2\n */\nfunction estimateCost(\n scanstats: ScanstatusLoop[],\n fanout: CostModelCost['fanout'],\n): CostModelCost {\n // Sort by selectId to process in execution order\n const sorted = scanstats.toSorted((a, b) => a.selectId - b.selectId);\n\n let totalRows = 0;\n let totalCost = 0;\n\n // Identify if there are multiple top-level (parentId=0) operations\n // If so, the first is typically the scan, and subsequent ones are sorts\n const topLevelOps = sorted.filter(s => s.parentId === 0);\n\n // We only consider top level ops since ZQL queries are single-table when hitting SQLite.\n // We do have a nested op in the case of `WHERE x IN (:arg)` but it is negligible\n // assuming :arg is small.\n let firstLoop = true;\n for (const op of topLevelOps) {\n if (firstLoop) {\n // First top-level op is the main scan\n // and determines the total number of rows output.\n totalRows = op.est;\n firstLoop = false;\n } else {\n if (op.explain.includes('ORDER BY')) {\n totalCost += btreeCost(totalRows);\n }\n }\n }\n\n return {\n rows: totalRows,\n startupCost: totalCost,\n fanout,\n };\n}\n\nexport function btreeCost(rows: number): number {\n // B-Tree construction is ~O(n log n) so we estimate the cost as such.\n // We divide the cost by 10 because sorting in SQLite is ~10x faster\n // than bringing the data into JS and sorting there.\n return (rows * Math.log2(rows)) / 10;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAsCA,SAAgB,sBACd,IACA,YACqB;CACrB,MAAM,kBAAkB,IAAI,iBAAiB,GAAG;AAChD,SACE,WACA,MACA,SACA,eACkB;EAIlB,MAAM,oBAAoB,UACtB,2BAA2B,QAAQ,GACnC,KAAA;EAGJ,MAAM,EAAC,YAAW,KAAK,WAAW,IAAI,UAAU,CAAC;EAiBjD,MAAM,MAAM,cAfE,iBACZ,WACA,SACA,YACA,mBACA,MACA,KAAA,GACA,KAAA,EACD,CAO+B;EAMhC,MAAM,QAAQ,mBAHD,GAAG,QAAQ,IAAI,CAGU;AAGtC,SACE,MAAM,SAAS,GACf,8DAA8D,MAC/D;AAMD,SAJY,aAAa,QAAQ,YAC/B,gBAAgB,UAAU,WAAW,QAAQ,CAC9C;;;;;;;;AAWL,SAAS,2BACP,WACiC;AACjC,SAAQ,UAAU,MAAlB;EACE,KAAK,qBAEH;EACF,KAAK,SACH,QAAO;EACT,KAAK,OAAO;GACV,MAAM,WAAW,UAAU,WACxB,KAAI,MAAK,2BAA2B,EAAE,CAAC,CACvC,QAAQ,MAAgC,MAAM,KAAA,EAAU;AAC3D,OAAI,SAAS,WAAW,EAAG,QAAO,KAAA;AAClC,OAAI,SAAS,WAAW,EAAG,QAAO,SAAS;AAC3C,UAAO;IAAC,MAAM;IAAO,YAAY;IAAS;;EAE5C,KAAK,MAAM;GACT,MAAM,WAAW,UAAU,WACxB,KAAI,MAAK,2BAA2B,EAAE,CAAC,CACvC,QAAQ,MAAgC,MAAM,KAAA,EAAU;AAC3D,OAAI,SAAS,WAAW,EAAG,QAAO,KAAA;AAClC,OAAI,SAAS,WAAW,EAAG,QAAO,SAAS;AAC3C,UAAO;IAAC,MAAM;IAAM,YAAY;IAAS;;;;;;;;;;;;;AAc/C,SAAS,mBAAmB,MAAmC;CAC7D,MAAM,QAA0B,EAAE;AAIlC,MAAK,IAAI,MAAM,IAAK,OAAO;EACzB,MAAM,WAAW,KAAK,WACpB,KACA,gBAAgB,0BAChB,EACD;AAED,MAAI,aAAa,KAAA,EACf;AAGF,QAAM,KAAK;GACT,UAAU,KAAK,SAAS;GACxB,UAAU,KACR,KAAK,WAAW,KAAK,gBAAgB,0BAA0B,EAAE,CAClE;GACD,SAAS,KACP,KAAK,WAAW,KAAK,gBAAgB,yBAAyB,EAAE,CACjE;GACD,KAAK,KAAK,KAAK,WAAW,KAAK,gBAAgB,qBAAqB,EAAE,CAAC;GACxE,CAAC;;AAGJ,QAAO,MAAM,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;;;;;AAMtD,SAAS,aACP,WACA,QACe;CAEf,MAAM,SAAS,UAAU,UAAU,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;CAEpE,IAAI,YAAY;CAChB,IAAI,YAAY;CAIhB,MAAM,cAAc,OAAO,QAAO,MAAK,EAAE,aAAa,EAAE;CAKxD,IAAI,YAAY;AAChB,MAAK,MAAM,MAAM,YACf,KAAI,WAAW;AAGb,cAAY,GAAG;AACf,cAAY;YAER,GAAG,QAAQ,SAAS,WAAW,CACjC,cAAa,UAAU,UAAU;AAKvC,QAAO;EACL,MAAM;EACN,aAAa;EACb;EACD;;AAGH,SAAgB,UAAU,MAAsB;AAI9C,QAAQ,OAAO,KAAK,KAAK,KAAK,GAAI"}