@rocicorp/zero 1.12.0-canary.3 → 1.12.0-canary.4

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 (785) hide show
  1. package/out/_virtual/{_@oxc-project_runtime@0.130.0/helpers → _@oxc-project_runtime@0.154.0/helpers/esm}/usingCtx.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js +4 -3
  3. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  4. package/out/analyze-query/src/bin-analyze.js +5 -3
  5. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  6. package/out/analyze-query/src/bin-transform.js +3 -2
  7. package/out/analyze-query/src/bin-transform.js.map +1 -1
  8. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  9. package/out/ast-to-zql/src/bin.js +3 -2
  10. package/out/ast-to-zql/src/bin.js.map +1 -1
  11. package/out/ast-to-zql/src/format.js.map +1 -1
  12. package/out/datadog/src/datadog-log-sink.js +5 -4
  13. package/out/datadog/src/datadog-log-sink.js.map +1 -1
  14. package/out/otel/src/log-options.js.map +1 -1
  15. package/out/replicache/src/btree/node.js +13 -11
  16. package/out/replicache/src/btree/node.js.map +1 -1
  17. package/out/replicache/src/btree/read.js +4 -2
  18. package/out/replicache/src/btree/read.js.map +1 -1
  19. package/out/replicache/src/btree/splice.js.map +1 -1
  20. package/out/replicache/src/btree/write.js +9 -5
  21. package/out/replicache/src/btree/write.js.map +1 -1
  22. package/out/replicache/src/config.js +1 -2
  23. package/out/replicache/src/connection-loop.js.map +1 -1
  24. package/out/replicache/src/dag/chunk.js +1 -1
  25. package/out/replicache/src/dag/gc.js.map +1 -1
  26. package/out/replicache/src/dag/lazy-store.js +8 -7
  27. package/out/replicache/src/dag/lazy-store.js.map +1 -1
  28. package/out/replicache/src/dag/store-impl.js +1 -1
  29. package/out/replicache/src/dag/store-impl.js.map +1 -1
  30. package/out/replicache/src/dag/visitor.js.map +1 -1
  31. package/out/replicache/src/db/commit.js +5 -3
  32. package/out/replicache/src/db/commit.js.map +1 -1
  33. package/out/replicache/src/db/read.js +1 -1
  34. package/out/replicache/src/db/read.js.map +1 -1
  35. package/out/replicache/src/db/rebase.js.map +1 -1
  36. package/out/replicache/src/db/write.js +5 -2
  37. package/out/replicache/src/db/write.js.map +1 -1
  38. package/out/replicache/src/deleted-clients.js +3 -2
  39. package/out/replicache/src/deleted-clients.js.map +1 -1
  40. package/out/replicache/src/expo-sqlite.js +1 -2
  41. package/out/replicache/src/frozen-json.js +1 -1
  42. package/out/replicache/src/frozen-json.js.map +1 -1
  43. package/out/replicache/src/get-default-puller.js +1 -1
  44. package/out/replicache/src/hash.js +1 -1
  45. package/out/replicache/src/hash.js.map +1 -1
  46. package/out/replicache/src/impl.js +1 -2
  47. package/out/replicache/src/index-defs.js +5 -3
  48. package/out/replicache/src/index-defs.js.map +1 -1
  49. package/out/replicache/src/kv/expo-sqlite/store.js +1 -1
  50. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  51. package/out/replicache/src/kv/idb-store.js +1 -1
  52. package/out/replicache/src/kv/idb-store.js.map +1 -1
  53. package/out/replicache/src/kv/mem-fallback-store.js +1 -1
  54. package/out/replicache/src/kv/mem-fallback-store.js.map +1 -1
  55. package/out/replicache/src/kv/mem-store.js.map +1 -1
  56. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  57. package/out/replicache/src/kv/op-sqlite/types.js.map +1 -1
  58. package/out/replicache/src/kv/read-impl.js.map +1 -1
  59. package/out/replicache/src/kv/sqlite-store.js +1 -1
  60. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  61. package/out/replicache/src/kv/write-impl-base.js +4 -1
  62. package/out/replicache/src/kv/write-impl-base.js.map +1 -1
  63. package/out/replicache/src/kv/write-impl.js.map +1 -1
  64. package/out/replicache/src/log-options.js +2 -1
  65. package/out/replicache/src/log-options.js.map +1 -1
  66. package/out/replicache/src/make-idb-name.js +1 -1
  67. package/out/replicache/src/new-client-channel.js +9 -7
  68. package/out/replicache/src/new-client-channel.js.map +1 -1
  69. package/out/replicache/src/on-persist-channel.js +1 -1
  70. package/out/replicache/src/on-persist-channel.js.map +1 -1
  71. package/out/replicache/src/op-sqlite.js +1 -2
  72. package/out/replicache/src/pending-mutations.js +2 -1
  73. package/out/replicache/src/pending-mutations.js.map +1 -1
  74. package/out/replicache/src/persist/client-gc.js +2 -2
  75. package/out/replicache/src/persist/client-gc.js.map +1 -1
  76. package/out/replicache/src/persist/client-group-gc.js +2 -2
  77. package/out/replicache/src/persist/client-group-gc.js.map +1 -1
  78. package/out/replicache/src/persist/client-groups.js +1 -1
  79. package/out/replicache/src/persist/client-groups.js.map +1 -1
  80. package/out/replicache/src/persist/clients.js +1 -1
  81. package/out/replicache/src/persist/clients.js.map +1 -1
  82. package/out/replicache/src/persist/collect-idb-databases.js +5 -4
  83. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  84. package/out/replicache/src/persist/gather-mem-only-visitor.js.map +1 -1
  85. package/out/replicache/src/persist/gather-not-cached-visitor.js.map +1 -1
  86. package/out/replicache/src/persist/heartbeat.js +2 -2
  87. package/out/replicache/src/persist/heartbeat.js.map +1 -1
  88. package/out/replicache/src/persist/idb-databases-store.js +1 -1
  89. package/out/replicache/src/persist/idb-databases-store.js.map +1 -1
  90. package/out/replicache/src/persist/make-client-id.js +1 -1
  91. package/out/replicache/src/persist/make-client-id.js.map +1 -1
  92. package/out/replicache/src/persist/persist.js +3 -2
  93. package/out/replicache/src/persist/persist.js.map +1 -1
  94. package/out/replicache/src/persist/refresh.js +8 -5
  95. package/out/replicache/src/persist/refresh.js.map +1 -1
  96. package/out/replicache/src/process-scheduler.js.map +1 -1
  97. package/out/replicache/src/replicache-impl.js +3 -3
  98. package/out/replicache/src/replicache-impl.js.map +1 -1
  99. package/out/replicache/src/scan-iterator.js.map +1 -1
  100. package/out/replicache/src/scan-options.js +7 -6
  101. package/out/replicache/src/scan-options.js.map +1 -1
  102. package/out/replicache/src/sqlite.js +1 -2
  103. package/out/replicache/src/subscriptions.js +1 -1
  104. package/out/replicache/src/subscriptions.js.map +1 -1
  105. package/out/replicache/src/sync/diff.js +3 -1
  106. package/out/replicache/src/sync/diff.js.map +1 -1
  107. package/out/replicache/src/sync/ids.js.map +1 -1
  108. package/out/replicache/src/sync/patch.js +2 -4
  109. package/out/replicache/src/sync/patch.js.map +1 -1
  110. package/out/replicache/src/sync/pull.js +3 -1
  111. package/out/replicache/src/sync/pull.js.map +1 -1
  112. package/out/replicache/src/sync/push.js.map +1 -1
  113. package/out/replicache/src/sync/request-id.js +1 -1
  114. package/out/replicache/src/sync/request-id.js.map +1 -1
  115. package/out/replicache/src/transactions.js +1 -1
  116. package/out/replicache/src/transactions.js.map +1 -1
  117. package/out/shared/src/arrays.js +4 -1
  118. package/out/shared/src/arrays.js.map +1 -1
  119. package/out/shared/src/asserts.js +1 -1
  120. package/out/shared/src/bigint-json.d.ts +9 -0
  121. package/out/shared/src/bigint-json.d.ts.map +1 -1
  122. package/out/shared/src/bigint-json.js +27 -2
  123. package/out/shared/src/bigint-json.js.map +1 -1
  124. package/out/shared/src/broadcast-channel.js +1 -1
  125. package/out/shared/src/btree-set.js +11 -8
  126. package/out/shared/src/btree-set.js.map +1 -1
  127. package/out/shared/src/cache.js.map +1 -1
  128. package/out/shared/src/config.js +1 -1
  129. package/out/shared/src/custom-key-map.js.map +1 -1
  130. package/out/shared/src/custom-key-set.js.map +1 -1
  131. package/out/shared/src/deep-clone.js +1 -1
  132. package/out/shared/src/document-visible.js.map +1 -1
  133. package/out/shared/src/dotenv.js.map +1 -1
  134. package/out/shared/src/error.js +1 -1
  135. package/out/shared/src/error.js.map +1 -1
  136. package/out/shared/src/hash.js +2 -2
  137. package/out/shared/src/hash.js.map +1 -1
  138. package/out/shared/src/iterables.js.map +1 -1
  139. package/out/shared/src/json-schema.js.map +1 -1
  140. package/out/shared/src/json.js.map +1 -1
  141. package/out/shared/src/logging-test-utils.js +1 -1
  142. package/out/shared/src/logging.js +1 -1
  143. package/out/shared/src/logging.js.map +1 -1
  144. package/out/shared/src/navigator.js +1 -1
  145. package/out/shared/src/options.js +9 -12
  146. package/out/shared/src/options.js.map +1 -1
  147. package/out/shared/src/promise-race.js.map +1 -1
  148. package/out/shared/src/queue.js.map +1 -1
  149. package/out/shared/src/record-proxy.js.map +1 -1
  150. package/out/shared/src/ring-buffer.js.map +1 -1
  151. package/out/shared/src/size-of-value.js +7 -10
  152. package/out/shared/src/size-of-value.js.map +1 -1
  153. package/out/shared/src/sorted-entries.js.map +1 -1
  154. package/out/shared/src/tdigest-schema.js.map +1 -1
  155. package/out/shared/src/tdigest.js.map +1 -1
  156. package/out/shared/src/valita.js +1 -1
  157. package/out/shared/src/valita.js.map +1 -1
  158. package/out/z2s/src/compiler.js +2 -4
  159. package/out/z2s/src/compiler.js.map +1 -1
  160. package/out/z2s/src/sql.js +1 -1
  161. package/out/z2s/src/sql.js.map +1 -1
  162. package/out/zero/package.js +212 -189
  163. package/out/zero/package.js.map +1 -1
  164. package/out/zero/src/adapters/drizzle.js +1 -2
  165. package/out/zero/src/adapters/kysely.js +1 -2
  166. package/out/zero/src/adapters/pg.js +1 -2
  167. package/out/zero/src/adapters/postgresjs.js +1 -2
  168. package/out/zero/src/adapters/prisma.js +1 -2
  169. package/out/zero/src/analyze.js +1 -2
  170. package/out/zero/src/change-protocol/v0.js +1 -2
  171. package/out/zero/src/expo-sqlite.js +1 -2
  172. package/out/zero/src/op-sqlite.js +1 -2
  173. package/out/zero/src/react-native.js.map +1 -1
  174. package/out/zero/src/sqlite.js +1 -2
  175. package/out/zero/src/zero-cache-dev.js +10 -9
  176. package/out/zero/src/zero-cache-dev.js.map +1 -1
  177. package/out/zero/src/zero-out.js +2 -1
  178. package/out/zero/src/zero-out.js.map +1 -1
  179. package/out/zero-cache/src/auth/auth.js +1 -1
  180. package/out/zero-cache/src/auth/auth.js.map +1 -1
  181. package/out/zero-cache/src/auth/jwt.js +4 -1
  182. package/out/zero-cache/src/auth/jwt.js.map +1 -1
  183. package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
  184. package/out/zero-cache/src/auth/read-authorizer.js +3 -2
  185. package/out/zero-cache/src/auth/read-authorizer.js.map +1 -1
  186. package/out/zero-cache/src/auth/write-authorizer.js +5 -16
  187. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  188. package/out/zero-cache/src/config/network.js +1 -1
  189. package/out/zero-cache/src/config/network.js.map +1 -1
  190. package/out/zero-cache/src/config/server-context.js.map +1 -1
  191. package/out/zero-cache/src/config/zero-config.d.ts +5 -0
  192. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  193. package/out/zero-cache/src/config/zero-config.js +13 -2
  194. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  195. package/out/zero-cache/src/custom/fetch.js +1 -1
  196. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  197. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  198. package/out/zero-cache/src/db/create.js.map +1 -1
  199. package/out/zero-cache/src/db/index-predicate.js.map +1 -1
  200. package/out/zero-cache/src/db/lite-tables.d.ts +2 -2
  201. package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
  202. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  203. package/out/zero-cache/src/db/migration-lite.js +1 -1
  204. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  205. package/out/zero-cache/src/db/migration.js +5 -3
  206. package/out/zero-cache/src/db/migration.js.map +1 -1
  207. package/out/zero-cache/src/db/pg-copy-binary.js +4 -3
  208. package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -1
  209. package/out/zero-cache/src/db/pg-copy.js +0 -1
  210. package/out/zero-cache/src/db/pg-copy.js.map +1 -1
  211. package/out/zero-cache/src/db/pg-to-lite.js +1 -1
  212. package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
  213. package/out/zero-cache/src/db/run-transaction.js.map +1 -1
  214. package/out/zero-cache/src/db/specs.js +7 -5
  215. package/out/zero-cache/src/db/specs.js.map +1 -1
  216. package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
  217. package/out/zero-cache/src/db/statements.d.ts.map +1 -1
  218. package/out/zero-cache/src/db/statements.js +9 -2
  219. package/out/zero-cache/src/db/statements.js.map +1 -1
  220. package/out/zero-cache/src/db/transaction-pool.js +5 -3
  221. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  222. package/out/zero-cache/src/db/warmup.js.map +1 -1
  223. package/out/zero-cache/src/observability/events.js.map +1 -1
  224. package/out/zero-cache/src/observability/log-throttle.js.map +1 -1
  225. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  226. package/out/zero-cache/src/scripts/deploy-permissions.js +4 -2
  227. package/out/zero-cache/src/scripts/deploy-permissions.js.map +1 -1
  228. package/out/zero-cache/src/scripts/permissions.js.map +1 -1
  229. package/out/zero-cache/src/server/anonymous-otel-start.js +2 -1
  230. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  231. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  232. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  233. package/out/zero-cache/src/server/logging.js +1 -1
  234. package/out/zero-cache/src/server/logging.js.map +1 -1
  235. package/out/zero-cache/src/server/main.js.map +1 -1
  236. package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
  237. package/out/zero-cache/src/server/otel-start.js.map +1 -1
  238. package/out/zero-cache/src/server/reaper.js +2 -2
  239. package/out/zero-cache/src/server/reaper.js.map +1 -1
  240. package/out/zero-cache/src/server/replicator.js.map +1 -1
  241. package/out/zero-cache/src/server/runner/run-worker.js +2 -1
  242. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  243. package/out/zero-cache/src/server/runner/runtime.js +2 -1
  244. package/out/zero-cache/src/server/runner/runtime.js.map +1 -1
  245. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  246. package/out/zero-cache/src/server/shadow-syncer.js +1 -1
  247. package/out/zero-cache/src/server/shadow-syncer.js.map +1 -1
  248. package/out/zero-cache/src/server/slot-keeper.js.map +1 -1
  249. package/out/zero-cache/src/server/syncer-assigner.js.map +1 -1
  250. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  251. package/out/zero-cache/src/server/syncer.js +6 -1
  252. package/out/zero-cache/src/server/syncer.js.map +1 -1
  253. package/out/zero-cache/src/server/worker-dispatcher.js +1 -1
  254. package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
  255. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  256. package/out/zero-cache/src/services/analyze.js +1 -1
  257. package/out/zero-cache/src/services/analyze.js.map +1 -1
  258. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +1 -1
  259. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  260. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  261. package/out/zero-cache/src/services/change-source/common/init-cleanup.js.map +1 -1
  262. package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -1
  263. package/out/zero-cache/src/services/change-source/common/replica-schema.js +3 -3
  264. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  265. package/out/zero-cache/src/services/change-source/custom/change-source.js +3 -3
  266. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  267. package/out/zero-cache/src/services/change-source/pg/backfill-metadata.js.map +1 -1
  268. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +3 -3
  269. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  270. package/out/zero-cache/src/services/change-source/pg/change-source-init.js +1 -1
  271. package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
  272. package/out/zero-cache/src/services/change-source/pg/change-source.js +4 -6
  273. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  274. package/out/zero-cache/src/services/change-source/pg/decommission.js +2 -1
  275. package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
  276. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +5 -3
  277. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  278. package/out/zero-cache/src/services/change-source/pg/logical-replication/binary-reader.js.map +1 -1
  279. package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js.map +1 -1
  280. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +3 -2
  281. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  282. package/out/zero-cache/src/services/change-source/pg/lsn.js.map +1 -1
  283. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js +2 -2
  284. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js.map +1 -1
  285. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js +1 -1
  286. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js.map +1 -1
  287. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +3 -3
  288. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  289. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +1 -1
  290. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  291. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +13 -12
  292. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
  293. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  294. package/out/zero-cache/src/services/change-source/pg/schema/published.js +1 -1
  295. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  296. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js.map +1 -1
  297. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +9 -6
  298. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  299. package/out/zero-cache/src/services/change-source/pg/schema/validation.js +1 -1
  300. package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
  301. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +1 -1
  302. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -1
  303. package/out/zero-cache/src/services/change-source/protocol/current/control.js.map +1 -1
  304. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  305. package/out/zero-cache/src/services/change-source/protocol/current/downstream.js.map +1 -1
  306. package/out/zero-cache/src/services/change-source/protocol/current/status.js.map +1 -1
  307. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  308. package/out/zero-cache/src/services/change-source/protocol/current.js +1 -1
  309. package/out/zero-cache/src/services/change-source/protocol/mod.js +1 -2
  310. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -1
  311. package/out/zero-cache/src/services/change-streamer/backfill-state.js +1 -1
  312. package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -1
  313. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
  314. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  315. package/out/zero-cache/src/services/change-streamer/broadcast.js +1 -1
  316. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  317. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +1 -1
  318. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -1
  319. package/out/zero-cache/src/services/change-streamer/change-log-codec.js +2 -3
  320. package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -1
  321. package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +2 -2
  322. package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -1
  323. package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js.map +1 -1
  324. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +5 -3
  325. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  326. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +21 -21
  327. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  328. package/out/zero-cache/src/services/change-streamer/change-streamer.js +1 -1
  329. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  330. package/out/zero-cache/src/services/change-streamer/drain-tracker.js.map +1 -1
  331. package/out/zero-cache/src/services/change-streamer/forwarder.js +11 -10
  332. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  333. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js +1 -1
  334. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js.map +1 -1
  335. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js +3 -3
  336. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -1
  337. package/out/zero-cache/src/services/change-streamer/replica-poller.js +1 -1
  338. package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -1
  339. package/out/zero-cache/src/services/change-streamer/restore-admission.js +12 -1
  340. package/out/zero-cache/src/services/change-streamer/restore-admission.js.map +1 -1
  341. package/out/zero-cache/src/services/change-streamer/schema/init.js +1 -1
  342. package/out/zero-cache/src/services/change-streamer/schema/init.js.map +1 -1
  343. package/out/zero-cache/src/services/change-streamer/schema/tables.js +1 -1
  344. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  345. package/out/zero-cache/src/services/change-streamer/snapshot-message.js +1 -1
  346. package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -1
  347. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
  348. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +1 -1
  349. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
  350. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js +2 -2
  351. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js.map +1 -1
  352. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js.map +1 -1
  353. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -1
  354. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -1
  355. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +4 -6
  356. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -1
  357. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +1 -3
  358. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -1
  359. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  360. package/out/zero-cache/src/services/change-streamer/storer.js +2 -2
  361. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  362. package/out/zero-cache/src/services/change-streamer/subscribe.js +1 -1
  363. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
  364. package/out/zero-cache/src/services/change-streamer/subscriber.js +2 -2
  365. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  366. package/out/zero-cache/src/services/change-streamer/upstream-acker.js +0 -1
  367. package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -1
  368. package/out/zero-cache/src/services/heapz.js +2 -1
  369. package/out/zero-cache/src/services/heapz.js.map +1 -1
  370. package/out/zero-cache/src/services/http-service.js.map +1 -1
  371. package/out/zero-cache/src/services/life-cycle.js +1 -1
  372. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  373. package/out/zero-cache/src/services/limiter/sliding-window-limiter.js +4 -2
  374. package/out/zero-cache/src/services/limiter/sliding-window-limiter.js.map +1 -1
  375. package/out/zero-cache/src/services/litestream/commands.js +1 -1
  376. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  377. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +2 -2
  378. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -1
  379. package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -1
  380. package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
  381. package/out/zero-cache/src/services/litestream/restore-progress.js.map +1 -1
  382. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
  383. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  384. package/out/zero-cache/src/services/mutagen/pusher.js +3 -2
  385. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  386. package/out/zero-cache/src/services/plannerz.js +4 -3
  387. package/out/zero-cache/src/services/plannerz.js.map +1 -1
  388. package/out/zero-cache/src/services/profz.js +5 -3
  389. package/out/zero-cache/src/services/profz.js.map +1 -1
  390. package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
  391. package/out/zero-cache/src/services/replicator/change-log-db.js +3 -2
  392. package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
  393. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +1 -1
  394. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
  395. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  396. package/out/zero-cache/src/services/replicator/drain-handler.js.map +1 -1
  397. package/out/zero-cache/src/services/replicator/incremental-sync.js +1 -1
  398. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  399. package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
  400. package/out/zero-cache/src/services/replicator/replication-status.js +4 -3
  401. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  402. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  403. package/out/zero-cache/src/services/replicator/reporter/recorder.js +1 -1
  404. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  405. package/out/zero-cache/src/services/replicator/reporter/report-schema.js +1 -1
  406. package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -1
  407. package/out/zero-cache/src/services/replicator/schema/backfilling.js +1 -1
  408. package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
  409. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +19 -7
  410. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts.map +1 -1
  411. package/out/zero-cache/src/services/replicator/schema/change-log.js +31 -15
  412. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  413. package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
  414. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
  415. package/out/zero-cache/src/services/replicator/schema/replication-state.js +10 -7
  416. package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
  417. package/out/zero-cache/src/services/replicator/schema/table-metadata.js.map +1 -1
  418. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +1 -1
  419. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
  420. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js +1 -1
  421. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js.map +1 -1
  422. package/out/zero-cache/src/services/replicator/write-worker-client.js +1 -1
  423. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  424. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  425. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  426. package/out/zero-cache/src/services/runner.js.map +1 -1
  427. package/out/zero-cache/src/services/running-state.js.map +1 -1
  428. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
  429. package/out/zero-cache/src/services/statz.js +2 -1
  430. package/out/zero-cache/src/services/statz.js.map +1 -1
  431. package/out/zero-cache/src/services/view-syncer/active-users-gauge.js +2 -2
  432. package/out/zero-cache/src/services/view-syncer/active-users-gauge.js.map +1 -1
  433. package/out/zero-cache/src/services/view-syncer/advancement-metrics.js.map +1 -1
  434. package/out/zero-cache/src/services/view-syncer/batch-pacer.js.map +1 -1
  435. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
  436. package/out/zero-cache/src/services/view-syncer/client-handler.js +24 -3
  437. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  438. package/out/zero-cache/src/services/view-syncer/client-schema.d.ts +15 -2
  439. package/out/zero-cache/src/services/view-syncer/client-schema.d.ts.map +1 -1
  440. package/out/zero-cache/src/services/view-syncer/client-schema.js +46 -6
  441. package/out/zero-cache/src/services/view-syncer/client-schema.js.map +1 -1
  442. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  443. package/out/zero-cache/src/services/view-syncer/cvr-purger.js +2 -2
  444. package/out/zero-cache/src/services/view-syncer/cvr-purger.js.map +1 -1
  445. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  446. package/out/zero-cache/src/services/view-syncer/cvr-store.js +10 -5
  447. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  448. package/out/zero-cache/src/services/view-syncer/cvr.js +12 -4
  449. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  450. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +1 -1
  451. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -1
  452. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  453. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -1
  454. package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -1
  455. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +2 -2
  456. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -1
  457. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +5 -3
  458. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  459. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  460. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +8 -5
  461. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  462. package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -1
  463. package/out/zero-cache/src/services/view-syncer/push-cost.js.map +1 -1
  464. package/out/zero-cache/src/services/view-syncer/push-cutoff.js.map +1 -1
  465. package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
  466. package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -1
  467. package/out/zero-cache/src/services/view-syncer/query-stats.js +1 -1
  468. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -1
  469. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  470. package/out/zero-cache/src/services/view-syncer/schema/cvr.js +1 -1
  471. package/out/zero-cache/src/services/view-syncer/schema/types.js +1 -1
  472. package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
  473. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js +7 -1
  474. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js.map +1 -1
  475. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +8 -1
  476. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  477. package/out/zero-cache/src/services/view-syncer/snapshotter.js +6 -5
  478. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  479. package/out/zero-cache/src/services/view-syncer/ttl-clock.js.map +1 -1
  480. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  481. package/out/zero-cache/src/services/view-syncer/view-syncer.js +13 -9
  482. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  483. package/out/zero-cache/src/types/error-with-level.js.map +1 -1
  484. package/out/zero-cache/src/types/lexi-version.js.map +1 -1
  485. package/out/zero-cache/src/types/lite.js +1 -1
  486. package/out/zero-cache/src/types/lite.js.map +1 -1
  487. package/out/zero-cache/src/types/pg-data-type.js +1 -1
  488. package/out/zero-cache/src/types/pg-data-type.js.map +1 -1
  489. package/out/zero-cache/src/types/pg.js +6 -4
  490. package/out/zero-cache/src/types/pg.js.map +1 -1
  491. package/out/zero-cache/src/types/poke-chunk.js +2 -2
  492. package/out/zero-cache/src/types/poke-chunk.js.map +1 -1
  493. package/out/zero-cache/src/types/processes.js +1 -1
  494. package/out/zero-cache/src/types/processes.js.map +1 -1
  495. package/out/zero-cache/src/types/profiler.js.map +1 -1
  496. package/out/zero-cache/src/types/row-key.js.map +1 -1
  497. package/out/zero-cache/src/types/sql.js.map +1 -1
  498. package/out/zero-cache/src/types/streams.js +10 -9
  499. package/out/zero-cache/src/types/streams.js.map +1 -1
  500. package/out/zero-cache/src/types/strings.js.map +1 -1
  501. package/out/zero-cache/src/types/subscription.js.map +1 -1
  502. package/out/zero-cache/src/types/timeout.js +1 -1
  503. package/out/zero-cache/src/types/websocket-handoff.js +1 -1
  504. package/out/zero-cache/src/types/ws.js +1 -1
  505. package/out/zero-cache/src/workers/connection.js +5 -6
  506. package/out/zero-cache/src/workers/connection.js.map +1 -1
  507. package/out/zero-cache/src/workers/mutator.js.map +1 -1
  508. package/out/zero-cache/src/workers/replicator.js +5 -4
  509. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  510. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  511. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +9 -1
  512. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  513. package/out/zero-cache/src/workers/syncer.js +4 -4
  514. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  515. package/out/zero-client/src/client/active-clients-manager.js +1 -1
  516. package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
  517. package/out/zero-client/src/client/bindings.js +11 -11
  518. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  519. package/out/zero-client/src/client/connection.js +1 -1
  520. package/out/zero-client/src/client/connection.js.map +1 -1
  521. package/out/zero-client/src/client/context.js.map +1 -1
  522. package/out/zero-client/src/client/crud-impl.js +1 -1
  523. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  524. package/out/zero-client/src/client/crud.js +1 -1
  525. package/out/zero-client/src/client/crud.js.map +1 -1
  526. package/out/zero-client/src/client/custom.js +1 -1
  527. package/out/zero-client/src/client/custom.js.map +1 -1
  528. package/out/zero-client/src/client/delete-clients-manager.js.map +1 -1
  529. package/out/zero-client/src/client/enable-analytics.js +1 -1
  530. package/out/zero-client/src/client/enable-analytics.js.map +1 -1
  531. package/out/zero-client/src/client/error.js.map +1 -1
  532. package/out/zero-client/src/client/inspector/client-group.js.map +1 -1
  533. package/out/zero-client/src/client/inspector/client.js.map +1 -1
  534. package/out/zero-client/src/client/inspector/html-dialog-prompt.js +1 -1
  535. package/out/zero-client/src/client/inspector/html-dialog-prompt.js.map +1 -1
  536. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  537. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  538. package/out/zero-client/src/client/inspector/query.js.map +1 -1
  539. package/out/zero-client/src/client/ivm-branch.js +1 -3
  540. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  541. package/out/zero-client/src/client/keys.js +2 -1
  542. package/out/zero-client/src/client/keys.js.map +1 -1
  543. package/out/zero-client/src/client/log-options.js.map +1 -1
  544. package/out/zero-client/src/client/make-replicache-mutators.js +5 -3
  545. package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
  546. package/out/zero-client/src/client/metrics.js +3 -5
  547. package/out/zero-client/src/client/metrics.js.map +1 -1
  548. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  549. package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
  550. package/out/zero-client/src/client/options.js.map +1 -1
  551. package/out/zero-client/src/client/query-manager.js +2 -2
  552. package/out/zero-client/src/client/query-manager.js.map +1 -1
  553. package/out/zero-client/src/client/reload-error-handler.js +4 -3
  554. package/out/zero-client/src/client/reload-error-handler.js.map +1 -1
  555. package/out/zero-client/src/client/version.js +1 -1
  556. package/out/zero-client/src/client/zero-poke-handler.js +1 -1
  557. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  558. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  559. package/out/zero-client/src/client/zero.js +19 -11
  560. package/out/zero-client/src/client/zero.js.map +1 -1
  561. package/out/zero-client/src/mod.js +23 -23
  562. package/out/zero-client/src/util/nanoid.js.map +1 -1
  563. package/out/zero-permissions/src/permissions.js +3 -2
  564. package/out/zero-permissions/src/permissions.js.map +1 -1
  565. package/out/zero-pg/src/mod.js +9 -9
  566. package/out/zero-protocol/src/analyze-query-result.js +1 -1
  567. package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
  568. package/out/zero-protocol/src/application-error.js.map +1 -1
  569. package/out/zero-protocol/src/ast.js +19 -9
  570. package/out/zero-protocol/src/ast.js.map +1 -1
  571. package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
  572. package/out/zero-protocol/src/client-schema.js +1 -1
  573. package/out/zero-protocol/src/client-schema.js.map +1 -1
  574. package/out/zero-protocol/src/close-connection.js +1 -1
  575. package/out/zero-protocol/src/close-connection.js.map +1 -1
  576. package/out/zero-protocol/src/connect.js +1 -1
  577. package/out/zero-protocol/src/connect.js.map +1 -1
  578. package/out/zero-protocol/src/custom-queries.js +1 -1
  579. package/out/zero-protocol/src/custom-queries.js.map +1 -1
  580. package/out/zero-protocol/src/data.js +4 -2
  581. package/out/zero-protocol/src/data.js.map +1 -1
  582. package/out/zero-protocol/src/delete-clients.js.map +1 -1
  583. package/out/zero-protocol/src/down.d.ts +27 -27
  584. package/out/zero-protocol/src/down.js.map +1 -1
  585. package/out/zero-protocol/src/error.js.map +1 -1
  586. package/out/zero-protocol/src/inspect-down.d.ts +81 -81
  587. package/out/zero-protocol/src/inspect-down.js +1 -1
  588. package/out/zero-protocol/src/inspect-down.js.map +1 -1
  589. package/out/zero-protocol/src/inspect-up.js +1 -1
  590. package/out/zero-protocol/src/inspect-up.js.map +1 -1
  591. package/out/zero-protocol/src/mutate-server.js +1 -1
  592. package/out/zero-protocol/src/mutate-server.js.map +1 -1
  593. package/out/zero-protocol/src/mutation-id.js.map +1 -1
  594. package/out/zero-protocol/src/mutation.js +1 -1
  595. package/out/zero-protocol/src/mutation.js.map +1 -1
  596. package/out/zero-protocol/src/mutations-patch.js +1 -1
  597. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  598. package/out/zero-protocol/src/ping.js +1 -1
  599. package/out/zero-protocol/src/ping.js.map +1 -1
  600. package/out/zero-protocol/src/poke.d.ts +6 -6
  601. package/out/zero-protocol/src/poke.js +1 -1
  602. package/out/zero-protocol/src/poke.js.map +1 -1
  603. package/out/zero-protocol/src/pong.js +1 -1
  604. package/out/zero-protocol/src/pong.js.map +1 -1
  605. package/out/zero-protocol/src/primary-key.js.map +1 -1
  606. package/out/zero-protocol/src/protocol-version.js +1 -0
  607. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  608. package/out/zero-protocol/src/pull.js +1 -1
  609. package/out/zero-protocol/src/pull.js.map +1 -1
  610. package/out/zero-protocol/src/push.js +1 -1
  611. package/out/zero-protocol/src/push.js.map +1 -1
  612. package/out/zero-protocol/src/queries-patch.js +1 -1
  613. package/out/zero-protocol/src/queries-patch.js.map +1 -1
  614. package/out/zero-protocol/src/query-hash-visitor.js.map +1 -1
  615. package/out/zero-protocol/src/query-hash.js +2 -2
  616. package/out/zero-protocol/src/query-server.js +1 -1
  617. package/out/zero-protocol/src/query-server.js.map +1 -1
  618. package/out/zero-protocol/src/row-patch.js.map +1 -1
  619. package/out/zero-protocol/src/up.d.ts +60 -60
  620. package/out/zero-protocol/src/up.js.map +1 -1
  621. package/out/zero-protocol/src/update-auth.js.map +1 -1
  622. package/out/zero-protocol/src/version.js.map +1 -1
  623. package/out/zero-react/src/bindings.js +11 -11
  624. package/out/zero-react/src/mod.js +5 -5
  625. package/out/zero-react/src/use-query.js +1 -1
  626. package/out/zero-react/src/use-query.js.map +1 -1
  627. package/out/zero-react/src/zero-provider.d.ts +1 -1
  628. package/out/zero-react/src/zero-provider.d.ts.map +1 -1
  629. package/out/zero-react/src/zero-provider.js +2 -1
  630. package/out/zero-react/src/zero-provider.js.map +1 -1
  631. package/out/zero-react/src/zero.js +23 -23
  632. package/out/zero-schema/src/builder/schema-builder.js +3 -2
  633. package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
  634. package/out/zero-schema/src/builder/table-builder.js +1 -1
  635. package/out/zero-schema/src/builder/table-builder.js.map +1 -1
  636. package/out/zero-schema/src/compiled-permissions.js.map +1 -1
  637. package/out/zero-schema/src/name-mapper.js +7 -5
  638. package/out/zero-schema/src/name-mapper.js.map +1 -1
  639. package/out/zero-schema/src/schema-config.js +1 -1
  640. package/out/zero-schema/src/schema-config.js.map +1 -1
  641. package/out/zero-server/src/adapters/drizzle.js.map +1 -1
  642. package/out/zero-server/src/adapters/kysely.js.map +1 -1
  643. package/out/zero-server/src/adapters/pg.js.map +1 -1
  644. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  645. package/out/zero-server/src/adapters/prisma.js.map +1 -1
  646. package/out/zero-server/src/closable-db-transaction.js.map +1 -1
  647. package/out/zero-server/src/custom.js +1 -1
  648. package/out/zero-server/src/custom.js.map +1 -1
  649. package/out/zero-server/src/mod.js +8 -8
  650. package/out/zero-server/src/process-mutations.js.map +1 -1
  651. package/out/zero-server/src/push-processor.js.map +1 -1
  652. package/out/zero-server/src/queries/process-queries.js +2 -1
  653. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  654. package/out/zero-server/src/schema.js +7 -7
  655. package/out/zero-server/src/schema.js.map +1 -1
  656. package/out/zero-server/src/zql-database.js.map +1 -1
  657. package/out/zero-solid/src/bindings.js +11 -11
  658. package/out/zero-solid/src/mod.js +5 -5
  659. package/out/zero-solid/src/solid-view.js +1 -1
  660. package/out/zero-solid/src/solid-view.js.map +1 -1
  661. package/out/zero-solid/src/use-connection-state.js +2 -1
  662. package/out/zero-solid/src/use-connection-state.js.map +1 -1
  663. package/out/zero-solid/src/use-query.js.map +1 -1
  664. package/out/zero-solid/src/use-zero-online.js +2 -1
  665. package/out/zero-solid/src/use-zero-online.js.map +1 -1
  666. package/out/zero-solid/src/use-zero.js +3 -2
  667. package/out/zero-solid/src/use-zero.js.map +1 -1
  668. package/out/zero-solid/src/zero.js +23 -23
  669. package/out/zero-types/src/name-mapper.js.map +1 -1
  670. package/out/zql/src/builder/builder.js +15 -13
  671. package/out/zql/src/builder/builder.js.map +1 -1
  672. package/out/zql/src/builder/correlated-predicate-pushdown.js.map +1 -1
  673. package/out/zql/src/builder/debug-delegate.js.map +1 -1
  674. package/out/zql/src/builder/like.js +0 -1
  675. package/out/zql/src/builder/like.js.map +1 -1
  676. package/out/zql/src/ivm/array-view.js.map +1 -1
  677. package/out/zql/src/ivm/cap.js.map +1 -1
  678. package/out/zql/src/ivm/constraint.js +1 -1
  679. package/out/zql/src/ivm/constraint.js.map +1 -1
  680. package/out/zql/src/ivm/data.js.map +1 -1
  681. package/out/zql/src/ivm/deferred-input.js.map +1 -1
  682. package/out/zql/src/ivm/exists.js +20 -18
  683. package/out/zql/src/ivm/exists.js.map +1 -1
  684. package/out/zql/src/ivm/fan-in.js.map +1 -1
  685. package/out/zql/src/ivm/fan-out.js.map +1 -1
  686. package/out/zql/src/ivm/filter-operators.js +1 -1
  687. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  688. package/out/zql/src/ivm/filter-push.js.map +1 -1
  689. package/out/zql/src/ivm/filter.js.map +1 -1
  690. package/out/zql/src/ivm/flipped-join.js +1 -3
  691. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  692. package/out/zql/src/ivm/join-index-tap.js.map +1 -1
  693. package/out/zql/src/ivm/join-index.js.map +1 -1
  694. package/out/zql/src/ivm/join-utils.js +11 -13
  695. package/out/zql/src/ivm/join-utils.js.map +1 -1
  696. package/out/zql/src/ivm/join.js.map +1 -1
  697. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  698. package/out/zql/src/ivm/memory-source.js +26 -22
  699. package/out/zql/src/ivm/memory-source.js.map +1 -1
  700. package/out/zql/src/ivm/memory-storage.js.map +1 -1
  701. package/out/zql/src/ivm/operator.js +2 -2
  702. package/out/zql/src/ivm/push-accumulated.js +0 -1
  703. package/out/zql/src/ivm/push-accumulated.js.map +1 -1
  704. package/out/zql/src/ivm/skip-yields.js.map +1 -1
  705. package/out/zql/src/ivm/skip.js.map +1 -1
  706. package/out/zql/src/ivm/take-gate.js.map +1 -1
  707. package/out/zql/src/ivm/take.js +1 -1
  708. package/out/zql/src/ivm/take.js.map +1 -1
  709. package/out/zql/src/ivm/union-fan-in.js +1 -1
  710. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  711. package/out/zql/src/ivm/union-fan-out.js.map +1 -1
  712. package/out/zql/src/ivm/view-apply-change.js +1 -1
  713. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  714. package/out/zql/src/mutate/crud.js +3 -2
  715. package/out/zql/src/mutate/crud.js.map +1 -1
  716. package/out/zql/src/planner/planner-builder.js +1 -1
  717. package/out/zql/src/planner/planner-builder.js.map +1 -1
  718. package/out/zql/src/planner/planner-connection.js.map +1 -1
  719. package/out/zql/src/planner/planner-debug.js.map +1 -1
  720. package/out/zql/src/planner/planner-fan-in.js.map +1 -1
  721. package/out/zql/src/planner/planner-fan-out.js.map +1 -1
  722. package/out/zql/src/planner/planner-filter.js.map +1 -1
  723. package/out/zql/src/planner/planner-graph.js +1 -5
  724. package/out/zql/src/planner/planner-graph.js.map +1 -1
  725. package/out/zql/src/planner/planner-join.js +1 -1
  726. package/out/zql/src/planner/planner-join.js.map +1 -1
  727. package/out/zql/src/planner/planner-source.js.map +1 -1
  728. package/out/zql/src/planner/planner-terminus.js.map +1 -1
  729. package/out/zql/src/query/create-builder.js +2 -1
  730. package/out/zql/src/query/create-builder.js.map +1 -1
  731. package/out/zql/src/query/expression.js +1 -1
  732. package/out/zql/src/query/expression.js.map +1 -1
  733. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  734. package/out/zql/src/query/named.js +2 -1
  735. package/out/zql/src/query/named.js.map +1 -1
  736. package/out/zql/src/query/query-delegate-base.js +1 -1
  737. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  738. package/out/zql/src/query/query-impl.js +3 -2
  739. package/out/zql/src/query/query-impl.js.map +1 -1
  740. package/out/zql/src/query/query-registry.js +1 -1
  741. package/out/zql/src/query/query-registry.js.map +1 -1
  742. package/out/zql/src/query/query-transitions.js +1 -1
  743. package/out/zql/src/query/query-transitions.js.map +1 -1
  744. package/out/zql/src/query/runnable-query-impl.js +3 -2
  745. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  746. package/out/zql/src/query/static-query.js.map +1 -1
  747. package/out/zql/src/query/ttl.js +8 -8
  748. package/out/zql/src/query/ttl.js.map +1 -1
  749. package/out/zql/src/query/validate-input.js.map +1 -1
  750. package/out/zqlite/src/database-storage.d.ts +27 -8
  751. package/out/zqlite/src/database-storage.d.ts.map +1 -1
  752. package/out/zqlite/src/database-storage.js +190 -53
  753. package/out/zqlite/src/database-storage.js.map +1 -1
  754. package/out/zqlite/src/db.d.ts +47 -1
  755. package/out/zqlite/src/db.d.ts.map +1 -1
  756. package/out/zqlite/src/db.js +121 -21
  757. package/out/zqlite/src/db.js.map +1 -1
  758. package/out/zqlite/src/explain-queries.js.map +1 -1
  759. package/out/zqlite/src/fetch-sql.js.map +1 -1
  760. package/out/zqlite/src/index-usage.js.map +1 -1
  761. package/out/zqlite/src/internal/sql-inline.js.map +1 -1
  762. package/out/zqlite/src/internal/sql.js.map +1 -1
  763. package/out/zqlite/src/internal/statement-cache.d.ts +7 -0
  764. package/out/zqlite/src/internal/statement-cache.d.ts.map +1 -1
  765. package/out/zqlite/src/internal/statement-cache.js +11 -4
  766. package/out/zqlite/src/internal/statement-cache.js.map +1 -1
  767. package/out/zqlite/src/mod.js +5 -5
  768. package/out/zqlite/src/pending-delta.js.map +1 -1
  769. package/out/zqlite/src/query-builder.js +1 -1
  770. package/out/zqlite/src/query-builder.js.map +1 -1
  771. package/out/zqlite/src/query-delegate.js.map +1 -1
  772. package/out/zqlite/src/resolve-scalar-subqueries.js +2 -5
  773. package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
  774. package/out/zqlite/src/sqlite-cost-model.js +3 -2
  775. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  776. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  777. package/out/zqlite/src/table-source.d.ts +11 -0
  778. package/out/zqlite/src/table-source.d.ts.map +1 -1
  779. package/out/zqlite/src/table-source.js +62 -25
  780. package/out/zqlite/src/table-source.js.map +1 -1
  781. package/package.json +17 -15
  782. package/out/otel/src/maybe-time.d.ts +0 -3
  783. package/out/otel/src/maybe-time.d.ts.map +0 -1
  784. package/out/otel/src/maybe-time.js +0 -15
  785. package/out/otel/src/maybe-time.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"view-apply-change.js","names":[],"sources":["../../../../../zql/src/ivm/view-apply-change.ts"],"sourcesContent":["import {\n assert,\n assertNumber,\n unreachable,\n} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {assignProperty} from '../../../shared/src/objects.ts';\nimport type {Writable} from '../../../shared/src/writable.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport {type Comparator, type Node} from './data.ts';\nimport {skipYields} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Entry, Format} from './view.ts';\n\nexport const refCountSymbol = Symbol('rc');\nexport const idSymbol = Symbol('id');\n\ntype ReadonlyMetaEntry = Entry & {\n readonly [refCountSymbol]: number;\n readonly [idSymbol]?: string | undefined;\n};\n\ntype MutableMetaEntry = Writable<ReadonlyMetaEntry>;\n\n/**\n * MetaEntry is an Entry with internal tracking fields for reference counting\n * and stable identity. All entries in the view tree are MetaEntry instances,\n * created through makeNewMetaEntry().\n */\ntype MetaEntry<M extends Mutate> = M extends true\n ? MutableMetaEntry\n : ReadonlyMetaEntry;\n\ntype MutableArray<M extends Mutate, T> = M extends true ? T[] : readonly T[];\n\ntype MetaEntryList<M extends Mutate> = MutableArray<M, MetaEntry<M>>;\n\ntype MutableMetaEntryList = MutableMetaEntry[];\n\n/**\n * Node with eagerly-expanded relationships (arrays instead of generators).\n * Used when batching changes to capture source state at push time.\n */\nexport type ExpandedNode = {\n row: Row;\n relationships: Record<string, ExpandedNode[]>;\n};\n\n/**\n * A node for view changes. Can be either:\n * - A lazy Node with generator relationships (original behavior)\n * - An ExpandedNode with pre-computed relationship arrays (for batching)\n */\nexport type ViewNode = Node | ExpandedNode;\n\n/**\n * `applyChange` does not consume the `relationships` of `ChildChange#node`,\n * `EditChange#node` and `EditChange#oldNode`. The `ViewChange` type\n * documents and enforces this via the type system.\n */\nexport type ViewChange =\n | AddViewChange\n | RemoveViewChange\n | ChildViewChange\n | EditViewChange;\n\nexport type RowOnlyNode = {row: Row};\n\nexport type AddViewChange = {\n type: 'add';\n node: ViewNode;\n};\n\nexport type RemoveViewChange = {\n type: 'remove';\n node: ViewNode;\n};\n\ntype ChildViewChange = {\n type: 'child';\n node: RowOnlyNode;\n child: {\n relationshipName: string;\n change: ViewChange;\n };\n};\n\ntype EditViewChange = {\n type: 'edit';\n node: RowOnlyNode;\n oldNode: RowOnlyNode;\n};\n\n/**\n * This is a subset of WeakMap but restricted to what we need.\n * @deprecated Not used anymore. This will be removed in the future.\n */\nexport interface RefCountMap {\n get(entry: Entry): number | undefined;\n set(entry: Entry, refCount: number): void;\n delete(entry: Entry): boolean;\n}\n\n/**\n * Get child nodes from a relationship, handling both lazy (Node) and expanded (ExpandedNode).\n */\nfunction getChildNodes(\n node: ViewNode,\n relationship: string,\n): Iterable<ViewNode> {\n const children = node.relationships[relationship];\n if (Array.isArray(children)) {\n // ExpandedNode: already an array\n return children;\n }\n // Node: lazy generator function\n return skipYields(children());\n}\n\ntype Mutate = boolean;\ntype WithIDs = boolean;\n\n/**\n * Transaction-scoped copy-on-write.\n *\n * `applyChange` is immutable: it path-copies a new spine on every call so the\n * previously-emitted snapshot is never mutated. But within a single transaction\n * (a run of `push()` calls before `flush()`) only the final state is ever\n * observed by a listener, so the intermediate spines are pure allocation/GC\n * churn — making a K-change transaction O(K * spineSize).\n *\n * When the caller passes a WeakSet as the `mutate` argument, objects and arrays\n * that were created or cloned during the current transaction are recorded in it.\n * A helper that needs to modify such an object mutates it in place (it is not\n * yet observed) instead of cloning again. The first touch of a node still\n * copies-on-write (preserving the committed snapshot's references); subsequent\n * touches in the same transaction are in-place. This brings a K-change\n * transaction to O(spineSize + K) while keeping cross-transaction reference\n * stability.\n *\n * The set is module-scoped for the duration of a single (synchronous,\n * non-re-entrant) `applyChange` call and saved/restored around it.\n *\n * It is a WeakSet on purpose: it must not keep transiently-cloned arrays/entries\n * alive. A strong Set would extend their lifetime to the end of the transaction,\n * pushing large clones out of the young generation and adding GC pressure that\n * shows up as a regression on single-change transactions over wide lists. A\n * WeakSet gives the same identity test (\"created this transaction?\") while\n * letting dead clones be collected as cheaply as in the plain immutable path.\n */\nlet activeDirty: WeakSet<object> | undefined;\n\n/** True if `o` was created/cloned during the current transaction. */\nfunction owns(o: object): boolean {\n return activeDirty !== undefined && activeDirty.has(o);\n}\n\n/** Record `o` as owned by the current transaction (no-op outside a txn). */\nfunction track<T extends object>(o: T): T {\n activeDirty?.add(o);\n return o;\n}\n\n/**\n * Immutable view update. Returns new Entry on change, same Entry if unchanged.\n * Unchanged entries keep identity, enabling shallow comparison optimizations\n * in UI frameworks (React.memo, Solid's fine-grained reactivity, etc).\n *\n * Propagation: recurse DOWN to find target, copy objects on the way UP.\n * Siblings keep original refs. Only the ancestor path is copied.\n *\n * root {users:[A,B], items:[C,D,E]} --edit C--> root' {users:[A,B], items':[C',D,E]}\n * │ same ref │ │ │\n * └─────────────────┴── unchanged ──────────────┘ new array C' new, D/E same\n *\n * `mutate` selects the update strategy:\n * - `false` (default): fully immutable; every changed node is path-copied.\n * - `true`: mutate the tree in place (only safe when it is not yet observed,\n * e.g. during initial hydration).\n * - a `WeakSet`: transaction-scoped copy-on-write — copy on first touch, then\n * mutate in place for the rest of the transaction. See `activeDirty` above.\n */\nexport function applyChange(\n parentEntry: Entry,\n change: ViewChange,\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs = false,\n mutate: boolean | WeakSet<object> = false,\n): Entry {\n // A WeakSet selects copy-on-write; a boolean selects full mutate / immutable.\n const inPlace = mutate === true;\n const prevDirty = activeDirty;\n activeDirty = typeof mutate === 'object' ? mutate : undefined;\n try {\n return applyChangeInternal(\n parentEntry as MetaEntry<typeof inPlace>,\n change,\n schema,\n relationship,\n format,\n withIDs,\n inPlace,\n );\n } finally {\n activeDirty = prevDirty;\n }\n}\n\nexport function applyChangeInternal<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n change: ViewChange,\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs: WithIDs,\n mutate: M,\n): MetaEntry<M> {\n if (schema.isHidden) {\n switch (change.type) {\n case 'add':\n case 'remove': {\n let currentParent = parentEntry;\n for (const relationship of Object.keys(change.node.relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n for (const node of getChildNodes(change.node, relationship)) {\n currentParent = applyChangeInternal(\n currentParent,\n {type: change.type, node},\n childSchema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n }\n return currentParent;\n }\n case 'edit':\n // If hidden at this level it means that the hidden row was changed. If\n // the row was changed in such a way that it would change the\n // relationships then the edit would have been split into remove and\n // add.\n return parentEntry;\n case 'child': {\n const childSchema = must(\n schema.relationships[change.child.relationshipName],\n );\n return applyChangeInternal(\n parentEntry,\n change.child.change,\n childSchema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n default:\n unreachable(change);\n }\n }\n\n const {singular, relationships: childFormats} = format;\n switch (change.type) {\n // ADD: Insert row (rc=1) or increment refCount if duplicate.\n // RefCount tracks when the same row is reachable via multiple edges\n // within a relationship:\n //\n // issue.labels: [L1, L2] both point to same creator\n // │ │ │\n // ▼ ▼ ▼\n // label.creator ──────────► [User:rc=2] (one row, two refs)\n //\n // add(A) add(A) remove(A) remove(A)\n // ↓ ↓ ↓ ↓\n // [A:rc=1] → [A:rc=2] → [A:rc=1] → (deleted)\n case 'add': {\n if (singular) {\n const oldEntry = getOptionalSingularEntry(parentEntry, relationship);\n if (oldEntry !== undefined) {\n // Duplicate add: increment refCount\n assert(\n schema.compareRows(oldEntry, change.node.row) === 0,\n `Singular relationship '${relationship}' should not have multiple rows. You may need to declare this relationship with the \\`many\\` helper instead of the \\`one\\` helper in your schema.`,\n );\n\n const newEntry = incRefCount(mutate, oldEntry);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n // New row: create with rc=1, initialize nested relationships\n const newEntry = makeNewMetaEntry(\n change.node.row,\n schema,\n withIDs,\n 1,\n );\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n change.node,\n schema,\n childFormats,\n withIDs,\n );\n return setRelation(mutate, parentEntry, relationship, newEntry);\n }\n } else {\n // Plural: binary search for position, insert or increment refCount\n const view = getChildEntryList(parentEntry, relationship);\n const {newEntry, newView} = add(\n change.node.row,\n view,\n schema,\n withIDs,\n mutate,\n );\n\n if (newEntry) {\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n change.node,\n schema,\n childFormats,\n withIDs,\n );\n }\n return setRelation(mutate, parentEntry, relationship, newView);\n }\n }\n // REMOVE: Decrement refCount, physically remove when rc=0.\n case 'remove': {\n if (singular) {\n const oldEntry = getSingularEntry(parentEntry, relationship);\n const rc = oldEntry[refCountSymbol];\n if (rc === 1) {\n return setRelation(mutate, parentEntry, relationship, undefined);\n }\n const newEntry = decRefCount(mutate, oldEntry);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n const view = getChildEntryList(parentEntry, relationship);\n const newView = removeAndUpdateRefCount(\n view,\n change.node.row,\n schema.compareRows,\n mutate,\n );\n return setRelation(mutate, parentEntry, relationship, newView);\n }\n }\n // CHILD: Propagate nested change up to this level (leaf-to-root pattern).\n case 'child': {\n const childSchema = must(\n schema.relationships[change.child.relationshipName],\n );\n const childFormat = format.relationships[change.child.relationshipName];\n if (childFormat === undefined) {\n return parentEntry; // Relationship not in view format\n }\n\n if (singular) {\n const existing = getSingularEntry(parentEntry, relationship);\n const newExisting = applyChangeInternal(\n existing,\n change.child.change,\n childSchema,\n change.child.relationshipName,\n childFormat,\n withIDs,\n mutate,\n );\n // Preserve identity if child didn't change (enables shallow-compare optimizations).\n if (newExisting === existing) {\n return parentEntry;\n }\n return setRelation(mutate, parentEntry, relationship, newExisting);\n } else {\n // Find the target row in the sorted array via binary search.\n const view = getChildEntryList(parentEntry, relationship);\n const pos = binarySearch(view, change.node.row, schema.compareRows);\n assert(pos >= 0, 'node does not exist');\n const existing = view[pos];\n const newExisting = applyChangeInternal(\n existing,\n change.child.change,\n childSchema,\n change.child.relationshipName,\n childFormat,\n withIDs,\n mutate,\n );\n // Preserve identity if descendant didn't change.\n if (newExisting === existing) {\n return parentEntry;\n }\n // applyChangeInternal preserves MetaEntry structure when input is MetaEntry\n assertMetaEntry(newExisting);\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, pos, newExisting),\n );\n }\n }\n // EDIT: Update row fields. If sort key changes, row may move position.\n //\n // Position change with rc>1 (two query paths reach same row):\n //\n // Before: [A, B(rc=2), C, D] After: [A, B(rc=1), C, B'(rc=1), D]\n // │ │ │\n // path1 + path2 path1 ghost path2 moved\n //\n // Why ghost stays: path1 still expects B at old position.\n // B' appears where path2's sort order places it.\n case 'edit': {\n if (singular) {\n const existing = getSingularEntry(parentEntry, relationship);\n const newEntry = applyEdit(existing, change, schema, withIDs, mutate);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n const view = getChildEntryList(parentEntry, relationship);\n // Sort key changed: row may need to move\n if (schema.compareRows(change.oldNode.row, change.node.row) !== 0) {\n const oldPos = binarySearch(\n view,\n change.oldNode.row,\n schema.compareRows,\n );\n assert(oldPos >= 0, 'old node does not exist');\n const oldEntry = view[oldPos];\n const rawPos = binarySearch(\n view,\n change.node.row,\n schema.compareRows,\n );\n const found = rawPos >= 0;\n const pos = found ? rawPos : ~rawPos;\n // A special case:\n // when refCount is 1 (so the row is being moved without leaving a\n // placeholder behind), and the new pos is the same as the old, or\n // directly after the old (so after the remove of the old it would be\n // in the same pos): the row does not need to be moved, just edited.\n if (\n oldEntry[refCountSymbol] === 1 &&\n (pos === oldPos || pos - 1 === oldPos)\n ) {\n const newEntry = applyEdit(\n oldEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, oldPos, newEntry),\n );\n } else {\n // Move the row. If rc > 1, an edit will be received for each ref.\n // On first edit: move row, apply edit, set rc=1. Leave a copy at\n // old pos with decremented rc. As each edit arrives, old copy's rc\n // decrements and new pos rc increments. When copy rc hits 0, remove.\n const newRefCount = oldEntry[refCountSymbol] - 1;\n let newView: MutableMetaEntryList;\n let adjustedPos = pos;\n\n if (newRefCount === 0) {\n newView = removeAt(mutate, view, oldPos);\n adjustedPos = oldPos < pos ? pos - 1 : pos;\n } else {\n const oldEntryCopy = setRefCount(mutate, oldEntry, newRefCount);\n newView = arrayWith(mutate, view, oldPos, oldEntryCopy);\n }\n\n if (found) {\n // Merge with existing at new pos\n const existingEntry = newView[adjustedPos];\n const editedEntry = applyEdit(\n existingEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n const updatedEntry = setRefCount(\n mutate,\n editedEntry,\n existingEntry[refCountSymbol] + 1,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, newView, adjustedPos, updatedEntry),\n );\n } else {\n // Insert at new pos with rc=1\n const editedEntry = applyEdit(\n oldEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n const movedEntry = setRefCount(mutate, editedEntry, 1);\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n insertAt(mutate, newView, adjustedPos, movedEntry),\n );\n }\n }\n } else {\n // Sort key unchanged: edit in place\n const pos = binarySearch(\n view,\n change.oldNode.row,\n schema.compareRows,\n );\n assert(pos >= 0, 'node does not exist');\n const newEntry = applyEdit(\n view[pos],\n change,\n schema,\n withIDs,\n mutate,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, pos, newEntry),\n );\n }\n }\n }\n default:\n unreachable(change);\n }\n}\n\n/**\n * Batch apply multiple changes to an Entry tree.\n * For small batches or complex cases, falls back to sequential applyChange.\n * Future optimization: O(N + K) batch processing for large K.\n */\nexport function applyChanges(\n parentEntry: Entry,\n changes: ViewChange[],\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs: WithIDs = false,\n mutate: Mutate = false,\n): Entry {\n let result = parentEntry;\n for (const change of changes) {\n result = applyChange(\n result,\n change,\n schema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n return result;\n}\n\nfunction applyEdit<M extends Mutate>(\n existing: MetaEntry<M>,\n change: EditViewChange,\n schema: SourceSchema,\n withIDs: WithIDs,\n mutate: Mutate,\n): MetaEntry<M> {\n // In-place edit is safe when fully mutating or when `existing` was already\n // created/cloned in this transaction (copy-on-write). A primary-key change\n // always needs a fresh entry so identity tracks the new key.\n const canMutate = mutate || owns(existing);\n const newEntry: MutableMetaEntry =\n canMutate && schema.compareRows(change.oldNode.row, change.node.row) === 0\n ? Object.assign(existing, change.node.row)\n : track({...existing, ...change.node.row});\n\n if (withIDs) {\n return setProperty(\n mutate,\n newEntry,\n idSymbol,\n makeID(change.node.row, schema),\n );\n }\n return newEntry;\n}\n\n/**\n * Initialize child relationships on a newly-added entry by mutating it in place.\n *\n * New nodes don't exist in the view yet, so we can build in-place (no refs to preserve):\n *\n * New nodes don't exist in the view yet, so we can build in-place (no refs to preserve):\n *\n * Existing node edit (must path-copy): New node (can build in-place):\n *\n * view: [A, B, C] view: [A, B, _]\n * │ │\n * edit C.child add C with children\n * │ │\n * ▼ ▼\n * view': [A, B, C'] ◄─ path-copy view': [A, B, C{children:[...]}]\n * up the tree └─ built in-place\n *\n * Hidden schemas still use applyChange to collapse junction levels.\n */\nfunction initializeRelationshipsForNewEntryIfAny(\n entry: MutableMetaEntry,\n node: ViewNode,\n schema: SourceSchema,\n childFormats: Record<string, Format>,\n withIDs: WithIDs,\n): void {\n let result = entry;\n for (const relationship of Object.keys(node.relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n const childFormat = childFormats[relationship];\n if (childFormat === undefined) {\n continue;\n }\n\n // Hidden/singular: use applyChange to handle properly\n if (childSchema.isHidden || childFormat.singular) {\n const newView = childFormat.singular\n ? undefined\n : track([] as MutableMetaEntryList);\n result[relationship] = newView;\n\n for (const childNode of getChildNodes(node, relationship)) {\n applyChangeInternal(\n result,\n {type: 'add', node: childNode},\n childSchema,\n relationship,\n childFormat,\n withIDs,\n true, // this is a new entry, so we can mutate\n );\n }\n } else {\n // Plural non-hidden: build array in-place for efficiency\n const childArray: MutableMetaEntryList = track([]);\n\n for (const childNode of getChildNodes(node, relationship)) {\n const newEntry = makeNewMetaEntry(\n childNode.row,\n childSchema,\n withIDs,\n 1,\n );\n const rawPos = binarySearch(\n childArray,\n childNode.row,\n childSchema.compareRows,\n );\n\n if (rawPos >= 0) {\n childArray[rawPos][refCountSymbol]++;\n } else {\n childArray.splice(~rawPos, 0, newEntry);\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n childNode,\n childSchema,\n childFormat.relationships,\n withIDs,\n );\n }\n }\n\n result[relationship] = childArray;\n }\n }\n}\n\nfunction add<M extends Mutate>(\n row: Row,\n view: MetaEntryList<M>,\n schema: SourceSchema,\n withIDs: WithIDs,\n mutate: M,\n): {\n newEntry: MutableMetaEntry | undefined;\n newView: MutableMetaEntryList;\n} {\n const rawPos = binarySearch(view, row, schema.compareRows);\n\n if (rawPos >= 0) {\n const existing = view[rawPos];\n\n const updated = incRefCount(mutate, existing);\n return {\n newEntry: undefined,\n newView: arrayWith(mutate, view, rawPos, updated),\n };\n }\n const newEntry = makeNewMetaEntry(row, schema, withIDs, 1);\n return {newEntry, newView: insertAt(mutate, view, ~rawPos, newEntry)};\n}\n\nfunction insertAt<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n item: T,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[]).splice(index, 0, item);\n return array as T[];\n }\n return track(array.toSpliced(index, 0, item));\n}\n\nfunction removeAt<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[]).splice(index, 1);\n return array as T[];\n }\n return track(array.toSpliced(index, 1));\n}\n\nfunction removeAndUpdateRefCount<M extends Mutate>(\n view: MetaEntryList<M>,\n row: Row,\n compareRows: Comparator,\n mutate: M,\n): MutableMetaEntryList {\n const pos = binarySearch(view, row, compareRows);\n assert(pos >= 0, 'node does not exist');\n const oldEntry = view[pos];\n const rc = oldEntry[refCountSymbol];\n if (rc === 1) {\n return removeAt(mutate, view, pos);\n }\n const newEntry = decRefCount(mutate, oldEntry);\n return arrayWith(mutate, view, pos, newEntry);\n}\n\n/**\n * Binary search returning a number.\n * - If found at index `i`: returns `i` (>= 0).\n * - If not found, insertion point is `low`: returns `~low` (< 0).\n * Recover insertion point with `~result`.\n */\nfunction binarySearch(\n view: MetaEntryList<Mutate>,\n target: Row,\n comparator: Comparator,\n): number {\n let high = view.length - 1;\n if (high < 0) {\n return ~0;\n }\n\n // Probe the last entry before searching. Hydration feeds the view rows in\n // the query's sort order, so every insert belongs at the end and the plain\n // search spends log2(n) comparisons to rediscover that -- about eleven per\n // row for a view of a couple of thousand. Row comparison is the single\n // largest cost in hydration on Hermes, so collapsing those eleven to one is\n // worth the one extra comparison this costs when the row does land inside\n // the view, which is a single push rather than a bulk load.\n // MetaEntry has all Row props; comparator only reads string keys\n const last = comparator(view[high] as Row, target);\n if (last < 0) {\n return ~(high + 1);\n }\n if (last === 0) {\n return high;\n }\n\n let low = 0;\n high -= 1;\n while (low <= high) {\n const mid = (low + high) >>> 1;\n const comparison = comparator(view[mid] as Row, target);\n if (comparison < 0) {\n low = mid + 1;\n } else if (comparison > 0) {\n high = mid - 1;\n } else {\n return mid;\n }\n }\n return ~low;\n}\n\n/** Assert value is MetaEntry (has refCountSymbol). */\nfunction assertMetaEntry<M extends Mutate>(\n v: unknown,\n): asserts v is MetaEntry<M> {\n assertNumber((v as Partial<MetaEntry<M>>)[refCountSymbol]);\n}\n\n/** Get singular MetaEntry, throws if missing. */\nfunction getSingularEntry<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntry<M> {\n const entry = parentEntry[relationship];\n assert(entry !== undefined, 'node does not exist');\n assertMetaEntry(entry);\n return entry;\n}\n\n/** Get singular MetaEntry or undefined if not set. */\nfunction getOptionalSingularEntry<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntry<M> | undefined {\n const entry = parentEntry[relationship];\n if (entry === undefined) {\n return undefined;\n }\n assertMetaEntry(entry);\n return entry;\n}\n\n/** Get child array as MetaEntryList. */\nfunction getChildEntryList<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntryList<M> {\n const view = parentEntry[relationship];\n // `relationship` is a string key, so a name colliding with a column would\n // put a row value here; the message must not repeat it back.\n assert(Array.isArray(view), 'expected relationship array');\n return view as MetaEntryList<M>;\n}\n\n/** Create MetaEntry from row with given refCount. */\nfunction makeNewMetaEntry(\n row: Row,\n schema: SourceSchema,\n withIDs: WithIDs,\n rc: number,\n): MutableMetaEntry {\n // This creates a new MetaEntry from a Row. We never mutate Rows.\n if (withIDs) {\n return track({\n ...row,\n [refCountSymbol]: rc,\n [idSymbol]: makeID(row, schema),\n });\n }\n return track({\n ...row,\n [refCountSymbol]: rc,\n });\n}\n\nfunction makeID(row: Row, schema: SourceSchema) {\n // optimization for case of non-compound primary key\n if (schema.primaryKey.length === 1) {\n return JSON.stringify(row[schema.primaryKey[0]]);\n }\n return JSON.stringify(schema.primaryKey.map(k => row[k]));\n}\n\nfunction incRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n): MutableMetaEntry {\n return setRefCount(mutate, entry, entry[refCountSymbol] + 1);\n}\n\nfunction decRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n): MutableMetaEntry {\n return setRefCount(mutate, entry, entry[refCountSymbol] - 1);\n}\n\nfunction setRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n count: number,\n): MutableMetaEntry {\n if (mutate || owns(entry)) {\n (entry as MutableMetaEntry)[refCountSymbol] = count;\n return entry;\n }\n return track({\n ...entry,\n [refCountSymbol]: count,\n });\n}\n\nfunction arrayWith<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n value: T,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[])[index] = value;\n return array as T[];\n }\n return track(array.with(index, value));\n}\n\nfunction setProperty<\n M extends Mutate,\n K extends string | keyof MetaEntry<M>,\n V,\n>(\n mutate: M,\n parentEntry: MetaEntry<M>,\n key: K,\n value: V,\n): MutableMetaEntry & {[P in K]: V} {\n if (mutate || owns(parentEntry)) {\n assignProperty(parentEntry as {[P in K]: V}, key, value);\n return parentEntry as MutableMetaEntry & {[P in K]: V};\n }\n return track({\n ...parentEntry,\n [key]: value,\n }) as MutableMetaEntry & {[P in K]: V};\n}\n\nconst setRelation: <M extends Mutate>(\n mutate: M,\n parentEntry: MetaEntry<M>,\n relationship: string,\n value: Entry | Entry[] | undefined,\n) => MutableMetaEntry = setProperty;\n"],"mappings":"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"}
1
+ {"version":3,"file":"view-apply-change.js","names":[],"sources":["../../../../../zql/src/ivm/view-apply-change.ts"],"sourcesContent":["import {\n assert,\n assertNumber,\n unreachable,\n} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {assignProperty} from '../../../shared/src/objects.ts';\nimport type {Writable} from '../../../shared/src/writable.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport {type Comparator, type Node} from './data.ts';\nimport {skipYields} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Entry, Format} from './view.ts';\n\nexport const refCountSymbol = Symbol('rc');\nexport const idSymbol = Symbol('id');\n\ntype ReadonlyMetaEntry = Entry & {\n readonly [refCountSymbol]: number;\n readonly [idSymbol]?: string | undefined;\n};\n\ntype MutableMetaEntry = Writable<ReadonlyMetaEntry>;\n\n/**\n * MetaEntry is an Entry with internal tracking fields for reference counting\n * and stable identity. All entries in the view tree are MetaEntry instances,\n * created through makeNewMetaEntry().\n */\ntype MetaEntry<M extends Mutate> = M extends true\n ? MutableMetaEntry\n : ReadonlyMetaEntry;\n\ntype MutableArray<M extends Mutate, T> = M extends true ? T[] : readonly T[];\n\ntype MetaEntryList<M extends Mutate> = MutableArray<M, MetaEntry<M>>;\n\ntype MutableMetaEntryList = MutableMetaEntry[];\n\n/**\n * Node with eagerly-expanded relationships (arrays instead of generators).\n * Used when batching changes to capture source state at push time.\n */\nexport type ExpandedNode = {\n row: Row;\n relationships: Record<string, ExpandedNode[]>;\n};\n\n/**\n * A node for view changes. Can be either:\n * - A lazy Node with generator relationships (original behavior)\n * - An ExpandedNode with pre-computed relationship arrays (for batching)\n */\nexport type ViewNode = Node | ExpandedNode;\n\n/**\n * `applyChange` does not consume the `relationships` of `ChildChange#node`,\n * `EditChange#node` and `EditChange#oldNode`. The `ViewChange` type\n * documents and enforces this via the type system.\n */\nexport type ViewChange =\n | AddViewChange\n | RemoveViewChange\n | ChildViewChange\n | EditViewChange;\n\nexport type RowOnlyNode = {row: Row};\n\nexport type AddViewChange = {\n type: 'add';\n node: ViewNode;\n};\n\nexport type RemoveViewChange = {\n type: 'remove';\n node: ViewNode;\n};\n\ntype ChildViewChange = {\n type: 'child';\n node: RowOnlyNode;\n child: {\n relationshipName: string;\n change: ViewChange;\n };\n};\n\ntype EditViewChange = {\n type: 'edit';\n node: RowOnlyNode;\n oldNode: RowOnlyNode;\n};\n\n/**\n * This is a subset of WeakMap but restricted to what we need.\n * @deprecated Not used anymore. This will be removed in the future.\n */\nexport interface RefCountMap {\n get(entry: Entry): number | undefined;\n set(entry: Entry, refCount: number): void;\n delete(entry: Entry): boolean;\n}\n\n/**\n * Get child nodes from a relationship, handling both lazy (Node) and expanded (ExpandedNode).\n */\nfunction getChildNodes(\n node: ViewNode,\n relationship: string,\n): Iterable<ViewNode> {\n const children = node.relationships[relationship];\n if (Array.isArray(children)) {\n // ExpandedNode: already an array\n return children;\n }\n // Node: lazy generator function\n return skipYields(children());\n}\n\ntype Mutate = boolean;\ntype WithIDs = boolean;\n\n/**\n * Transaction-scoped copy-on-write.\n *\n * `applyChange` is immutable: it path-copies a new spine on every call so the\n * previously-emitted snapshot is never mutated. But within a single transaction\n * (a run of `push()` calls before `flush()`) only the final state is ever\n * observed by a listener, so the intermediate spines are pure allocation/GC\n * churn — making a K-change transaction O(K * spineSize).\n *\n * When the caller passes a WeakSet as the `mutate` argument, objects and arrays\n * that were created or cloned during the current transaction are recorded in it.\n * A helper that needs to modify such an object mutates it in place (it is not\n * yet observed) instead of cloning again. The first touch of a node still\n * copies-on-write (preserving the committed snapshot's references); subsequent\n * touches in the same transaction are in-place. This brings a K-change\n * transaction to O(spineSize + K) while keeping cross-transaction reference\n * stability.\n *\n * The set is module-scoped for the duration of a single (synchronous,\n * non-re-entrant) `applyChange` call and saved/restored around it.\n *\n * It is a WeakSet on purpose: it must not keep transiently-cloned arrays/entries\n * alive. A strong Set would extend their lifetime to the end of the transaction,\n * pushing large clones out of the young generation and adding GC pressure that\n * shows up as a regression on single-change transactions over wide lists. A\n * WeakSet gives the same identity test (\"created this transaction?\") while\n * letting dead clones be collected as cheaply as in the plain immutable path.\n */\nlet activeDirty: WeakSet<object> | undefined;\n\n/** True if `o` was created/cloned during the current transaction. */\nfunction owns(o: object): boolean {\n return activeDirty !== undefined && activeDirty.has(o);\n}\n\n/** Record `o` as owned by the current transaction (no-op outside a txn). */\nfunction track<T extends object>(o: T): T {\n activeDirty?.add(o);\n return o;\n}\n\n/**\n * Immutable view update. Returns new Entry on change, same Entry if unchanged.\n * Unchanged entries keep identity, enabling shallow comparison optimizations\n * in UI frameworks (React.memo, Solid's fine-grained reactivity, etc).\n *\n * Propagation: recurse DOWN to find target, copy objects on the way UP.\n * Siblings keep original refs. Only the ancestor path is copied.\n *\n * root {users:[A,B], items:[C,D,E]} --edit C--> root' {users:[A,B], items':[C',D,E]}\n * │ same ref │ │ │\n * └─────────────────┴── unchanged ──────────────┘ new array C' new, D/E same\n *\n * `mutate` selects the update strategy:\n * - `false` (default): fully immutable; every changed node is path-copied.\n * - `true`: mutate the tree in place (only safe when it is not yet observed,\n * e.g. during initial hydration).\n * - a `WeakSet`: transaction-scoped copy-on-write — copy on first touch, then\n * mutate in place for the rest of the transaction. See `activeDirty` above.\n */\nexport function applyChange(\n parentEntry: Entry,\n change: ViewChange,\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs = false,\n mutate: boolean | WeakSet<object> = false,\n): Entry {\n // A WeakSet selects copy-on-write; a boolean selects full mutate / immutable.\n const inPlace = mutate === true;\n const prevDirty = activeDirty;\n activeDirty = typeof mutate === 'object' ? mutate : undefined;\n try {\n return applyChangeInternal(\n parentEntry as MetaEntry<typeof inPlace>,\n change,\n schema,\n relationship,\n format,\n withIDs,\n inPlace,\n );\n } finally {\n activeDirty = prevDirty;\n }\n}\n\nexport function applyChangeInternal<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n change: ViewChange,\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs: WithIDs,\n mutate: M,\n): MetaEntry<M> {\n if (schema.isHidden) {\n switch (change.type) {\n case 'add':\n case 'remove': {\n let currentParent = parentEntry;\n for (const relationship of Object.keys(change.node.relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n for (const node of getChildNodes(change.node, relationship)) {\n currentParent = applyChangeInternal(\n currentParent,\n {type: change.type, node},\n childSchema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n }\n return currentParent;\n }\n case 'edit':\n // If hidden at this level it means that the hidden row was changed. If\n // the row was changed in such a way that it would change the\n // relationships then the edit would have been split into remove and\n // add.\n return parentEntry;\n case 'child': {\n const childSchema = must(\n schema.relationships[change.child.relationshipName],\n );\n return applyChangeInternal(\n parentEntry,\n change.child.change,\n childSchema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n default:\n unreachable(change);\n }\n }\n\n const {singular, relationships: childFormats} = format;\n switch (change.type) {\n // ADD: Insert row (rc=1) or increment refCount if duplicate.\n // RefCount tracks when the same row is reachable via multiple edges\n // within a relationship:\n //\n // issue.labels: [L1, L2] both point to same creator\n // │ │ │\n // ▼ ▼ ▼\n // label.creator ──────────► [User:rc=2] (one row, two refs)\n //\n // add(A) add(A) remove(A) remove(A)\n // ↓ ↓ ↓ ↓\n // [A:rc=1] → [A:rc=2] → [A:rc=1] → (deleted)\n case 'add': {\n if (singular) {\n const oldEntry = getOptionalSingularEntry(parentEntry, relationship);\n if (oldEntry !== undefined) {\n // Duplicate add: increment refCount\n assert(\n schema.compareRows(oldEntry, change.node.row) === 0,\n `Singular relationship '${relationship}' should not have multiple rows. You may need to declare this relationship with the \\`many\\` helper instead of the \\`one\\` helper in your schema.`,\n );\n\n const newEntry = incRefCount(mutate, oldEntry);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n // New row: create with rc=1, initialize nested relationships\n const newEntry = makeNewMetaEntry(\n change.node.row,\n schema,\n withIDs,\n 1,\n );\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n change.node,\n schema,\n childFormats,\n withIDs,\n );\n return setRelation(mutate, parentEntry, relationship, newEntry);\n }\n } else {\n // Plural: binary search for position, insert or increment refCount\n const view = getChildEntryList(parentEntry, relationship);\n const {newEntry, newView} = add(\n change.node.row,\n view,\n schema,\n withIDs,\n mutate,\n );\n\n if (newEntry) {\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n change.node,\n schema,\n childFormats,\n withIDs,\n );\n }\n return setRelation(mutate, parentEntry, relationship, newView);\n }\n }\n // REMOVE: Decrement refCount, physically remove when rc=0.\n case 'remove': {\n if (singular) {\n const oldEntry = getSingularEntry(parentEntry, relationship);\n const rc = oldEntry[refCountSymbol];\n if (rc === 1) {\n return setRelation(mutate, parentEntry, relationship, undefined);\n }\n const newEntry = decRefCount(mutate, oldEntry);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n const view = getChildEntryList(parentEntry, relationship);\n const newView = removeAndUpdateRefCount(\n view,\n change.node.row,\n schema.compareRows,\n mutate,\n );\n return setRelation(mutate, parentEntry, relationship, newView);\n }\n }\n // CHILD: Propagate nested change up to this level (leaf-to-root pattern).\n case 'child': {\n const childSchema = must(\n schema.relationships[change.child.relationshipName],\n );\n const childFormat = format.relationships[change.child.relationshipName];\n if (childFormat === undefined) {\n return parentEntry; // Relationship not in view format\n }\n\n if (singular) {\n const existing = getSingularEntry(parentEntry, relationship);\n const newExisting = applyChangeInternal(\n existing,\n change.child.change,\n childSchema,\n change.child.relationshipName,\n childFormat,\n withIDs,\n mutate,\n );\n // Preserve identity if child didn't change (enables shallow-compare optimizations).\n if (newExisting === existing) {\n return parentEntry;\n }\n return setRelation(mutate, parentEntry, relationship, newExisting);\n } else {\n // Find the target row in the sorted array via binary search.\n const view = getChildEntryList(parentEntry, relationship);\n const pos = binarySearch(view, change.node.row, schema.compareRows);\n assert(pos >= 0, 'node does not exist');\n const existing = view[pos];\n const newExisting = applyChangeInternal(\n existing,\n change.child.change,\n childSchema,\n change.child.relationshipName,\n childFormat,\n withIDs,\n mutate,\n );\n // Preserve identity if descendant didn't change.\n if (newExisting === existing) {\n return parentEntry;\n }\n // applyChangeInternal preserves MetaEntry structure when input is MetaEntry\n assertMetaEntry(newExisting);\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, pos, newExisting),\n );\n }\n }\n // EDIT: Update row fields. If sort key changes, row may move position.\n //\n // Position change with rc>1 (two query paths reach same row):\n //\n // Before: [A, B(rc=2), C, D] After: [A, B(rc=1), C, B'(rc=1), D]\n // │ │ │\n // path1 + path2 path1 ghost path2 moved\n //\n // Why ghost stays: path1 still expects B at old position.\n // B' appears where path2's sort order places it.\n case 'edit': {\n if (singular) {\n const existing = getSingularEntry(parentEntry, relationship);\n const newEntry = applyEdit(existing, change, schema, withIDs, mutate);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n const view = getChildEntryList(parentEntry, relationship);\n // Sort key changed: row may need to move\n if (schema.compareRows(change.oldNode.row, change.node.row) !== 0) {\n const oldPos = binarySearch(\n view,\n change.oldNode.row,\n schema.compareRows,\n );\n assert(oldPos >= 0, 'old node does not exist');\n const oldEntry = view[oldPos];\n const rawPos = binarySearch(\n view,\n change.node.row,\n schema.compareRows,\n );\n const found = rawPos >= 0;\n const pos = found ? rawPos : ~rawPos;\n // A special case:\n // when refCount is 1 (so the row is being moved without leaving a\n // placeholder behind), and the new pos is the same as the old, or\n // directly after the old (so after the remove of the old it would be\n // in the same pos): the row does not need to be moved, just edited.\n if (\n oldEntry[refCountSymbol] === 1 &&\n (pos === oldPos || pos - 1 === oldPos)\n ) {\n const newEntry = applyEdit(\n oldEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, oldPos, newEntry),\n );\n } else {\n // Move the row. If rc > 1, an edit will be received for each ref.\n // On first edit: move row, apply edit, set rc=1. Leave a copy at\n // old pos with decremented rc. As each edit arrives, old copy's rc\n // decrements and new pos rc increments. When copy rc hits 0, remove.\n const newRefCount = oldEntry[refCountSymbol] - 1;\n let newView: MutableMetaEntryList;\n let adjustedPos = pos;\n\n if (newRefCount === 0) {\n newView = removeAt(mutate, view, oldPos);\n adjustedPos = oldPos < pos ? pos - 1 : pos;\n } else {\n const oldEntryCopy = setRefCount(mutate, oldEntry, newRefCount);\n newView = arrayWith(mutate, view, oldPos, oldEntryCopy);\n }\n\n if (found) {\n // Merge with existing at new pos\n const existingEntry = newView[adjustedPos];\n const editedEntry = applyEdit(\n existingEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n const updatedEntry = setRefCount(\n mutate,\n editedEntry,\n existingEntry[refCountSymbol] + 1,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, newView, adjustedPos, updatedEntry),\n );\n } else {\n // Insert at new pos with rc=1\n const editedEntry = applyEdit(\n oldEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n const movedEntry = setRefCount(mutate, editedEntry, 1);\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n insertAt(mutate, newView, adjustedPos, movedEntry),\n );\n }\n }\n } else {\n // Sort key unchanged: edit in place\n const pos = binarySearch(\n view,\n change.oldNode.row,\n schema.compareRows,\n );\n assert(pos >= 0, 'node does not exist');\n const newEntry = applyEdit(\n view[pos],\n change,\n schema,\n withIDs,\n mutate,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, pos, newEntry),\n );\n }\n }\n }\n default:\n unreachable(change);\n }\n}\n\n/**\n * Batch apply multiple changes to an Entry tree.\n * For small batches or complex cases, falls back to sequential applyChange.\n * Future optimization: O(N + K) batch processing for large K.\n */\nexport function applyChanges(\n parentEntry: Entry,\n changes: ViewChange[],\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs: WithIDs = false,\n mutate: Mutate = false,\n): Entry {\n let result = parentEntry;\n for (const change of changes) {\n result = applyChange(\n result,\n change,\n schema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n return result;\n}\n\nfunction applyEdit<M extends Mutate>(\n existing: MetaEntry<M>,\n change: EditViewChange,\n schema: SourceSchema,\n withIDs: WithIDs,\n mutate: Mutate,\n): MetaEntry<M> {\n // In-place edit is safe when fully mutating or when `existing` was already\n // created/cloned in this transaction (copy-on-write). A primary-key change\n // always needs a fresh entry so identity tracks the new key.\n const canMutate = mutate || owns(existing);\n const newEntry: MutableMetaEntry =\n canMutate && schema.compareRows(change.oldNode.row, change.node.row) === 0\n ? Object.assign(existing, change.node.row)\n : track({...existing, ...change.node.row});\n\n if (withIDs) {\n return setProperty(\n mutate,\n newEntry,\n idSymbol,\n makeID(change.node.row, schema),\n );\n }\n return newEntry;\n}\n\n/**\n * Initialize child relationships on a newly-added entry by mutating it in place.\n *\n * New nodes don't exist in the view yet, so we can build in-place (no refs to preserve):\n *\n * New nodes don't exist in the view yet, so we can build in-place (no refs to preserve):\n *\n * Existing node edit (must path-copy): New node (can build in-place):\n *\n * view: [A, B, C] view: [A, B, _]\n * │ │\n * edit C.child add C with children\n * │ │\n * ▼ ▼\n * view': [A, B, C'] ◄─ path-copy view': [A, B, C{children:[...]}]\n * up the tree └─ built in-place\n *\n * Hidden schemas still use applyChange to collapse junction levels.\n */\nfunction initializeRelationshipsForNewEntryIfAny(\n entry: MutableMetaEntry,\n node: ViewNode,\n schema: SourceSchema,\n childFormats: Record<string, Format>,\n withIDs: WithIDs,\n): void {\n let result = entry;\n for (const relationship of Object.keys(node.relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n const childFormat = childFormats[relationship];\n if (childFormat === undefined) {\n continue;\n }\n\n // Hidden/singular: use applyChange to handle properly\n if (childSchema.isHidden || childFormat.singular) {\n const newView = childFormat.singular\n ? undefined\n : track([] as MutableMetaEntryList);\n result[relationship] = newView;\n\n for (const childNode of getChildNodes(node, relationship)) {\n applyChangeInternal(\n result,\n {type: 'add', node: childNode},\n childSchema,\n relationship,\n childFormat,\n withIDs,\n true, // this is a new entry, so we can mutate\n );\n }\n } else {\n // Plural non-hidden: build array in-place for efficiency\n const childArray: MutableMetaEntryList = track([]);\n\n for (const childNode of getChildNodes(node, relationship)) {\n const newEntry = makeNewMetaEntry(\n childNode.row,\n childSchema,\n withIDs,\n 1,\n );\n const rawPos = binarySearch(\n childArray,\n childNode.row,\n childSchema.compareRows,\n );\n\n if (rawPos >= 0) {\n childArray[rawPos][refCountSymbol]++;\n } else {\n childArray.splice(~rawPos, 0, newEntry);\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n childNode,\n childSchema,\n childFormat.relationships,\n withIDs,\n );\n }\n }\n\n result[relationship] = childArray;\n }\n }\n}\n\nfunction add<M extends Mutate>(\n row: Row,\n view: MetaEntryList<M>,\n schema: SourceSchema,\n withIDs: WithIDs,\n mutate: M,\n): {\n newEntry: MutableMetaEntry | undefined;\n newView: MutableMetaEntryList;\n} {\n const rawPos = binarySearch(view, row, schema.compareRows);\n\n if (rawPos >= 0) {\n const existing = view[rawPos];\n\n const updated = incRefCount(mutate, existing);\n return {\n newEntry: undefined,\n newView: arrayWith(mutate, view, rawPos, updated),\n };\n }\n const newEntry = makeNewMetaEntry(row, schema, withIDs, 1);\n return {newEntry, newView: insertAt(mutate, view, ~rawPos, newEntry)};\n}\n\nfunction insertAt<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n item: T,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[]).splice(index, 0, item);\n return array as T[];\n }\n return track(array.toSpliced(index, 0, item));\n}\n\nfunction removeAt<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[]).splice(index, 1);\n return array as T[];\n }\n return track(array.toSpliced(index, 1));\n}\n\nfunction removeAndUpdateRefCount<M extends Mutate>(\n view: MetaEntryList<M>,\n row: Row,\n compareRows: Comparator,\n mutate: M,\n): MutableMetaEntryList {\n const pos = binarySearch(view, row, compareRows);\n assert(pos >= 0, 'node does not exist');\n const oldEntry = view[pos];\n const rc = oldEntry[refCountSymbol];\n if (rc === 1) {\n return removeAt(mutate, view, pos);\n }\n const newEntry = decRefCount(mutate, oldEntry);\n return arrayWith(mutate, view, pos, newEntry);\n}\n\n/**\n * Binary search returning a number.\n * - If found at index `i`: returns `i` (>= 0).\n * - If not found, insertion point is `low`: returns `~low` (< 0).\n * Recover insertion point with `~result`.\n */\nfunction binarySearch(\n view: MetaEntryList<Mutate>,\n target: Row,\n comparator: Comparator,\n): number {\n let high = view.length - 1;\n if (high < 0) {\n return ~0;\n }\n\n // Probe the last entry before searching. Hydration feeds the view rows in\n // the query's sort order, so every insert belongs at the end and the plain\n // search spends log2(n) comparisons to rediscover that -- about eleven per\n // row for a view of a couple of thousand. Row comparison is the single\n // largest cost in hydration on Hermes, so collapsing those eleven to one is\n // worth the one extra comparison this costs when the row does land inside\n // the view, which is a single push rather than a bulk load.\n // MetaEntry has all Row props; comparator only reads string keys\n const last = comparator(view[high] as Row, target);\n if (last < 0) {\n return ~(high + 1);\n }\n if (last === 0) {\n return high;\n }\n\n let low = 0;\n high -= 1;\n while (low <= high) {\n const mid = (low + high) >>> 1;\n const comparison = comparator(view[mid] as Row, target);\n if (comparison < 0) {\n low = mid + 1;\n } else if (comparison > 0) {\n high = mid - 1;\n } else {\n return mid;\n }\n }\n return ~low;\n}\n\n/** Assert value is MetaEntry (has refCountSymbol). */\nfunction assertMetaEntry<M extends Mutate>(\n v: unknown,\n): asserts v is MetaEntry<M> {\n assertNumber((v as Partial<MetaEntry<M>>)[refCountSymbol]);\n}\n\n/** Get singular MetaEntry, throws if missing. */\nfunction getSingularEntry<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntry<M> {\n const entry = parentEntry[relationship];\n assert(entry !== undefined, 'node does not exist');\n assertMetaEntry(entry);\n return entry;\n}\n\n/** Get singular MetaEntry or undefined if not set. */\nfunction getOptionalSingularEntry<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntry<M> | undefined {\n const entry = parentEntry[relationship];\n if (entry === undefined) {\n return undefined;\n }\n assertMetaEntry(entry);\n return entry;\n}\n\n/** Get child array as MetaEntryList. */\nfunction getChildEntryList<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntryList<M> {\n const view = parentEntry[relationship];\n // `relationship` is a string key, so a name colliding with a column would\n // put a row value here; the message must not repeat it back.\n assert(Array.isArray(view), 'expected relationship array');\n return view as MetaEntryList<M>;\n}\n\n/** Create MetaEntry from row with given refCount. */\nfunction makeNewMetaEntry(\n row: Row,\n schema: SourceSchema,\n withIDs: WithIDs,\n rc: number,\n): MutableMetaEntry {\n // This creates a new MetaEntry from a Row. We never mutate Rows.\n if (withIDs) {\n return track({\n ...row,\n [refCountSymbol]: rc,\n [idSymbol]: makeID(row, schema),\n });\n }\n return track({\n ...row,\n [refCountSymbol]: rc,\n });\n}\n\nfunction makeID(row: Row, schema: SourceSchema) {\n // optimization for case of non-compound primary key\n if (schema.primaryKey.length === 1) {\n return JSON.stringify(row[schema.primaryKey[0]]);\n }\n return JSON.stringify(schema.primaryKey.map(k => row[k]));\n}\n\nfunction incRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n): MutableMetaEntry {\n return setRefCount(mutate, entry, entry[refCountSymbol] + 1);\n}\n\nfunction decRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n): MutableMetaEntry {\n return setRefCount(mutate, entry, entry[refCountSymbol] - 1);\n}\n\nfunction setRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n count: number,\n): MutableMetaEntry {\n if (mutate || owns(entry)) {\n (entry as MutableMetaEntry)[refCountSymbol] = count;\n return entry;\n }\n return track({\n ...entry,\n [refCountSymbol]: count,\n });\n}\n\nfunction arrayWith<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n value: T,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[])[index] = value;\n return array as T[];\n }\n return track(array.with(index, value));\n}\n\nfunction setProperty<\n M extends Mutate,\n K extends string | keyof MetaEntry<M>,\n V,\n>(\n mutate: M,\n parentEntry: MetaEntry<M>,\n key: K,\n value: V,\n): MutableMetaEntry & {[P in K]: V} {\n if (mutate || owns(parentEntry)) {\n assignProperty(parentEntry as {[P in K]: V}, key, value);\n return parentEntry as MutableMetaEntry & {[P in K]: V};\n }\n return track({\n ...parentEntry,\n [key]: value,\n }) as MutableMetaEntry & {[P in K]: V};\n}\n\nconst setRelation: <M extends Mutate>(\n mutate: M,\n parentEntry: MetaEntry<M>,\n relationship: string,\n value: Entry | Entry[] | undefined,\n) => MutableMetaEntry = setProperty;\n"],"mappings":"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"}
@@ -27,7 +27,8 @@ function makeCRUDMutate(schema, addSchemaCRUD, executor) {
27
27
  return mutate;
28
28
  }
29
29
  function makeTransactionMutate(schema, executor) {
30
- return recordProxy(mapValues(schema.tables, () => void 0), (_value, tableName) => makeTableCRUD(tableName, executor));
30
+ const target = mapValues(schema.tables, () => void 0);
31
+ return recordProxy(target, (_value, tableName) => makeTableCRUD(tableName, executor));
31
32
  }
32
33
  /**
33
34
  * Creates a TableCRUD object that delegates to the executor.
@@ -36,6 +37,6 @@ function makeTableCRUD(tableName, executor) {
36
37
  return Object.fromEntries(CRUD_KINDS.map((kind) => [kind, (value) => executor(tableName, kind, value)]));
37
38
  }
38
39
  //#endregion
39
- export { makeCRUDMutate, makeTransactionMutate };
40
+ export { CRUD_KINDS, makeCRUDMutate, makeTransactionMutate };
40
41
 
41
42
  //# sourceMappingURL=crud.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crud.js","names":[],"sources":["../../../../../zql/src/mutate/crud.ts"],"sourcesContent":["import type {Expand} from '../../../shared/src/expand.ts';\nimport {assignProperty, mapValues} from '../../../shared/src/objects.ts';\nimport {recordProxy} from '../../../shared/src/record-proxy.ts';\nimport type {SchemaValueToTSType} from '../../../zero-types/src/schema-value.ts';\nimport type {Schema, TableSchema} from '../../../zero-types/src/schema.ts';\nimport type {MutateCRUD} from './custom.ts';\n\nexport type SchemaCRUD<S extends Schema> = {\n [Table in keyof S['tables']]: TableCRUD<S['tables'][Table]>;\n};\n\nexport type TransactionMutate<S extends Schema> = SchemaCRUD<S>;\n\nexport type TableCRUD<S extends TableSchema> = {\n /**\n * Writes a row. If a row with the same primary key already exists this is a\n * no-op; the existing row is left unchanged. A collision on any other unique\n * constraint still throws. Non-primary-key fields that are 'optional' can be\n * omitted or set to `undefined`. Such fields will be assigned the value\n * `null` optimistically and then the default value as defined by the server.\n */\n insert: (value: InsertValue<S>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<S>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<S>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<S>) => Promise<void>;\n};\n\nexport type CRUDKind = keyof TableCRUD<TableSchema>;\n\nexport const CRUD_KINDS = ['insert', 'upsert', 'update', 'delete'] as const;\n\nexport type DeleteID<S extends TableSchema> = Expand<PrimaryKeyFields<S>>;\n\ntype PrimaryKeyFields<S extends TableSchema> = {\n [\n K in Extract<S['primaryKey'][number], keyof S['columns']>\n ]: SchemaValueToTSType<S['columns'][K]>;\n};\n\nexport type InsertValue<S extends TableSchema> = Expand<\n PrimaryKeyFields<S> & {\n [\n K in keyof S['columns'] as S['columns'][K] extends {optional: true}\n ? K\n : never\n ]?: SchemaValueToTSType<S['columns'][K]> | undefined;\n } & {\n [\n K in keyof S['columns'] as S['columns'][K] extends {optional: true}\n ? never\n : K\n ]: SchemaValueToTSType<S['columns'][K]>;\n }\n>;\n\nexport type UpsertValue<S extends TableSchema> = InsertValue<S>;\n\nexport type UpdateValue<S extends TableSchema> = Expand<\n PrimaryKeyFields<S> & {\n [K in keyof S['columns']]?:\n | SchemaValueToTSType<S['columns'][K]>\n | undefined;\n }\n>;\n\n/**\n * This is the type of the generated mutate.<name>.<verb> function.\n */\nexport type TableMutator<TS extends TableSchema> = {\n /**\n * Writes a row. If a row with the same primary key already exists this is a\n * no-op; the existing row is left unchanged. A collision on any other unique\n * constraint still throws. Non-primary-key fields that are 'optional' can be\n * omitted or set to `undefined`. Such fields will be assigned the value\n * `null` optimistically and then the default value as defined by the server.\n */\n insert: (value: InsertValue<TS>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<TS>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<TS>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<TS>) => Promise<void>;\n};\n\n/**\n * A function that executes a CRUD operation.\n * Client and server provide different implementations.\n */\nexport type CRUDExecutor = (\n table: string,\n kind: CRUDKind,\n args: unknown,\n) => Promise<void>;\n\n/**\n * Creates a MutateCRUD function from a schema and executor.\n * This is the shared implementation used by both client and server.\n *\n * @param schema - The Zero schema\n * @param executor - A function that executes CRUD operations\n * @returns A MutateCRUD function that can be called with CRUDMutateRequest objects\n */\nexport function makeCRUDMutate<\n TSchema extends Schema,\n TAddSchemaCRUD extends boolean,\n>(\n schema: TSchema,\n addSchemaCRUD: TAddSchemaCRUD,\n executor: CRUDExecutor,\n): MutateCRUD<TSchema, TAddSchemaCRUD> {\n // Create a callable function that accepts CRUDMutateRequest\n const mutate = (request: AnyCRUDMutateRequest) => {\n const {table, kind, args} = request;\n return executor(table, kind, args);\n };\n\n // Only add table properties when enableLegacyMutators is true\n if (addSchemaCRUD) {\n // Add table names as keys so the proxy can discover them\n for (const tableName of Object.keys(schema.tables)) {\n assignProperty(\n mutate as unknown as Record<string, undefined>,\n tableName,\n undefined,\n );\n }\n\n // Wrap in proxy that lazily creates and caches table CRUD objects\n return recordProxy(\n mutate as unknown as Record<string, undefined>,\n (_value, tableName) => makeTableCRUD(tableName, executor),\n ) as unknown as MutateCRUD<TSchema, TAddSchemaCRUD>;\n }\n\n return mutate as MutateCRUD<TSchema, TAddSchemaCRUD>;\n}\n\nexport function makeTransactionMutate<TSchema extends Schema>(\n schema: TSchema,\n executor: CRUDExecutor,\n): TransactionMutate<TSchema> {\n const target = mapValues(schema.tables, () => undefined);\n\n return recordProxy(target, (_value, tableName) =>\n makeTableCRUD(tableName, executor),\n ) as SchemaCRUD<TSchema>;\n}\n\n/**\n * Creates a TableCRUD object that delegates to the executor.\n */\nfunction makeTableCRUD(\n tableName: string,\n executor: CRUDExecutor,\n): TableCRUD<TableSchema> {\n return Object.fromEntries(\n CRUD_KINDS.map(kind => [\n kind,\n (value: unknown) => executor(tableName, kind, value),\n ]),\n ) as TableCRUD<TableSchema>;\n}\n\nexport type CRUDMutator<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'] & string,\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = {\n (args: TArgs): CRUDMutateRequest<TSchema, TTable, TKind, TArgs>;\n\n /**\n * Type-only phantom property to surface mutator types in a covariant position.\n */\n ['~']: Expand<CRUDMutatorTypes<TSchema, TTable, TKind, TArgs>>;\n};\n\nexport type CRUDMutatorTypes<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'] & string,\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = 'CRUDMutator' & CRUDMutateRequest<TSchema, TTable, TKind, TArgs>;\n\nexport type CRUDMutateRequest<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'],\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = {\n readonly schema: TSchema;\n readonly table: TTable;\n readonly kind: TKind;\n readonly args: TArgs;\n};\n\n// oxlint-disable-next-line no-explicit-any\nexport type AnyCRUDMutateRequest = CRUDMutateRequest<any, any, CRUDKind, any>;\n"],"mappings":";;;AA+CA,IAAa,aAAa;CAAC;CAAU;CAAU;CAAU;AAAQ;;;;;;;;;AAyFjE,SAAgB,eAId,QACA,eACA,UACqC;CAErC,MAAM,UAAU,YAAkC;EAChD,MAAM,EAAC,OAAO,MAAM,SAAQ;EAC5B,OAAO,SAAS,OAAO,MAAM,IAAI;CACnC;CAGA,IAAI,eAAe;EAEjB,KAAK,MAAM,aAAa,OAAO,KAAK,OAAO,MAAM,GAC/C,eACE,QACA,WACA,KAAA,CACF;EAIF,OAAO,YACL,SACC,QAAQ,cAAc,cAAc,WAAW,QAAQ,CAC1D;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,sBACd,QACA,UAC4B;CAG5B,OAAO,YAFQ,UAAU,OAAO,cAAc,KAAA,CAE3B,IAAS,QAAQ,cAClC,cAAc,WAAW,QAAQ,CACnC;AACF;;;;AAKA,SAAS,cACP,WACA,UACwB;CACxB,OAAO,OAAO,YACZ,WAAW,KAAI,SAAQ,CACrB,OACC,UAAmB,SAAS,WAAW,MAAM,KAAK,CACrD,CAAC,CACH;AACF"}
1
+ {"version":3,"file":"crud.js","names":[],"sources":["../../../../../zql/src/mutate/crud.ts"],"sourcesContent":["import type {Expand} from '../../../shared/src/expand.ts';\nimport {assignProperty, mapValues} from '../../../shared/src/objects.ts';\nimport {recordProxy} from '../../../shared/src/record-proxy.ts';\nimport type {SchemaValueToTSType} from '../../../zero-types/src/schema-value.ts';\nimport type {Schema, TableSchema} from '../../../zero-types/src/schema.ts';\nimport type {MutateCRUD} from './custom.ts';\n\nexport type SchemaCRUD<S extends Schema> = {\n [Table in keyof S['tables']]: TableCRUD<S['tables'][Table]>;\n};\n\nexport type TransactionMutate<S extends Schema> = SchemaCRUD<S>;\n\nexport type TableCRUD<S extends TableSchema> = {\n /**\n * Writes a row. If a row with the same primary key already exists this is a\n * no-op; the existing row is left unchanged. A collision on any other unique\n * constraint still throws. Non-primary-key fields that are 'optional' can be\n * omitted or set to `undefined`. Such fields will be assigned the value\n * `null` optimistically and then the default value as defined by the server.\n */\n insert: (value: InsertValue<S>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<S>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<S>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<S>) => Promise<void>;\n};\n\nexport type CRUDKind = keyof TableCRUD<TableSchema>;\n\nexport const CRUD_KINDS = ['insert', 'upsert', 'update', 'delete'] as const;\n\nexport type DeleteID<S extends TableSchema> = Expand<PrimaryKeyFields<S>>;\n\ntype PrimaryKeyFields<S extends TableSchema> = {\n [\n K in Extract<S['primaryKey'][number], keyof S['columns']>\n ]: SchemaValueToTSType<S['columns'][K]>;\n};\n\nexport type InsertValue<S extends TableSchema> = Expand<\n PrimaryKeyFields<S> & {\n [\n K in keyof S['columns'] as S['columns'][K] extends {optional: true}\n ? K\n : never\n ]?: SchemaValueToTSType<S['columns'][K]> | undefined;\n } & {\n [\n K in keyof S['columns'] as S['columns'][K] extends {optional: true}\n ? never\n : K\n ]: SchemaValueToTSType<S['columns'][K]>;\n }\n>;\n\nexport type UpsertValue<S extends TableSchema> = InsertValue<S>;\n\nexport type UpdateValue<S extends TableSchema> = Expand<\n PrimaryKeyFields<S> & {\n [K in keyof S['columns']]?:\n | SchemaValueToTSType<S['columns'][K]>\n | undefined;\n }\n>;\n\n/**\n * This is the type of the generated mutate.<name>.<verb> function.\n */\nexport type TableMutator<TS extends TableSchema> = {\n /**\n * Writes a row. If a row with the same primary key already exists this is a\n * no-op; the existing row is left unchanged. A collision on any other unique\n * constraint still throws. Non-primary-key fields that are 'optional' can be\n * omitted or set to `undefined`. Such fields will be assigned the value\n * `null` optimistically and then the default value as defined by the server.\n */\n insert: (value: InsertValue<TS>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<TS>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<TS>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<TS>) => Promise<void>;\n};\n\n/**\n * A function that executes a CRUD operation.\n * Client and server provide different implementations.\n */\nexport type CRUDExecutor = (\n table: string,\n kind: CRUDKind,\n args: unknown,\n) => Promise<void>;\n\n/**\n * Creates a MutateCRUD function from a schema and executor.\n * This is the shared implementation used by both client and server.\n *\n * @param schema - The Zero schema\n * @param executor - A function that executes CRUD operations\n * @returns A MutateCRUD function that can be called with CRUDMutateRequest objects\n */\nexport function makeCRUDMutate<\n TSchema extends Schema,\n TAddSchemaCRUD extends boolean,\n>(\n schema: TSchema,\n addSchemaCRUD: TAddSchemaCRUD,\n executor: CRUDExecutor,\n): MutateCRUD<TSchema, TAddSchemaCRUD> {\n // Create a callable function that accepts CRUDMutateRequest\n const mutate = (request: AnyCRUDMutateRequest) => {\n const {table, kind, args} = request;\n return executor(table, kind, args);\n };\n\n // Only add table properties when enableLegacyMutators is true\n if (addSchemaCRUD) {\n // Add table names as keys so the proxy can discover them\n for (const tableName of Object.keys(schema.tables)) {\n assignProperty(\n mutate as unknown as Record<string, undefined>,\n tableName,\n undefined,\n );\n }\n\n // Wrap in proxy that lazily creates and caches table CRUD objects\n return recordProxy(\n mutate as unknown as Record<string, undefined>,\n (_value, tableName) => makeTableCRUD(tableName, executor),\n ) as unknown as MutateCRUD<TSchema, TAddSchemaCRUD>;\n }\n\n return mutate as MutateCRUD<TSchema, TAddSchemaCRUD>;\n}\n\nexport function makeTransactionMutate<TSchema extends Schema>(\n schema: TSchema,\n executor: CRUDExecutor,\n): TransactionMutate<TSchema> {\n const target = mapValues(schema.tables, () => undefined);\n\n return recordProxy(target, (_value, tableName) =>\n makeTableCRUD(tableName, executor),\n ) as SchemaCRUD<TSchema>;\n}\n\n/**\n * Creates a TableCRUD object that delegates to the executor.\n */\nfunction makeTableCRUD(\n tableName: string,\n executor: CRUDExecutor,\n): TableCRUD<TableSchema> {\n return Object.fromEntries(\n CRUD_KINDS.map(kind => [\n kind,\n (value: unknown) => executor(tableName, kind, value),\n ]),\n ) as TableCRUD<TableSchema>;\n}\n\nexport type CRUDMutator<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'] & string,\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = {\n (args: TArgs): CRUDMutateRequest<TSchema, TTable, TKind, TArgs>;\n\n /**\n * Type-only phantom property to surface mutator types in a covariant position.\n */\n ['~']: Expand<CRUDMutatorTypes<TSchema, TTable, TKind, TArgs>>;\n};\n\nexport type CRUDMutatorTypes<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'] & string,\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = 'CRUDMutator' & CRUDMutateRequest<TSchema, TTable, TKind, TArgs>;\n\nexport type CRUDMutateRequest<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'],\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = {\n readonly schema: TSchema;\n readonly table: TTable;\n readonly kind: TKind;\n readonly args: TArgs;\n};\n\n// oxlint-disable-next-line no-explicit-any\nexport type AnyCRUDMutateRequest = CRUDMutateRequest<any, any, CRUDKind, any>;\n"],"mappings":";;;AA+CA,IAAa,aAAa;CAAC;CAAU;CAAU;CAAU;AAAQ;;;;;;;;;AAyFjE,SAAgB,eAId,QACA,eACA,UACqC;CAErC,MAAM,UAAU,YAAkC;EAChD,MAAM,EAAC,OAAO,MAAM,SAAQ;EAC5B,OAAO,SAAS,OAAO,MAAM,IAAI;CACnC;CAGA,IAAI,eAAe;EAEjB,KAAK,MAAM,aAAa,OAAO,KAAK,OAAO,MAAM,GAC/C,eACE,QACA,WACA,KAAA,CACF;EAIF,OAAO,YACL,SACC,QAAQ,cAAc,cAAc,WAAW,QAAQ,CAC1D;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,sBACd,QACA,UAC4B;CAC5B,MAAM,SAAS,UAAU,OAAO,cAAc,KAAA,CAAS;CAEvD,OAAO,YAAY,SAAS,QAAQ,cAClC,cAAc,WAAW,QAAQ,CACnC;AACF;;;;AAKA,SAAS,cACP,WACA,UACwB;CACxB,OAAO,OAAO,YACZ,WAAW,KAAI,SAAQ,CACrB,OACC,UAAmB,SAAS,WAAW,MAAM,KAAK,CACrD,CAAC,CACH;AACF"}
@@ -188,6 +188,6 @@ function applyPlansToAST(ast, plans) {
188
188
  };
189
189
  }
190
190
  //#endregion
191
- export { planQuery };
191
+ export { applyPlansToAST, buildPlanGraph, planQuery };
192
192
 
193
193
  //# sourceMappingURL=planner-builder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"planner-builder.js","names":[],"sources":["../../../../../zql/src/planner/planner-builder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n Conjunction,\n CorrelatedSubqueryCondition,\n Disjunction,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport {planIdSymbol} from '../../../zero-protocol/src/ast.ts';\nimport {transformFilters} from '../builder/filter.ts';\nimport type {ConnectionCostModel} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {PlannerFanIn} from './planner-fan-in.ts';\nimport {PlannerFanOut} from './planner-fan-out.ts';\nimport {PlannerFilter} from './planner-filter.ts';\nimport {PlannerGraph} from './planner-graph.ts';\nimport {PlannerJoin} from './planner-join.ts';\nimport type {PlannerNode} from './planner-node.ts';\nimport {PlannerTerminus} from './planner-terminus.ts';\nimport {\n collectPlanWarnings,\n type PlanWarning,\n type PlanWarningThresholds,\n} from './planner-warnings.ts';\n\nfunction wireOutput(from: PlannerNode, to: PlannerNode): void {\n switch (from.kind) {\n case 'connection':\n case 'join':\n case 'fan-in':\n case 'filter':\n from.setOutput(to);\n break;\n case 'fan-out':\n from.addOutput(to);\n break;\n case 'terminus':\n assert(false, 'Terminus nodes cannot have outputs');\n }\n}\n\nexport type Plans = {\n plan: PlannerGraph;\n subPlans: {[key: string]: Plans};\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`).\n */\nexport function buildPlanGraph(\n ast: AST,\n model: ConnectionCostModel,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n pushed?: ReadonlySet<SimpleCondition>,\n): Plans {\n const graph = new PlannerGraph();\n let nextPlanId = 0;\n\n const source = graph.addSource(ast.table, model);\n const connection = source.connect(\n ast.orderBy ?? [],\n ast.where,\n isRoot,\n baseConstraints,\n ast.limit,\n pushed,\n );\n graph.connections.push(connection);\n\n let end: PlannerNode = connection;\n if (ast.where) {\n end = processCondition(\n ast.where,\n end,\n graph,\n model,\n ast.table,\n () => nextPlanId++,\n pushed,\n );\n }\n\n const terminus = new PlannerTerminus(end);\n wireOutput(end, terminus);\n graph.setTerminus(terminus);\n\n const subPlans: {[key: string]: Plans} = {};\n if (ast.related) {\n for (const csq of ast.related) {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const childConstraints = extractConstraint(\n csq.correlation.childField,\n csq.subquery.table,\n );\n subPlans[alias] = buildPlanGraph(\n csq.subquery,\n model,\n true,\n childConstraints,\n pushed,\n );\n }\n }\n\n return {plan: graph, subPlans};\n}\n\nfunction processCondition(\n condition: Condition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n switch (condition.type) {\n case 'simple':\n return input;\n case 'and':\n return processAnd(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'or':\n return processOr(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'correlatedSubquery':\n return processCorrelatedSubquery(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n}\n\nfunction processAnd(\n condition: Conjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n let end = input;\n for (const subCondition of condition.conditions) {\n end = processCondition(\n subCondition,\n end,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n return end;\n}\n\nfunction processOr(\n condition: Disjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n // Skip building fan structure when no branch contains a CSQ. The runtime\n // collapses such ORs to a single Filter node, so the planner has nothing\n // to choose between.\n const hasAnySubquery = condition.conditions.some(\n c => c.type === 'correlatedSubquery' || hasCorrelatedSubquery(c),\n );\n if (!hasAnySubquery) {\n return input;\n }\n\n const fanOut = new PlannerFanOut(input);\n graph.fanOuts.push(fanOut);\n wireOutput(input, fanOut);\n\n const branches: Exclude<PlannerNode, PlannerTerminus>[] = [];\n for (const subCondition of condition.conditions) {\n let branch: Exclude<PlannerNode, PlannerTerminus>;\n if (\n subCondition.type === 'correlatedSubquery' ||\n hasCorrelatedSubquery(subCondition)\n ) {\n branch = processCondition(\n subCondition,\n fanOut,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n } else {\n // Simple OR branch: wrap in a PlannerFilter so its filter can be\n // registered at the receiving connection on a per-branch basis when\n // the FanIn is in UFI mode.\n const transformed = transformFilters(subCondition).filters;\n const filter = new PlannerFilter(fanOut, transformed);\n graph.filters.push(filter);\n wireOutput(fanOut, filter);\n branch = filter;\n }\n branches.push(branch);\n fanOut.addOutput(branch);\n }\n\n const fanIn = new PlannerFanIn(branches);\n graph.fanIns.push(fanIn);\n for (const branch of branches) {\n wireOutput(branch, fanIn);\n }\n\n return fanIn;\n}\n\nfunction processCorrelatedSubquery(\n condition: CorrelatedSubqueryCondition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n const {related} = condition;\n const childTable = related.subquery.table;\n\n const childSource = graph.hasSource(childTable)\n ? graph.getSource(childTable)\n : graph.addSource(childTable, model);\n\n const childConnection = childSource.connect(\n related.subquery.orderBy ?? [],\n related.subquery.where,\n false,\n undefined, // no base constraints for EXISTS/NOT EXISTS\n condition.op === 'EXISTS' ? 1 : undefined,\n pushed,\n );\n graph.connections.push(childConnection);\n\n let childEnd: PlannerNode = childConnection;\n if (related.subquery.where) {\n childEnd = processCondition(\n related.subquery.where,\n childEnd,\n graph,\n model,\n childTable,\n getPlanId,\n pushed,\n );\n }\n\n const parentConstraint = extractConstraint(\n related.correlation.parentField,\n parentTable,\n );\n const childConstraint = extractConstraint(\n related.correlation.childField,\n childTable,\n );\n\n const planId = getPlanId();\n condition[planIdSymbol] = planId;\n\n // Determine flippability and initial type based on flip flag and operator\n const isNotExists = condition.op === 'NOT EXISTS';\n const manualFlip = condition.flip;\n\n let flippable: boolean;\n let initialType: 'semi' | 'flipped';\n\n if (isNotExists) {\n // NOT EXISTS joins can never be flipped\n flippable = false;\n initialType = 'semi';\n } else if (manualFlip === true) {\n // User explicitly requested flip=true: start flipped, don't allow planner to change\n flippable = false;\n initialType = 'flipped';\n } else if (manualFlip === false) {\n // User explicitly requested flip=false: start semi, don't allow planner to change\n flippable = false;\n initialType = 'semi';\n } else {\n // flip is undefined: planner can decide\n flippable = true;\n initialType = 'semi';\n }\n\n const join = new PlannerJoin(\n input,\n childEnd,\n parentConstraint,\n childConstraint,\n flippable,\n planId,\n initialType,\n );\n graph.joins.push(join);\n childConnection.setParentJoin(join);\n\n wireOutput(input, join);\n wireOutput(childEnd, join);\n\n return join;\n}\n\nfunction hasCorrelatedSubquery(condition: Condition): boolean {\n if (condition.type === 'correlatedSubquery') {\n return true;\n }\n if (condition.type === 'and' || condition.type === 'or') {\n return condition.conditions.some(hasCorrelatedSubquery);\n }\n // simple conditions don't contain correlated subqueries\n return false;\n}\n\nfunction extractConstraint(\n fields: readonly string[],\n _tableName: string,\n): PlannerConstraint {\n return Object.fromEntries(fields.map(field => [field, undefined]));\n}\n\nfunction planRecursively(\n plans: Plans,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n): void {\n for (const subPlan of Object.values(plans.subPlans)) {\n planRecursively(subPlan, planDebugger, lc);\n }\n plans.plan.plan(planDebugger, lc);\n}\n\n/**\n * Where the planner reports the {@link PlanWarning}s for the plan it chose.\n */\nexport type PlanWarningSink = {\n readonly thresholds: PlanWarningThresholds;\n /** Called once per planned query, and only if there are warnings. */\n report(warnings: readonly PlanWarning[]): void;\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`). The planner counts each one\n * only where it removes rows.\n * @param planWarnings Receives warnings about the chosen plan. Collecting them\n * costs extra cost model calls, so this is best left out when unused.\n */\nexport function planQuery(\n ast: AST,\n model: ConnectionCostModel,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n pushed?: ReadonlySet<SimpleCondition>,\n planWarnings?: PlanWarningSink,\n): AST {\n const plans = buildPlanGraph(ast, model, true, undefined, pushed);\n planRecursively(plans, planDebugger, lc);\n if (planWarnings) {\n const warnings = collectPlanWarnings(\n ast.table,\n plans,\n planWarnings.thresholds,\n );\n if (warnings.length > 0) {\n planWarnings.report(warnings);\n }\n }\n return applyPlansToAST(ast, plans);\n}\n\nfunction applyToCondition(\n condition: Condition,\n flippedIds: Set<number>,\n): Condition {\n if (condition.type === 'simple') {\n return condition;\n }\n\n if (condition.type === 'correlatedSubquery') {\n const planId = (condition as unknown as Record<symbol, number>)[\n planIdSymbol\n ];\n const shouldFlip = planId !== undefined && flippedIds.has(planId);\n\n return {\n ...condition,\n flip: shouldFlip,\n related: {\n ...condition.related,\n subquery: {\n ...condition.related.subquery,\n where: condition.related.subquery.where\n ? applyToCondition(condition.related.subquery.where, flippedIds)\n : undefined,\n },\n },\n };\n }\n\n return {\n ...condition,\n conditions: condition.conditions.map(c => applyToCondition(c, flippedIds)),\n };\n}\n\nexport function applyPlansToAST(ast: AST, plans: Plans): AST {\n const flippedIds = new Set<number>();\n for (const join of plans.plan.joins) {\n if (join.type === 'flipped' && join.planId !== undefined) {\n flippedIds.add(join.planId);\n }\n }\n\n return {\n ...ast,\n where: ast.where ? applyToCondition(ast.where, flippedIds) : undefined,\n related: ast.related?.map(csq => {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const subPlan = plans.subPlans[alias];\n return {\n ...csq,\n subquery: subPlan\n ? applyPlansToAST(csq.subquery, subPlan)\n : csq.subquery,\n };\n }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"planner-builder.js","names":[],"sources":["../../../../../zql/src/planner/planner-builder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n Conjunction,\n CorrelatedSubqueryCondition,\n Disjunction,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport {planIdSymbol} from '../../../zero-protocol/src/ast.ts';\nimport {transformFilters} from '../builder/filter.ts';\nimport type {ConnectionCostModel} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {PlannerFanIn} from './planner-fan-in.ts';\nimport {PlannerFanOut} from './planner-fan-out.ts';\nimport {PlannerFilter} from './planner-filter.ts';\nimport {PlannerGraph} from './planner-graph.ts';\nimport {PlannerJoin} from './planner-join.ts';\nimport type {PlannerNode} from './planner-node.ts';\nimport {PlannerTerminus} from './planner-terminus.ts';\nimport {\n collectPlanWarnings,\n type PlanWarning,\n type PlanWarningThresholds,\n} from './planner-warnings.ts';\n\nfunction wireOutput(from: PlannerNode, to: PlannerNode): void {\n switch (from.kind) {\n case 'connection':\n case 'join':\n case 'fan-in':\n case 'filter':\n from.setOutput(to);\n break;\n case 'fan-out':\n from.addOutput(to);\n break;\n case 'terminus':\n assert(false, 'Terminus nodes cannot have outputs');\n }\n}\n\nexport type Plans = {\n plan: PlannerGraph;\n subPlans: {[key: string]: Plans};\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`).\n */\nexport function buildPlanGraph(\n ast: AST,\n model: ConnectionCostModel,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n pushed?: ReadonlySet<SimpleCondition>,\n): Plans {\n const graph = new PlannerGraph();\n let nextPlanId = 0;\n\n const source = graph.addSource(ast.table, model);\n const connection = source.connect(\n ast.orderBy ?? [],\n ast.where,\n isRoot,\n baseConstraints,\n ast.limit,\n pushed,\n );\n graph.connections.push(connection);\n\n let end: PlannerNode = connection;\n if (ast.where) {\n end = processCondition(\n ast.where,\n end,\n graph,\n model,\n ast.table,\n () => nextPlanId++,\n pushed,\n );\n }\n\n const terminus = new PlannerTerminus(end);\n wireOutput(end, terminus);\n graph.setTerminus(terminus);\n\n const subPlans: {[key: string]: Plans} = {};\n if (ast.related) {\n for (const csq of ast.related) {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const childConstraints = extractConstraint(\n csq.correlation.childField,\n csq.subquery.table,\n );\n subPlans[alias] = buildPlanGraph(\n csq.subquery,\n model,\n true,\n childConstraints,\n pushed,\n );\n }\n }\n\n return {plan: graph, subPlans};\n}\n\nfunction processCondition(\n condition: Condition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n switch (condition.type) {\n case 'simple':\n return input;\n case 'and':\n return processAnd(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'or':\n return processOr(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'correlatedSubquery':\n return processCorrelatedSubquery(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n}\n\nfunction processAnd(\n condition: Conjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n let end = input;\n for (const subCondition of condition.conditions) {\n end = processCondition(\n subCondition,\n end,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n return end;\n}\n\nfunction processOr(\n condition: Disjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n // Skip building fan structure when no branch contains a CSQ. The runtime\n // collapses such ORs to a single Filter node, so the planner has nothing\n // to choose between.\n const hasAnySubquery = condition.conditions.some(\n c => c.type === 'correlatedSubquery' || hasCorrelatedSubquery(c),\n );\n if (!hasAnySubquery) {\n return input;\n }\n\n const fanOut = new PlannerFanOut(input);\n graph.fanOuts.push(fanOut);\n wireOutput(input, fanOut);\n\n const branches: Exclude<PlannerNode, PlannerTerminus>[] = [];\n for (const subCondition of condition.conditions) {\n let branch: Exclude<PlannerNode, PlannerTerminus>;\n if (\n subCondition.type === 'correlatedSubquery' ||\n hasCorrelatedSubquery(subCondition)\n ) {\n branch = processCondition(\n subCondition,\n fanOut,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n } else {\n // Simple OR branch: wrap in a PlannerFilter so its filter can be\n // registered at the receiving connection on a per-branch basis when\n // the FanIn is in UFI mode.\n const transformed = transformFilters(subCondition).filters;\n const filter = new PlannerFilter(fanOut, transformed);\n graph.filters.push(filter);\n wireOutput(fanOut, filter);\n branch = filter;\n }\n branches.push(branch);\n fanOut.addOutput(branch);\n }\n\n const fanIn = new PlannerFanIn(branches);\n graph.fanIns.push(fanIn);\n for (const branch of branches) {\n wireOutput(branch, fanIn);\n }\n\n return fanIn;\n}\n\nfunction processCorrelatedSubquery(\n condition: CorrelatedSubqueryCondition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n const {related} = condition;\n const childTable = related.subquery.table;\n\n const childSource = graph.hasSource(childTable)\n ? graph.getSource(childTable)\n : graph.addSource(childTable, model);\n\n const childConnection = childSource.connect(\n related.subquery.orderBy ?? [],\n related.subquery.where,\n false,\n undefined, // no base constraints for EXISTS/NOT EXISTS\n condition.op === 'EXISTS' ? 1 : undefined,\n pushed,\n );\n graph.connections.push(childConnection);\n\n let childEnd: PlannerNode = childConnection;\n if (related.subquery.where) {\n childEnd = processCondition(\n related.subquery.where,\n childEnd,\n graph,\n model,\n childTable,\n getPlanId,\n pushed,\n );\n }\n\n const parentConstraint = extractConstraint(\n related.correlation.parentField,\n parentTable,\n );\n const childConstraint = extractConstraint(\n related.correlation.childField,\n childTable,\n );\n\n const planId = getPlanId();\n condition[planIdSymbol] = planId;\n\n // Determine flippability and initial type based on flip flag and operator\n const isNotExists = condition.op === 'NOT EXISTS';\n const manualFlip = condition.flip;\n\n let flippable: boolean;\n let initialType: 'semi' | 'flipped';\n\n if (isNotExists) {\n // NOT EXISTS joins can never be flipped\n flippable = false;\n initialType = 'semi';\n } else if (manualFlip === true) {\n // User explicitly requested flip=true: start flipped, don't allow planner to change\n flippable = false;\n initialType = 'flipped';\n } else if (manualFlip === false) {\n // User explicitly requested flip=false: start semi, don't allow planner to change\n flippable = false;\n initialType = 'semi';\n } else {\n // flip is undefined: planner can decide\n flippable = true;\n initialType = 'semi';\n }\n\n const join = new PlannerJoin(\n input,\n childEnd,\n parentConstraint,\n childConstraint,\n flippable,\n planId,\n initialType,\n );\n graph.joins.push(join);\n childConnection.setParentJoin(join);\n\n wireOutput(input, join);\n wireOutput(childEnd, join);\n\n return join;\n}\n\nfunction hasCorrelatedSubquery(condition: Condition): boolean {\n if (condition.type === 'correlatedSubquery') {\n return true;\n }\n if (condition.type === 'and' || condition.type === 'or') {\n return condition.conditions.some(hasCorrelatedSubquery);\n }\n // simple conditions don't contain correlated subqueries\n return false;\n}\n\nfunction extractConstraint(\n fields: readonly string[],\n _tableName: string,\n): PlannerConstraint {\n return Object.fromEntries(fields.map(field => [field, undefined]));\n}\n\nfunction planRecursively(\n plans: Plans,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n): void {\n for (const subPlan of Object.values(plans.subPlans)) {\n planRecursively(subPlan, planDebugger, lc);\n }\n plans.plan.plan(planDebugger, lc);\n}\n\n/**\n * Where the planner reports the {@link PlanWarning}s for the plan it chose.\n */\nexport type PlanWarningSink = {\n readonly thresholds: PlanWarningThresholds;\n /** Called once per planned query, and only if there are warnings. */\n report(warnings: readonly PlanWarning[]): void;\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`). The planner counts each one\n * only where it removes rows.\n * @param planWarnings Receives warnings about the chosen plan. Collecting them\n * costs extra cost model calls, so this is best left out when unused.\n */\nexport function planQuery(\n ast: AST,\n model: ConnectionCostModel,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n pushed?: ReadonlySet<SimpleCondition>,\n planWarnings?: PlanWarningSink,\n): AST {\n const plans = buildPlanGraph(ast, model, true, undefined, pushed);\n planRecursively(plans, planDebugger, lc);\n if (planWarnings) {\n const warnings = collectPlanWarnings(\n ast.table,\n plans,\n planWarnings.thresholds,\n );\n if (warnings.length > 0) {\n planWarnings.report(warnings);\n }\n }\n return applyPlansToAST(ast, plans);\n}\n\nfunction applyToCondition(\n condition: Condition,\n flippedIds: Set<number>,\n): Condition {\n if (condition.type === 'simple') {\n return condition;\n }\n\n if (condition.type === 'correlatedSubquery') {\n const planId = (condition as unknown as Record<symbol, number>)[\n planIdSymbol\n ];\n const shouldFlip = planId !== undefined && flippedIds.has(planId);\n\n return {\n ...condition,\n flip: shouldFlip,\n related: {\n ...condition.related,\n subquery: {\n ...condition.related.subquery,\n where: condition.related.subquery.where\n ? applyToCondition(condition.related.subquery.where, flippedIds)\n : undefined,\n },\n },\n };\n }\n\n return {\n ...condition,\n conditions: condition.conditions.map(c => applyToCondition(c, flippedIds)),\n };\n}\n\nexport function applyPlansToAST(ast: AST, plans: Plans): AST {\n const flippedIds = new Set<number>();\n for (const join of plans.plan.joins) {\n if (join.type === 'flipped' && join.planId !== undefined) {\n flippedIds.add(join.planId);\n }\n }\n\n return {\n ...ast,\n where: ast.where ? applyToCondition(ast.where, flippedIds) : undefined,\n related: ast.related?.map(csq => {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const subPlan = plans.subPlans[alias];\n return {\n ...csq,\n subquery: subPlan\n ? applyPlansToAST(csq.subquery, subPlan)\n : csq.subquery,\n };\n }),\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"planner-connection.js","names":["#sort","#filters","#pushed","#unpushedFilters","#model","#baseConstraints","#baseLimit","#constraints","#perBranchFilters","#isRoot","#computeSelectivity","#output","#parentJoin","#clearCostCaches","#cachedConstraintCosts","#filtersFor","#cachedAccesses"],"sources":["../../../../../zql/src/planner/planner-connection.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {NoSubqueryCondition} from '../builder/filter.ts';\nimport {\n mergeConstraints,\n type PlannerConstraint,\n} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport type {PlannerJoin} from './planner-join.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\n\n/**\n * Represents a connection to a source (table scan).\n *\n * # Dual State Pattern\n * Like all planner nodes, PlannerConnection separates:\n * 1. immutable structure: Ordering, filters, cost model (set at construction)\n * 2. mutable state: Pinned status, constraints (mutated during planning)\n *\n * # Cost Estimation\n * The ordering and filters determine the initial cost. As planning progresses,\n * constraints from parent joins refine the cost estimate.\n *\n * # Constraint Flow\n * When a connection is pinned as the outer loop, it reveals constraints for\n * connected joins. These constraints propagate through the graph, allowing\n * other connections to update their cost estimates.\n *\n * Example:\n *\n * ```ts\n * builder.issue.whereExists('assignee', a => a.where('name', 'Alice'))\n * ```\n *\n * ```\n * [issue] [assignee]\n * | |\n * | +-- where name = 'Alice'\n * \\ /\n * \\ /\n * [join]\n * |\n * ```\n *\n * - Initial state: Both connections have no constraints, costs are unconstrained\n * - If `issue` chosen first: Reveals constraint `assignee_id` for assignee connection\n * - If `assignee` chosen first: Reveals constraint `assignee_id` for issue connection\n * - Updated costs guide the next selection\n *\n * # Pushed conditions\n * Correlated predicate pushdown copies a parent's condition on a correlation\n * column into the child (`pushed`). When the parent drives the join into the\n * child, the join binds that column to a parent row, so the parent's\n * condition implies the copy and the copy removes no rows. The connection\n * leaves such a copy out of the cost model call. Another join that binds the\n * column does not imply the copy, because its rows need not hold the parent's\n * condition. The connection also leaves every copy out of `selectivity`: a\n * semi-join always binds the column, and a flipped join gets the copy's\n * reduction through `returnedRows` instead.\n *\n * # Lifecycle\n * 1. Construct with immutable structure (ordering, filters, cost model)\n * 2. Wire to output node during graph construction\n * 3. Planning mutates pinned status and accumulates constraints\n * 4. reset() clears mutable state for replanning\n */\nexport class PlannerConnection {\n readonly kind = 'connection' as const;\n\n // ========================================================================\n // IMMUTABLE STRUCTURE (set during construction, never changes)\n // ========================================================================\n readonly #sort: Ordering;\n readonly #filters: Condition | undefined;\n readonly #pushed: ReadonlySet<SimpleCondition> | undefined;\n /** `#filters` without the pushed conditions. */\n readonly #unpushedFilters: Condition | undefined;\n readonly #model: ConnectionCostModel;\n readonly table: string;\n readonly name: string; // Human-readable name for debugging (defaults to table name)\n readonly #baseConstraints: PlannerConstraint | undefined; // Constraints from parent correlation\n readonly #baseLimit: number | undefined; // Original limit from query structure (never modified)\n readonly selectivity: number; // Fraction of rows passing filters (1.0 = no filtering)\n #output?: PlannerNode | undefined; // Set once during graph construction\n /**\n * The join from the parent into this connection's EXISTS subquery. Set once\n * during graph construction. The pushed conditions are copies of that\n * parent's conditions. Undefined for a root connection, whose parent (if\n * it is a `related` subquery) always drives it.\n */\n #parentJoin: PlannerJoin | undefined;\n\n // ========================================================================\n // MUTABLE PLANNING STATE (changes during plan search)\n // ========================================================================\n /**\n * Current limit during planning. Can be cleared (set to undefined) when a\n * parent join is flipped, indicating this connection is now in an outer loop\n * and should not be limited by EXISTS semantics.\n */\n limit: number | undefined;\n\n /**\n * Constraints accumulated from parent joins during planning.\n * Key is a path through the graph (e.g., \"0,1\" for branch pattern [0,1]).\n *\n * Undefined constraints are possible when a FO converts to UFO and only\n * a single join in the UFO is flipped - other branches report undefined.\n */\n readonly #constraints: Map<string, PlannerConstraint | undefined>;\n\n /**\n * Per-branch filters registered by `PlannerFilter` nodes (simple OR\n * branches) when the enclosing FanIn is in UFI mode. Key is the same\n * `branchPattern.join(',')` used by `#constraints`.\n *\n * These are AND-merged with `#filters` when calling the cost model so\n * the model sees the true filter that the runtime would push to the\n * source for this specific branch.\n */\n readonly #perBranchFilters: Map<string, NoSubqueryCondition> = new Map();\n\n readonly #isRoot: boolean;\n\n /**\n * Cached per-constraint costs to avoid redundant cost model calls.\n * Maps constraint key (branch pattern string) to computed cost.\n * Invalidated when constraints change.\n */\n #cachedConstraintCosts: Map<string, CostEstimate> = new Map();\n\n /**\n * How the source reads rows for each constraint key with a cached cost.\n * Cleared along with {@link #cachedConstraintCosts}.\n */\n #cachedAccesses: Map<string, ConnectionAccess> = new Map();\n\n constructor(\n table: string,\n model: ConnectionCostModel,\n sort: Ordering,\n filters: Condition | undefined,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n limit?: number,\n name?: string,\n pushed?: ReadonlySet<SimpleCondition>,\n ) {\n this.table = table;\n this.name = name ?? table;\n this.#sort = sort;\n this.#filters = filters;\n this.#pushed = pushed;\n this.#unpushedFilters = pushed\n ? withoutConjuncts(filters, c => pushed.has(c))\n : filters;\n this.#model = model;\n this.#baseConstraints = baseConstraints;\n this.#baseLimit = limit;\n this.limit = limit;\n this.#constraints = new Map();\n this.#isRoot = isRoot;\n\n this.selectivity = this.#computeSelectivity(this.#unpushedFilters);\n }\n\n setOutput(node: PlannerNode): void {\n this.#output = node;\n }\n\n get output(): PlannerNode {\n assert(this.#output !== undefined, 'Output not set');\n return this.#output;\n }\n\n setParentJoin(join: PlannerJoin): void {\n this.#parentJoin = join;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return 'connection';\n }\n\n /**\n * Constraints are uniquely identified by their path through the\n * graph.\n *\n * FO represents all sub-joins as a single path.\n * UFO represents each sub-join as a separate path.\n * The first branch in a UFO will match the path of FO so no re-set needs to happen\n * when swapping from FO to UFO.\n *\n * FO swaps to UFO when a join inside FO-FI gets flipped.\n *\n * The max of the last element of the paths is the number of\n * root branches.\n */\n propagateConstraints(\n path: number[],\n c: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n const key = path.join(',');\n this.#constraints.set(key, c);\n // Constraints changed, invalidate cost caches\n this.#clearCostCaches();\n\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'connection',\n node: this.name,\n branchPattern: path,\n constraint: c,\n from: from?.kind ?? 'unknown',\n });\n }\n\n /**\n * Register a per-branch filter for the given branch pattern. Called by\n * `PlannerFilter` (a simple OR branch) when the enclosing FanIn is in\n * UFI mode. The filter is AND-merged with `this.#filters` when the cost\n * model is invoked for this branch.\n */\n setPerBranchFilter(path: number[], filter: NoSubqueryCondition): void {\n this.#perBranchFilters.set(path.join(','), filter);\n // The cost depends on the filter, so invalidate caches.\n this.#clearCostCaches();\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n // Branch pattern specified - return cost for this specific branch\n const key = branchPattern.join(',');\n\n // Check per-constraint cache first\n let cost = this.#cachedConstraintCosts.get(key);\n if (cost !== undefined) {\n return cost;\n }\n\n // Cache miss - compute and cache\n const constraint = this.#constraints.get(key);\n // Merge base constraints with propagated constraints\n const mergedConstraint = mergeConstraints(\n this.#baseConstraints,\n constraint,\n );\n // AND the per-branch filter (registered by a PlannerFilter for a\n // simple OR branch in UFI mode) with the connection-time filter so\n // the cost model sees the same effective filter the runtime applies.\n const perBranchFilter = this.#perBranchFilters.get(key);\n const filters = andFilters(\n this.#filtersFor(mergedConstraint),\n perBranchFilter,\n );\n const {startupCost, fanout, rows, plan} = this.#model(\n this.table,\n this.#sort,\n filters,\n mergedConstraint,\n );\n const selectivity = perBranchFilter\n ? this.#computeSelectivity(\n andFilters(this.#unpushedFilters, perBranchFilter),\n )\n : this.selectivity;\n cost = {\n startupCost,\n scanEst:\n this.limit === undefined\n ? rows\n : Math.min(rows, this.limit / downstreamChildSelectivity),\n cost: 0,\n returnedRows: rows,\n selectivity,\n limit: this.limit,\n fanout,\n };\n this.#cachedConstraintCosts.set(key, cost);\n this.#cachedAccesses.set(key, {\n constraint: mergedConstraint,\n filters,\n rows,\n plan,\n });\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'connection',\n node: this.name,\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(cost),\n filters: this.#filters,\n ordering: this.#sort,\n });\n }\n\n return cost;\n }\n\n /**\n * `#filters` without the pushed conditions that `constraint` binds the\n * column of, when the parent drives the join into this connection. The\n * runtime still applies them, but they remove no rows.\n *\n * When that join is flipped, this connection is the outer loop, so every\n * pushed condition counts, even where another flipped join binds its\n * column.\n */\n #filtersFor(\n constraint: PlannerConstraint | undefined,\n ): Condition | undefined {\n const pushed = this.#pushed;\n if (\n !pushed ||\n !constraint ||\n this.#unpushedFilters === this.#filters ||\n this.#parentJoin?.type === 'flipped'\n ) {\n return this.#filters;\n }\n return withoutConjuncts(\n this.#filters,\n c =>\n pushed.has(c) &&\n c.left.type === 'column' &&\n Object.hasOwn(constraint, c.left.name),\n );\n }\n\n #computeSelectivity(filters: Condition | undefined): number {\n if (this.#baseLimit === undefined || filters === undefined) {\n return 1.0;\n }\n\n const costWithFilters = this.#model(\n this.table,\n this.#sort,\n filters,\n undefined,\n );\n const costWithoutFilters = this.#model(\n this.table,\n this.#sort,\n undefined,\n undefined,\n );\n return costWithoutFilters.rows > 0\n ? costWithFilters.rows / costWithoutFilters.rows\n : 1.0;\n }\n\n /**\n * Remove the limit from this connection.\n * Called when a parent join is flipped, making this connection part of an\n * outer loop that should produce all rows rather than stopping at the limit.\n */\n unlimit(): void {\n if (this.#isRoot) {\n // We cannot unlimit root connections\n return;\n }\n if (this.limit !== undefined) {\n this.limit = undefined;\n // Limit changes do not impact connection costs.\n // Limit is taken into account at the join level.\n // Given that, we do not need to invalidate cost caches here.\n }\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * For connections, we simply remove the limit.\n */\n propagateUnlimitFromFlippedJoin(): void {\n this.unlimit();\n }\n\n reset() {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n this.limit = this.#baseLimit;\n // Clear all cost caches\n this.#clearCostCaches();\n }\n\n #clearCostCaches(): void {\n this.#cachedConstraintCosts.clear();\n this.#cachedAccesses.clear();\n }\n\n /**\n * Capture constraint state for snapshotting.\n * Used by PlannerGraph to save/restore planning state.\n */\n captureConstraints(): Map<string, PlannerConstraint | undefined> {\n return new Map(this.#constraints);\n }\n\n /**\n * Restore constraint state from a snapshot.\n * Used by PlannerGraph to restore planning state.\n *\n * Per-branch filters are not snapshotted — they are re-derived by the\n * subsequent `propagateConstraints` pass. Clearing them here ensures\n * that any entries left over from the previous iteration don't linger\n * in case the restored plan registers a different set.\n */\n restoreConstraints(\n constraints: Map<string, PlannerConstraint | undefined>,\n ): void {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n for (const [key, value] of constraints) {\n this.#constraints.set(key, value);\n }\n // Constraints changed, invalidate cost caches\n this.#clearCostCaches();\n }\n\n get sort(): Ordering {\n return this.#sort;\n }\n\n /**\n * How the source reads rows for the current plan: one access per branch\n * pattern this connection was costed for since its constraints last\n * changed. Estimate the plan's cost first to populate them.\n */\n accesses(): Iterable<ConnectionAccess> {\n return this.#cachedAccesses.values();\n }\n\n /** Get current constraints for debugging. */\n getConstraintsForDebug(): Record<string, PlannerConstraint | undefined> {\n const record: Record<string, PlannerConstraint | undefined> = {};\n for (const [key, value] of this.#constraints) {\n record[key] = value;\n }\n return record;\n }\n\n /** Get filters for debugging. */\n getFiltersForDebug(): Condition | undefined {\n return this.#filters;\n }\n\n /** Get sort/ordering for debugging. */\n getSortForDebug(): Ordering {\n return this.#sort;\n }\n\n /** Get estimated cost for each constraint branch. */\n getConstraintCostsForDebug(): Record<string, CostEstimate> {\n const record: Record<string, CostEstimate> = {};\n for (const [key, value] of this.#cachedConstraintCosts) {\n record[key] = value;\n }\n return record;\n }\n}\n\nfunction andFilters(\n a: Condition | undefined,\n b: Condition | undefined,\n): Condition | undefined {\n return a && b ? {type: 'and', conditions: [a, b]} : (a ?? b);\n}\n\n/**\n * `c` without the simple conjuncts, including those of nested ANDs, for which\n * `remove` returns true. Returns `c` itself when it removes nothing.\n */\nfunction withoutConjuncts(\n c: Condition | undefined,\n remove: (c: SimpleCondition) => boolean,\n): Condition | undefined {\n switch (c?.type) {\n case 'simple':\n return remove(c) ? undefined : c;\n case 'and': {\n const conditions: Condition[] = [];\n for (const x of c.conditions) {\n const y = withoutConjuncts(x, remove);\n if (y !== undefined) {\n conditions.push(y);\n }\n }\n if (\n conditions.length === c.conditions.length &&\n conditions.every((x, i) => x === c.conditions[i])\n ) {\n return c;\n }\n if (conditions.length <= 1) {\n return conditions[0];\n }\n return {type: 'and', conditions};\n }\n default:\n return c;\n }\n}\n\ntype FanoutEst = {\n fanout: number;\n confidence: 'high' | 'med' | 'none';\n};\nexport type FanoutCostModel = (columns: string[]) => FanoutEst;\n\n/**\n * How a source would read the rows of a connection, as far as the cost model\n * can tell. This is used to warn about slow plans, not to choose between\n * them.\n */\nexport type AccessPlan = {\n /**\n * `search` if the source uses an index to find the matching rows. `scan`\n * if it reads the whole table (or a whole index, for its order) and tests\n * each row, stopping early only if the reader stops.\n */\n readonly access: 'search' | 'scan';\n\n /**\n * Whether the rows are sorted after they are read. `full` means that every\n * matching row is read and sorted before the first one is returned, so\n * reading fewer rows (e.g. for a limit) does not help. `partial` means that\n * only runs of rows that tie on a prefix of the ordering are sorted.\n */\n readonly sort: 'none' | 'partial' | 'full';\n\n /** The number of rows in the table, if the cost model knows it. */\n readonly tableRows: number | undefined;\n};\n\nexport type CostModelCost = {\n startupCost: number;\n rows: number;\n fanout: FanoutCostModel;\n /** Omitted by cost models that cannot tell. */\n plan?: AccessPlan | undefined;\n};\n\n/**\n * A read of a connection's source under a particular constraint, as costed\n * by the cost model.\n */\nexport type ConnectionAccess = {\n /** The columns bound to a single value by the time of the read. */\n readonly constraint: PlannerConstraint | undefined;\n readonly filters: Condition | undefined;\n /** The estimated number of rows returned. */\n readonly rows: number;\n readonly plan: AccessPlan | undefined;\n};\nexport type ConnectionCostModel = (\n table: string,\n sort: Ordering,\n filters: Condition | undefined,\n constraint: PlannerConstraint | undefined,\n) => CostModelCost;\n"],"mappings":"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"}
1
+ {"version":3,"file":"planner-connection.js","names":[],"sources":["../../../../../zql/src/planner/planner-connection.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {NoSubqueryCondition} from '../builder/filter.ts';\nimport {\n mergeConstraints,\n type PlannerConstraint,\n} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport type {PlannerJoin} from './planner-join.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\n\n/**\n * Represents a connection to a source (table scan).\n *\n * # Dual State Pattern\n * Like all planner nodes, PlannerConnection separates:\n * 1. immutable structure: Ordering, filters, cost model (set at construction)\n * 2. mutable state: Pinned status, constraints (mutated during planning)\n *\n * # Cost Estimation\n * The ordering and filters determine the initial cost. As planning progresses,\n * constraints from parent joins refine the cost estimate.\n *\n * # Constraint Flow\n * When a connection is pinned as the outer loop, it reveals constraints for\n * connected joins. These constraints propagate through the graph, allowing\n * other connections to update their cost estimates.\n *\n * Example:\n *\n * ```ts\n * builder.issue.whereExists('assignee', a => a.where('name', 'Alice'))\n * ```\n *\n * ```\n * [issue] [assignee]\n * | |\n * | +-- where name = 'Alice'\n * \\ /\n * \\ /\n * [join]\n * |\n * ```\n *\n * - Initial state: Both connections have no constraints, costs are unconstrained\n * - If `issue` chosen first: Reveals constraint `assignee_id` for assignee connection\n * - If `assignee` chosen first: Reveals constraint `assignee_id` for issue connection\n * - Updated costs guide the next selection\n *\n * # Pushed conditions\n * Correlated predicate pushdown copies a parent's condition on a correlation\n * column into the child (`pushed`). When the parent drives the join into the\n * child, the join binds that column to a parent row, so the parent's\n * condition implies the copy and the copy removes no rows. The connection\n * leaves such a copy out of the cost model call. Another join that binds the\n * column does not imply the copy, because its rows need not hold the parent's\n * condition. The connection also leaves every copy out of `selectivity`: a\n * semi-join always binds the column, and a flipped join gets the copy's\n * reduction through `returnedRows` instead.\n *\n * # Lifecycle\n * 1. Construct with immutable structure (ordering, filters, cost model)\n * 2. Wire to output node during graph construction\n * 3. Planning mutates pinned status and accumulates constraints\n * 4. reset() clears mutable state for replanning\n */\nexport class PlannerConnection {\n readonly kind = 'connection' as const;\n\n // ========================================================================\n // IMMUTABLE STRUCTURE (set during construction, never changes)\n // ========================================================================\n readonly #sort: Ordering;\n readonly #filters: Condition | undefined;\n readonly #pushed: ReadonlySet<SimpleCondition> | undefined;\n /** `#filters` without the pushed conditions. */\n readonly #unpushedFilters: Condition | undefined;\n readonly #model: ConnectionCostModel;\n readonly table: string;\n readonly name: string; // Human-readable name for debugging (defaults to table name)\n readonly #baseConstraints: PlannerConstraint | undefined; // Constraints from parent correlation\n readonly #baseLimit: number | undefined; // Original limit from query structure (never modified)\n readonly selectivity: number; // Fraction of rows passing filters (1.0 = no filtering)\n #output?: PlannerNode | undefined; // Set once during graph construction\n /**\n * The join from the parent into this connection's EXISTS subquery. Set once\n * during graph construction. The pushed conditions are copies of that\n * parent's conditions. Undefined for a root connection, whose parent (if\n * it is a `related` subquery) always drives it.\n */\n #parentJoin: PlannerJoin | undefined;\n\n // ========================================================================\n // MUTABLE PLANNING STATE (changes during plan search)\n // ========================================================================\n /**\n * Current limit during planning. Can be cleared (set to undefined) when a\n * parent join is flipped, indicating this connection is now in an outer loop\n * and should not be limited by EXISTS semantics.\n */\n limit: number | undefined;\n\n /**\n * Constraints accumulated from parent joins during planning.\n * Key is a path through the graph (e.g., \"0,1\" for branch pattern [0,1]).\n *\n * Undefined constraints are possible when a FO converts to UFO and only\n * a single join in the UFO is flipped - other branches report undefined.\n */\n readonly #constraints: Map<string, PlannerConstraint | undefined>;\n\n /**\n * Per-branch filters registered by `PlannerFilter` nodes (simple OR\n * branches) when the enclosing FanIn is in UFI mode. Key is the same\n * `branchPattern.join(',')` used by `#constraints`.\n *\n * These are AND-merged with `#filters` when calling the cost model so\n * the model sees the true filter that the runtime would push to the\n * source for this specific branch.\n */\n readonly #perBranchFilters: Map<string, NoSubqueryCondition> = new Map();\n\n readonly #isRoot: boolean;\n\n /**\n * Cached per-constraint costs to avoid redundant cost model calls.\n * Maps constraint key (branch pattern string) to computed cost.\n * Invalidated when constraints change.\n */\n #cachedConstraintCosts: Map<string, CostEstimate> = new Map();\n\n /**\n * How the source reads rows for each constraint key with a cached cost.\n * Cleared along with {@link #cachedConstraintCosts}.\n */\n #cachedAccesses: Map<string, ConnectionAccess> = new Map();\n\n constructor(\n table: string,\n model: ConnectionCostModel,\n sort: Ordering,\n filters: Condition | undefined,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n limit?: number,\n name?: string,\n pushed?: ReadonlySet<SimpleCondition>,\n ) {\n this.table = table;\n this.name = name ?? table;\n this.#sort = sort;\n this.#filters = filters;\n this.#pushed = pushed;\n this.#unpushedFilters = pushed\n ? withoutConjuncts(filters, c => pushed.has(c))\n : filters;\n this.#model = model;\n this.#baseConstraints = baseConstraints;\n this.#baseLimit = limit;\n this.limit = limit;\n this.#constraints = new Map();\n this.#isRoot = isRoot;\n\n this.selectivity = this.#computeSelectivity(this.#unpushedFilters);\n }\n\n setOutput(node: PlannerNode): void {\n this.#output = node;\n }\n\n get output(): PlannerNode {\n assert(this.#output !== undefined, 'Output not set');\n return this.#output;\n }\n\n setParentJoin(join: PlannerJoin): void {\n this.#parentJoin = join;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return 'connection';\n }\n\n /**\n * Constraints are uniquely identified by their path through the\n * graph.\n *\n * FO represents all sub-joins as a single path.\n * UFO represents each sub-join as a separate path.\n * The first branch in a UFO will match the path of FO so no re-set needs to happen\n * when swapping from FO to UFO.\n *\n * FO swaps to UFO when a join inside FO-FI gets flipped.\n *\n * The max of the last element of the paths is the number of\n * root branches.\n */\n propagateConstraints(\n path: number[],\n c: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n const key = path.join(',');\n this.#constraints.set(key, c);\n // Constraints changed, invalidate cost caches\n this.#clearCostCaches();\n\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'connection',\n node: this.name,\n branchPattern: path,\n constraint: c,\n from: from?.kind ?? 'unknown',\n });\n }\n\n /**\n * Register a per-branch filter for the given branch pattern. Called by\n * `PlannerFilter` (a simple OR branch) when the enclosing FanIn is in\n * UFI mode. The filter is AND-merged with `this.#filters` when the cost\n * model is invoked for this branch.\n */\n setPerBranchFilter(path: number[], filter: NoSubqueryCondition): void {\n this.#perBranchFilters.set(path.join(','), filter);\n // The cost depends on the filter, so invalidate caches.\n this.#clearCostCaches();\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n // Branch pattern specified - return cost for this specific branch\n const key = branchPattern.join(',');\n\n // Check per-constraint cache first\n let cost = this.#cachedConstraintCosts.get(key);\n if (cost !== undefined) {\n return cost;\n }\n\n // Cache miss - compute and cache\n const constraint = this.#constraints.get(key);\n // Merge base constraints with propagated constraints\n const mergedConstraint = mergeConstraints(\n this.#baseConstraints,\n constraint,\n );\n // AND the per-branch filter (registered by a PlannerFilter for a\n // simple OR branch in UFI mode) with the connection-time filter so\n // the cost model sees the same effective filter the runtime applies.\n const perBranchFilter = this.#perBranchFilters.get(key);\n const filters = andFilters(\n this.#filtersFor(mergedConstraint),\n perBranchFilter,\n );\n const {startupCost, fanout, rows, plan} = this.#model(\n this.table,\n this.#sort,\n filters,\n mergedConstraint,\n );\n const selectivity = perBranchFilter\n ? this.#computeSelectivity(\n andFilters(this.#unpushedFilters, perBranchFilter),\n )\n : this.selectivity;\n cost = {\n startupCost,\n scanEst:\n this.limit === undefined\n ? rows\n : Math.min(rows, this.limit / downstreamChildSelectivity),\n cost: 0,\n returnedRows: rows,\n selectivity,\n limit: this.limit,\n fanout,\n };\n this.#cachedConstraintCosts.set(key, cost);\n this.#cachedAccesses.set(key, {\n constraint: mergedConstraint,\n filters,\n rows,\n plan,\n });\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'connection',\n node: this.name,\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(cost),\n filters: this.#filters,\n ordering: this.#sort,\n });\n }\n\n return cost;\n }\n\n /**\n * `#filters` without the pushed conditions that `constraint` binds the\n * column of, when the parent drives the join into this connection. The\n * runtime still applies them, but they remove no rows.\n *\n * When that join is flipped, this connection is the outer loop, so every\n * pushed condition counts, even where another flipped join binds its\n * column.\n */\n #filtersFor(\n constraint: PlannerConstraint | undefined,\n ): Condition | undefined {\n const pushed = this.#pushed;\n if (\n !pushed ||\n !constraint ||\n this.#unpushedFilters === this.#filters ||\n this.#parentJoin?.type === 'flipped'\n ) {\n return this.#filters;\n }\n return withoutConjuncts(\n this.#filters,\n c =>\n pushed.has(c) &&\n c.left.type === 'column' &&\n Object.hasOwn(constraint, c.left.name),\n );\n }\n\n #computeSelectivity(filters: Condition | undefined): number {\n if (this.#baseLimit === undefined || filters === undefined) {\n return 1.0;\n }\n\n const costWithFilters = this.#model(\n this.table,\n this.#sort,\n filters,\n undefined,\n );\n const costWithoutFilters = this.#model(\n this.table,\n this.#sort,\n undefined,\n undefined,\n );\n return costWithoutFilters.rows > 0\n ? costWithFilters.rows / costWithoutFilters.rows\n : 1.0;\n }\n\n /**\n * Remove the limit from this connection.\n * Called when a parent join is flipped, making this connection part of an\n * outer loop that should produce all rows rather than stopping at the limit.\n */\n unlimit(): void {\n if (this.#isRoot) {\n // We cannot unlimit root connections\n return;\n }\n if (this.limit !== undefined) {\n this.limit = undefined;\n // Limit changes do not impact connection costs.\n // Limit is taken into account at the join level.\n // Given that, we do not need to invalidate cost caches here.\n }\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * For connections, we simply remove the limit.\n */\n propagateUnlimitFromFlippedJoin(): void {\n this.unlimit();\n }\n\n reset() {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n this.limit = this.#baseLimit;\n // Clear all cost caches\n this.#clearCostCaches();\n }\n\n #clearCostCaches(): void {\n this.#cachedConstraintCosts.clear();\n this.#cachedAccesses.clear();\n }\n\n /**\n * Capture constraint state for snapshotting.\n * Used by PlannerGraph to save/restore planning state.\n */\n captureConstraints(): Map<string, PlannerConstraint | undefined> {\n return new Map(this.#constraints);\n }\n\n /**\n * Restore constraint state from a snapshot.\n * Used by PlannerGraph to restore planning state.\n *\n * Per-branch filters are not snapshotted — they are re-derived by the\n * subsequent `propagateConstraints` pass. Clearing them here ensures\n * that any entries left over from the previous iteration don't linger\n * in case the restored plan registers a different set.\n */\n restoreConstraints(\n constraints: Map<string, PlannerConstraint | undefined>,\n ): void {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n for (const [key, value] of constraints) {\n this.#constraints.set(key, value);\n }\n // Constraints changed, invalidate cost caches\n this.#clearCostCaches();\n }\n\n get sort(): Ordering {\n return this.#sort;\n }\n\n /**\n * How the source reads rows for the current plan: one access per branch\n * pattern this connection was costed for since its constraints last\n * changed. Estimate the plan's cost first to populate them.\n */\n accesses(): Iterable<ConnectionAccess> {\n return this.#cachedAccesses.values();\n }\n\n /** Get current constraints for debugging. */\n getConstraintsForDebug(): Record<string, PlannerConstraint | undefined> {\n const record: Record<string, PlannerConstraint | undefined> = {};\n for (const [key, value] of this.#constraints) {\n record[key] = value;\n }\n return record;\n }\n\n /** Get filters for debugging. */\n getFiltersForDebug(): Condition | undefined {\n return this.#filters;\n }\n\n /** Get sort/ordering for debugging. */\n getSortForDebug(): Ordering {\n return this.#sort;\n }\n\n /** Get estimated cost for each constraint branch. */\n getConstraintCostsForDebug(): Record<string, CostEstimate> {\n const record: Record<string, CostEstimate> = {};\n for (const [key, value] of this.#cachedConstraintCosts) {\n record[key] = value;\n }\n return record;\n }\n}\n\nfunction andFilters(\n a: Condition | undefined,\n b: Condition | undefined,\n): Condition | undefined {\n return a && b ? {type: 'and', conditions: [a, b]} : (a ?? b);\n}\n\n/**\n * `c` without the simple conjuncts, including those of nested ANDs, for which\n * `remove` returns true. Returns `c` itself when it removes nothing.\n */\nfunction withoutConjuncts(\n c: Condition | undefined,\n remove: (c: SimpleCondition) => boolean,\n): Condition | undefined {\n switch (c?.type) {\n case 'simple':\n return remove(c) ? undefined : c;\n case 'and': {\n const conditions: Condition[] = [];\n for (const x of c.conditions) {\n const y = withoutConjuncts(x, remove);\n if (y !== undefined) {\n conditions.push(y);\n }\n }\n if (\n conditions.length === c.conditions.length &&\n conditions.every((x, i) => x === c.conditions[i])\n ) {\n return c;\n }\n if (conditions.length <= 1) {\n return conditions[0];\n }\n return {type: 'and', conditions};\n }\n default:\n return c;\n }\n}\n\ntype FanoutEst = {\n fanout: number;\n confidence: 'high' | 'med' | 'none';\n};\nexport type FanoutCostModel = (columns: string[]) => FanoutEst;\n\n/**\n * How a source would read the rows of a connection, as far as the cost model\n * can tell. This is used to warn about slow plans, not to choose between\n * them.\n */\nexport type AccessPlan = {\n /**\n * `search` if the source uses an index to find the matching rows. `scan`\n * if it reads the whole table (or a whole index, for its order) and tests\n * each row, stopping early only if the reader stops.\n */\n readonly access: 'search' | 'scan';\n\n /**\n * Whether the rows are sorted after they are read. `full` means that every\n * matching row is read and sorted before the first one is returned, so\n * reading fewer rows (e.g. for a limit) does not help. `partial` means that\n * only runs of rows that tie on a prefix of the ordering are sorted.\n */\n readonly sort: 'none' | 'partial' | 'full';\n\n /** The number of rows in the table, if the cost model knows it. */\n readonly tableRows: number | undefined;\n};\n\nexport type CostModelCost = {\n startupCost: number;\n rows: number;\n fanout: FanoutCostModel;\n /** Omitted by cost models that cannot tell. */\n plan?: AccessPlan | undefined;\n};\n\n/**\n * A read of a connection's source under a particular constraint, as costed\n * by the cost model.\n */\nexport type ConnectionAccess = {\n /** The columns bound to a single value by the time of the read. */\n readonly constraint: PlannerConstraint | undefined;\n readonly filters: Condition | undefined;\n /** The estimated number of rows returned. */\n readonly rows: number;\n readonly plan: AccessPlan | undefined;\n};\nexport type ConnectionCostModel = (\n table: string,\n sort: Ordering,\n filters: Condition | undefined,\n constraint: PlannerConstraint | undefined,\n) => CostModelCost;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import {newArray} from '../../../shared/src/arrays.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\nimport type * as v from '../../../shared/src/valita.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanState} from './planner-graph.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType:\n | 'connection'\n | 'join'\n | 'fan-out'\n | 'fan-in'\n | 'filter'\n | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n getOrInsertComputed(eventsByAttempt, attempt, newArray).push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import {newArray} from '../../../shared/src/arrays.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\nimport type * as v from '../../../shared/src/valita.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanState} from './planner-graph.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType:\n | 'connection'\n | 'join'\n | 'fan-out'\n | 'fan-in'\n | 'filter'\n | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n getOrInsertComputed(eventsByAttempt, attempt, newArray).push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;AAuJA,IAAa,sBAAb,MAAyD;CACvD,SAAoC,CAAC;CACrC,iBAAyB;CAEzB,IAAI,OAA6B;EAE/B,IAAI,MAAM,SAAS,iBACjB,KAAK,iBAAiB,MAAM;EAI9B,IAAI,MAAM,SAAS,eAAe,MAAM,SAAS,mBAC/C,MAA+C,gBAC7C,KAAK;EAGT,KAAK,OAAO,KAAK,KAAK;CACxB;;;;CAKA,UACE,MACsC;EACtC,OAAO,KAAK,OAAO,QAAO,MAAK,EAAE,SAAS,IAAI;CAIhD;;;;CAKA,SAAiB;EACf,OAAO,oBAAoB,KAAK,MAAM;CACxC;AACF;;;;AAKA,SAAS,iBACP,YACQ;CACR,IAAI,CAAC,YAAY,OAAO;CACxB,MAAM,OAAO,OAAO,KAAK,UAAU;CACnC,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,OAAO,MAAM,KAAK,KAAK,IAAI,IAAI;AACjC;;;;AAKA,SAAS,oBAAoB,OAA8B;CACzD,QAAQ,MAAM,MAAd;EACE,KAAK,UACH,OAAO,MAAM;EACf,KAAK;GAEH,IAAI,OAAO,MAAM,UAAU,UACzB,OAAO,IAAI,MAAM,MAAM;GAEzB,OAAO,KAAK,UAAU,MAAM,KAAK;EACnC,KAAK,UACH,OAAO,IAAI,MAAM,OAAO,GAAG,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,MAAM,KAAK,GAAG,IAAI,MAAM;CAC1F;AACF;;;;AAKA,SAAS,aAAa,QAAuC;CAC3D,IAAI,CAAC,QAAQ,OAAO;CAEpB,QAAQ,OAAO,MAAf;EACE,KAAK,UACH,OAAO,GAAG,oBAAoB,OAAO,IAAI,EAAE,GAAG,OAAO,GAAG,GAAG,oBAAoB,OAAO,KAAK;EAC7F,KAAK,OACH,OAAO,IAAI,OAAO,WAAW,IAAI,YAAY,CAAC,CAAC,KAAK,OAAO,EAAE;EAC/D,KAAK,MACH,OAAO,IAAI,OAAO,WAAW,IAAI,YAAY,CAAC,CAAC,KAAK,MAAM,EAAE;EAC9D,KAAK,sBACH,OAAO,UAAU,OAAO,QAAQ,SAAS,MAAM;EACjD,SACE,OAAO,KAAK,UAAU,MAAM;CAChC;AACF;;;;AAKA,SAAS,eAAe,UAAwC;CAC9D,IAAI,CAAC,YAAY,SAAS,WAAW,GAAG,OAAO;CAC/C,OAAO,SACJ,KAAK,CAAC,OAAO,eAAe,GAAG,MAAM,GAAG,WAAW,CAAC,CACpD,KAAK,IAAI;AACd;;;;AAKA,SAAS,qBACP,YACA,QACU;CACV,MAAM,QAAkB,CAAC;CAMzB,MAAM,gBAHa,OAAO,MAAK,MAAK,EAAE,SAAS,eAGzB,CAAA,EAAY,iBAAiB;CAGnD,MAAM,UACJ,OAAO,kBAAkB,WACrB,KAAK,KAAK,KAAK,KAAK,aAAa,CAAC,KAAK,IACvC;CACN,MAAM,aAAa,WAAW,SAAS,CAAC,CAAC,CAAC,SAAS,SAAS,GAAG;CAE/D,MAAM,KACJ,YAAY,aAAa,EAAE,GAAG,cAAc,YAAY,WAAW,IAAI,WAAW,EACpF;CAGA,MAAM,uBAGA,CAAC;CACP,MAAM,6BAAoD,CAAC;CAE3D,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,MAAM,SAAS,eAAe,MAAM,aAAa,cACnD,qBAAqB,KAAK,KAAK;EAEjC,IAAI,MAAM,SAAS,qBAAqB,MAAM,aAAa,cACzD,2BAA2B,KAAK,KAAK;CAEzC;CAGA,IAAI,qBAAqB,SAAS,GAAG;EACnC,MAAM,KAAK,gBAAgB;EAC3B,KAAK,MAAM,QAAQ,sBAAsB;GAEvC,MAAM,aAAa,2BAA2B,MAC5C,MACE,EAAE,SAAS,KAAK,QAChB,EAAE,cAAc,KAAK,GAAG,MAAM,KAAK,cAAc,KAAK,GAAG,CAC7D,CAAC,EAAE;GAEH,MAAM,gBAAgB,iBAAiB,UAAU;GACjD,MAAM,YAAY,aAAa,KAAK,OAAO;GAC3C,MAAM,cAAc,eAAe,KAAK,QAAQ;GAChD,MAAM,WACJ,KAAK,aAAa,UAAU,KAAA,IACxB,KAAK,aAAa,MAAM,SAAS,IACjC;GAEN,MAAM,KAAK,OAAO,KAAK,KAAK,EAAE;GAC9B,MAAM,KACJ,cAAc,KAAK,aAAa,KAAK,QAAQ,CAAC,EAAE,YAAY,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,SAAS,KAAK,aAAa,QAAQ,QAAQ,CAAC,GACnJ;GACA,MAAM,KACJ,cAAc,KAAK,aAAa,aAAa,QAAQ,CAAC,EAAE,gBAAgB,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,UAAU,UAC7H;GACA,MAAM,KACJ,oCAAoC,KAAK,2BAA2B,QAAQ,CAAC,GAC/E;GACA,MAAM,KAAK,qBAAqB,eAAe;GAC/C,MAAM,KAAK,iBAAiB,WAAW;GACvC,MAAM,KAAK,kBAAkB,aAAa;EAC5C;CACF;CAGA,MAAM,YAGA,CAAC;CACP,KAAK,MAAM,SAAS,QAClB,IAAI,MAAM,SAAS,eAAe,MAAM,aAAa,QACnD,UAAU,KAAK,KAAK;CAIxB,IAAI,UAAU,SAAS,GAAG;EACxB,MAAM,KAAK,UAAU;EACrB,KAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,UAAU,KAAK,WAAW,KAAK,KAAK,SAAS,KAAK;GACxD,MAAM,WACJ,KAAK,aAAa,UAAU,KAAA,IACxB,KAAK,aAAa,MAAM,SAAS,IACjC;GAEN,MAAM,KAAK,OAAO,KAAK,OAAO,QAAQ,EAAE;GACxC,MAAM,KACJ,cAAc,KAAK,aAAa,KAAK,QAAQ,CAAC,EAAE,YAAY,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,SAAS,KAAK,aAAa,QAAQ,QAAQ,CAAC,GACnJ;GACA,MAAM,KACJ,cAAc,KAAK,aAAa,aAAa,QAAQ,CAAC,EAAE,gBAAgB,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,UAAU,UAC7H;GACA,MAAM,KACJ,oCAAoC,KAAK,2BAA2B,QAAQ,CAAC,GAC/E;EACF;CACF;CAGA,MAAM,gBAAgB,OAAO,MAAK,MAAK,EAAE,SAAS,eAAe;CAGjE,MAAM,cAAc,OAAO,MAAK,MAAK,EAAE,SAAS,aAAa;CAM7D,IAAI,eACF,MAAM,KACJ,mCAAmC,cAAc,UAAU,QAAQ,CAAC,GACtE;MACK,IAAI,aACT,MAAM,KAAK,oBAAoB,YAAY,QAAQ;CAGrD,OAAO;AACT;;;;;AAMA,SAAS,iCACP,YAC4C;CAC5C,IAAI,eAAe,KAAA,GACjB;CAEF,IAAI,eAAe,MACjB,OAAO;CAET,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,UAAU,GAChD,OAAO,OAAO,QAAQ,KAAA,IAAY,OAAO;CAE3C,OAAO;AACT;;;;;;;AAQA,SAAS,eAAe,OAA2C;CAEjE,IAAI,MAAM,SAAS,iBAAiB;EAClC,MAAM,EAAC,cAAc,GAAG,GAAG,SAAQ;EACnC,OAAO;CACT;CAGA,IAAI,MAAM,SAAS,mBACjB,OAAO;EACL,GAAG;EACH,YAAY,iCAAiC,MAAM,UAAU;CAC/D;CAGF,IAAI,MAAM,SAAS,oBACjB,OAAO;EACL,GAAG;EACH,OAAO,MAAM,MAAM,KAAI,UAAS;GAC9B,GAAG;GACH,aAAa,OAAO,YAClB,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CACnD,KACA,iCAAiC,GAAG,CACtC,CAAC,CACH;EACF,EAAE;CACJ;CAGF,IAAI,MAAM,SAAS,0BACjB,OAAO;EACL,GAAG;EACH,uBAAuB,MAAM,sBAAsB,KAAI,QAAO;GAC5D,GAAG;GACH,aAAa,OAAO,YAClB,OAAO,QAAQ,GAAG,WAAW,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CACjD,KACA,iCAAiC,GAAG,CACtC,CAAC,CACH;EACF,EAAE;CACJ;CAGF,OAAO;AACT;;;;;;AAOA,SAAgB,yBACd,QACsB;CACtB,OAAO,OAAO,IAAI,cAAc;AAClC;;;;;;;;;;;;;;;;AAiBA,SAAgB,oBACd,QACQ;CACR,MAAM,QAAkB,CAAC;CAGzB,MAAM,kCAAkB,IAAI,IAG1B;CACF,IAAI;CAUJ,KAAK,MAAM,SAAS,QAClB,IAAI,mBAAmB,OAAO;EAC5B,MAAM,UAAU,MAAM;EACtB,IAAI,YAAY,KAAA,GACd,oBAAoB,iBAAiB,SAAS,QAAQ,CAAC,CAAC,KAAK,KAAK;CAEtE,OAAO,IAAI,MAAM,SAAS,sBAExB,gBAAgB;CAKpB,KAAK,MAAM,CAAC,YAAY,WAAW,gBAAgB,QAAQ,GAAG;EAC5D,MAAM,KAAK,GAAG,qBAAqB,YAAY,MAAM,CAAC;EACtD,MAAM,KAAK,EAAE;CACf;CAGA,IAAI,eAAe;EACjB,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;EACzB,MAAM,KACJ,wBAAwB,cAAc,oBAAoB,EAAE,SAAS,cAAc,UAAU,QAAQ,CAAC,EAAE,EAC1G;EACA,IAAI,cAAc,WAAW,SAAS,GAAG;GACvC,MAAM,KAAK,eAAe;GAC1B,KAAK,MAAM,KAAK,cAAc,YAC5B,MAAM,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM;EAEzC;EACA,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;CAC3B;CAEA,OAAO,MAAM,KAAK,IAAI;AACxB"}