@rocicorp/zero 1.12.0-canary.10 → 1.12.0-canary.12

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 (231) hide show
  1. package/out/analyze-query/src/zql-chain.js +9 -1
  2. package/out/analyze-query/src/zql-chain.js.map +1 -1
  3. package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
  4. package/out/ast-to-zql/src/ast-to-zql.js +61 -20
  5. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  6. package/out/z2s/src/compiler.d.ts +1 -1
  7. package/out/z2s/src/compiler.d.ts.map +1 -1
  8. package/out/z2s/src/compiler.js +339 -2
  9. package/out/z2s/src/compiler.js.map +1 -1
  10. package/out/z2s/src/sql.d.ts +8 -0
  11. package/out/z2s/src/sql.d.ts.map +1 -1
  12. package/out/z2s/src/sql.js +58 -11
  13. package/out/z2s/src/sql.js.map +1 -1
  14. package/out/zero/package.js +1 -1
  15. package/out/zero/package.js.map +1 -1
  16. package/out/zero/src/bindings.js +4 -4
  17. package/out/zero-cache/src/auth/write-authorizer.js +1 -1
  18. package/out/zero-cache/src/config/zero-config.d.ts +4 -0
  19. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  20. package/out/zero-cache/src/config/zero-config.js +13 -0
  21. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  22. package/out/zero-cache/src/db/create.js +1 -1
  23. package/out/zero-cache/src/scripts/decommission.js +1 -1
  24. package/out/zero-cache/src/server/replicator.js +4 -2
  25. package/out/zero-cache/src/server/replicator.js.map +1 -1
  26. package/out/zero-cache/src/server/runner/run-worker.d.ts.map +1 -1
  27. package/out/zero-cache/src/server/runner/run-worker.js +8 -5
  28. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  29. package/out/zero-cache/src/server/serving-checkpointer.js +41 -0
  30. package/out/zero-cache/src/server/serving-checkpointer.js.map +1 -0
  31. package/out/zero-cache/src/server/worker-urls.d.ts +1 -0
  32. package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
  33. package/out/zero-cache/src/server/worker-urls.js +2 -1
  34. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  35. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
  36. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
  37. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +1 -1
  38. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +1 -1
  39. package/out/zero-cache/src/services/change-source/pg/schema/init.js +1 -1
  40. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +1 -1
  41. package/out/zero-cache/src/services/http-service.d.ts +6 -0
  42. package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
  43. package/out/zero-cache/src/services/http-service.js +13 -1
  44. package/out/zero-cache/src/services/http-service.js.map +1 -1
  45. package/out/zero-cache/src/services/life-cycle.d.ts +19 -0
  46. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/life-cycle.js +53 -1
  48. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  49. package/out/zero-cache/src/services/replicator/change-processor.js +1 -1
  50. package/out/zero-cache/src/services/replicator/serving-checkpointer.js +85 -0
  51. package/out/zero-cache/src/services/replicator/serving-checkpointer.js.map +1 -0
  52. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +101 -13
  53. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  54. package/out/zero-cache/src/services/view-syncer/query-covering.js +2 -2
  55. package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
  56. package/out/zero-cache/src/services/view-syncer/snapshotter.js +1 -1
  57. package/out/zero-cache/src/services/view-syncer/view-syncer.js +1 -1
  58. package/out/zero-cache/src/types/processes.js +4 -4
  59. package/out/zero-cache/src/types/processes.js.map +1 -1
  60. package/out/zero-cache/src/types/row-key.js +8 -1
  61. package/out/zero-cache/src/types/row-key.js.map +1 -1
  62. package/out/zero-cache/src/workers/connection.js +2 -2
  63. package/out/zero-cache/src/workers/replicator.js +9 -4
  64. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  65. package/out/zero-client/src/client/bindings.d.ts +1 -1
  66. package/out/zero-client/src/client/bindings.d.ts.map +1 -1
  67. package/out/zero-client/src/client/bindings.js +4 -4
  68. package/out/zero-client/src/client/context.d.ts +7 -1
  69. package/out/zero-client/src/client/context.d.ts.map +1 -1
  70. package/out/zero-client/src/client/context.js +7 -0
  71. package/out/zero-client/src/client/context.js.map +1 -1
  72. package/out/zero-client/src/client/crud-impl.d.ts.map +1 -1
  73. package/out/zero-client/src/client/crud-impl.js +171 -2
  74. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  75. package/out/zero-client/src/client/custom.d.ts.map +1 -1
  76. package/out/zero-client/src/client/custom.js +42 -3
  77. package/out/zero-client/src/client/custom.js.map +1 -1
  78. package/out/zero-client/src/client/ivm-branch.d.ts +62 -2
  79. package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
  80. package/out/zero-client/src/client/ivm-branch.js +152 -31
  81. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  82. package/out/zero-client/src/client/keys.d.ts +33 -0
  83. package/out/zero-client/src/client/keys.d.ts.map +1 -1
  84. package/out/zero-client/src/client/keys.js +43 -4
  85. package/out/zero-client/src/client/keys.js.map +1 -1
  86. package/out/zero-client/src/client/version.js +1 -1
  87. package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
  88. package/out/zero-client/src/client/zero-poke-handler.js +29 -1
  89. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  90. package/out/zero-client/src/client/zero-rep.d.ts +1 -1
  91. package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
  92. package/out/zero-client/src/client/zero-rep.js +2 -15
  93. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  94. package/out/zero-client/src/client/zero.js +1 -1
  95. package/out/zero-client/src/mod.d.ts +3 -3
  96. package/out/zero-client/src/mod.d.ts.map +1 -1
  97. package/out/zero-protocol/src/aggregate-table.d.ts +15 -0
  98. package/out/zero-protocol/src/aggregate-table.d.ts.map +1 -0
  99. package/out/zero-protocol/src/aggregate-table.js +5 -0
  100. package/out/zero-protocol/src/aggregate-table.js.map +1 -0
  101. package/out/zero-protocol/src/ast.d.ts +141 -5
  102. package/out/zero-protocol/src/ast.d.ts.map +1 -1
  103. package/out/zero-protocol/src/ast.js +244 -18
  104. package/out/zero-protocol/src/ast.js.map +1 -1
  105. package/out/zero-protocol/src/down.d.ts +29 -29
  106. package/out/zero-protocol/src/inspect-down.d.ts +81 -81
  107. package/out/zero-protocol/src/protocol-version.d.ts +1 -1
  108. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  109. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  110. package/out/zero-protocol/src/query-hash-visitor.d.ts.map +1 -1
  111. package/out/zero-protocol/src/query-hash-visitor.js +38 -0
  112. package/out/zero-protocol/src/query-hash-visitor.js.map +1 -1
  113. package/out/zero-react/src/bindings.js +4 -4
  114. package/out/zero-react/src/use-query.d.ts +3 -1
  115. package/out/zero-react/src/use-query.d.ts.map +1 -1
  116. package/out/zero-react/src/use-query.js +4 -2
  117. package/out/zero-react/src/use-query.js.map +1 -1
  118. package/out/zero-schema/src/builder/schema-builder.d.ts.map +1 -1
  119. package/out/zero-schema/src/builder/schema-builder.js +2 -0
  120. package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
  121. package/out/zero-schema/src/name-mapper.d.ts.map +1 -1
  122. package/out/zero-schema/src/name-mapper.js +7 -4
  123. package/out/zero-schema/src/name-mapper.js.map +1 -1
  124. package/out/zero-server/src/custom.js +1 -1
  125. package/out/zero-server/src/schema.d.ts.map +1 -1
  126. package/out/zero-server/src/schema.js +13 -2
  127. package/out/zero-server/src/schema.js.map +1 -1
  128. package/out/zero-solid/src/bindings.js +4 -4
  129. package/out/zero-solid/src/solid-view.d.ts.map +1 -1
  130. package/out/zero-solid/src/solid-view.js +3 -3
  131. package/out/zero-solid/src/solid-view.js.map +1 -1
  132. package/out/zero-types/src/format.d.ts +13 -0
  133. package/out/zero-types/src/format.d.ts.map +1 -1
  134. package/out/zero-types/src/format.js.map +1 -1
  135. package/out/zero-types/src/name-mapper.d.ts +14 -0
  136. package/out/zero-types/src/name-mapper.d.ts.map +1 -1
  137. package/out/zero-types/src/name-mapper.js +12 -0
  138. package/out/zero-types/src/name-mapper.js.map +1 -1
  139. package/out/zero-types/src/schema-value.d.ts +2 -0
  140. package/out/zero-types/src/schema-value.d.ts.map +1 -1
  141. package/out/zero-types/src/schema-value.js +15 -0
  142. package/out/zero-types/src/schema-value.js.map +1 -0
  143. package/out/zero-types/src/server-schema.d.ts +18 -0
  144. package/out/zero-types/src/server-schema.d.ts.map +1 -1
  145. package/out/zql/src/builder/builder.d.ts +126 -1
  146. package/out/zql/src/builder/builder.d.ts.map +1 -1
  147. package/out/zql/src/builder/builder.js +281 -11
  148. package/out/zql/src/builder/builder.js.map +1 -1
  149. package/out/zql/src/builder/filter.d.ts +1 -1
  150. package/out/zql/src/builder/filter.d.ts.map +1 -1
  151. package/out/zql/src/builder/filter.js +41 -3
  152. package/out/zql/src/builder/filter.js.map +1 -1
  153. package/out/zql/src/ivm/aggregate.d.ts +188 -0
  154. package/out/zql/src/ivm/aggregate.d.ts.map +1 -0
  155. package/out/zql/src/ivm/aggregate.js +470 -0
  156. package/out/zql/src/ivm/aggregate.js.map +1 -0
  157. package/out/zql/src/ivm/array-view.d.ts +1 -1
  158. package/out/zql/src/ivm/array-view.d.ts.map +1 -1
  159. package/out/zql/src/ivm/array-view.js +3 -3
  160. package/out/zql/src/ivm/array-view.js.map +1 -1
  161. package/out/zql/src/ivm/cap.js +2 -2
  162. package/out/zql/src/ivm/constraint.d.ts.map +1 -1
  163. package/out/zql/src/ivm/constraint.js +2 -1
  164. package/out/zql/src/ivm/constraint.js.map +1 -1
  165. package/out/zql/src/ivm/deferred-input.js +1 -1
  166. package/out/zql/src/ivm/fan-in.js +1 -1
  167. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  168. package/out/zql/src/ivm/flipped-join.js +3 -2
  169. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  170. package/out/zql/src/ivm/group-by.d.ts +98 -0
  171. package/out/zql/src/ivm/group-by.d.ts.map +1 -0
  172. package/out/zql/src/ivm/group-by.js +384 -0
  173. package/out/zql/src/ivm/group-by.js.map +1 -0
  174. package/out/zql/src/ivm/join-index.d.ts.map +1 -1
  175. package/out/zql/src/ivm/join-index.js +2 -1
  176. package/out/zql/src/ivm/join-index.js.map +1 -1
  177. package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
  178. package/out/zql/src/ivm/join-utils.js +2 -1
  179. package/out/zql/src/ivm/join-utils.js.map +1 -1
  180. package/out/zql/src/ivm/join.js +1 -1
  181. package/out/zql/src/ivm/lift-field.d.ts +54 -0
  182. package/out/zql/src/ivm/lift-field.d.ts.map +1 -0
  183. package/out/zql/src/ivm/lift-field.js +168 -0
  184. package/out/zql/src/ivm/lift-field.js.map +1 -0
  185. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +1 -1
  186. package/out/zql/src/ivm/memory-source.d.ts +2 -0
  187. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  188. package/out/zql/src/ivm/memory-source.js +9 -4
  189. package/out/zql/src/ivm/memory-source.js.map +1 -1
  190. package/out/zql/src/ivm/push-accumulated.js +1 -1
  191. package/out/zql/src/ivm/schema.d.ts +1 -0
  192. package/out/zql/src/ivm/schema.d.ts.map +1 -1
  193. package/out/zql/src/ivm/source.d.ts +21 -0
  194. package/out/zql/src/ivm/source.d.ts.map +1 -1
  195. package/out/zql/src/ivm/source.js.map +1 -1
  196. package/out/zql/src/ivm/take.js +1 -1
  197. package/out/zql/src/ivm/union-fan-in.js +1 -1
  198. package/out/zql/src/ivm/view-apply-change.d.ts +5 -0
  199. package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
  200. package/out/zql/src/ivm/view-apply-change.js +23 -3
  201. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  202. package/out/zql/src/planner/planner-debug.d.ts +1 -1
  203. package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
  204. package/out/zql/src/planner/planner-debug.js +4 -0
  205. package/out/zql/src/planner/planner-debug.js.map +1 -1
  206. package/out/zql/src/query/expression.d.ts +50 -3
  207. package/out/zql/src/query/expression.d.ts.map +1 -1
  208. package/out/zql/src/query/expression.js +62 -6
  209. package/out/zql/src/query/expression.js.map +1 -1
  210. package/out/zql/src/query/measure-push-operator.js +1 -1
  211. package/out/zql/src/query/query-delegate-base.d.ts +8 -0
  212. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  213. package/out/zql/src/query/query-delegate-base.js +13 -5
  214. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  215. package/out/zql/src/query/query-impl.d.ts +17 -3
  216. package/out/zql/src/query/query-impl.d.ts.map +1 -1
  217. package/out/zql/src/query/query-impl.js +239 -7
  218. package/out/zql/src/query/query-impl.js.map +1 -1
  219. package/out/zql/src/query/query.d.ts +319 -7
  220. package/out/zql/src/query/query.d.ts.map +1 -1
  221. package/out/zqlite/src/pending-delta.d.ts.map +1 -1
  222. package/out/zqlite/src/pending-delta.js +4 -3
  223. package/out/zqlite/src/pending-delta.js.map +1 -1
  224. package/out/zqlite/src/query-builder.d.ts +12 -1
  225. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  226. package/out/zqlite/src/query-builder.js +114 -13
  227. package/out/zqlite/src/query-builder.js.map +1 -1
  228. package/out/zqlite/src/table-source.d.ts.map +1 -1
  229. package/out/zqlite/src/table-source.js +37 -3
  230. package/out/zqlite/src/table-source.js.map +1 -1
  231. package/package.json +1 -1
@@ -49,7 +49,7 @@ function rootKey(ast) {
49
49
  ]);
50
50
  }
51
51
  function astCoveredBy(covered, covering) {
52
- return covered.schema === covering.schema && covered.table === covering.table && covered.alias === covering.alias && conditionImplies(covered.where, covering.where) && relatedCoveredBy(covered.related, covering.related) && boundsCoveredBy(covered, covering);
52
+ return covered.aggregate === void 0 && covering.aggregate === void 0 && covered.groupBy === void 0 && covering.groupBy === void 0 && covered.schema === covering.schema && covered.table === covering.table && covered.alias === covering.alias && conditionImplies(covered.where, covering.where) && relatedCoveredBy(covered.related, covering.related) && boundsCoveredBy(covered, covering);
53
53
  }
54
54
  function boundsCoveredBy(covered, covering) {
55
55
  if (covering.limit === void 0) {
@@ -85,7 +85,7 @@ function correlatedConditionImplies(covered, covering) {
85
85
  return astCoveredBy(covering.related.subquery, covered.related.subquery);
86
86
  }
87
87
  function sameRelatedEdge(a, b) {
88
- return jsonEqual(a.correlation, b.correlation) && a.hidden === b.hidden && a.system === b.system && a.subquery.alias === b.subquery.alias;
88
+ return a.aggregate === void 0 && b.aggregate === void 0 && jsonEqual(a.correlation, b.correlation) && a.hidden === b.hidden && a.system === b.system && a.subquery.alias === b.subquery.alias;
89
89
  }
90
90
  function simpleConditionImplies(covered, covering) {
91
91
  const coveredParts = columnLiteralParts(covered);
@@ -1 +1 @@
1
- {"version":3,"file":"query-covering.js","names":[],"sources":["../../../../../../zero-cache/src/services/view-syncer/query-covering.ts"],"sourcesContent":["import {\n deepEqual,\n type ReadonlyJSONValue,\n} from '../../../../shared/src/json.ts';\nimport {getOrInsertComputed, newMap} from '../../../../shared/src/map.ts';\nimport {\n normalizeAST,\n type AST,\n type Condition,\n type CorrelatedSubquery,\n type CorrelatedSubqueryCondition,\n type LiteralValue,\n type NormalizedAST,\n type SimpleCondition,\n} from '../../../../zero-protocol/src/ast.ts';\n\nexport type RunningQuery = {\n readonly transformedAst: AST;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\nexport type CoveringQuery = {\n readonly queryID: string;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\ntype NonNullScalarLiteralValue = string | number | boolean;\ntype IndexedRunningQuery = RunningQuery & {\n readonly normalizedAst: NormalizedAST;\n};\n\n/**\n * Returns true when every row that can be produced by `covered` is also\n * produced by `covering`.\n *\n * This is intentionally conservative: unsupported cases return false rather\n * than guessing. It is used for shadow logging only.\n */\nexport function isQueryCoveredBy(covered: AST, covering: AST): boolean {\n return astCoveredBy(normalizeAST(covered), normalizeAST(covering));\n}\n\nexport function findCoveringQuery(\n coveredQueryID: string,\n coveredAst: AST,\n runningQueries: ReadonlyMap<string, RunningQuery>,\n): CoveringQuery | undefined {\n return new QueryCoveringIndex(runningQueries).findCoveringQuery(\n coveredQueryID,\n coveredAst,\n );\n}\n\nexport class QueryCoveringIndex {\n readonly #byRoot = new Map<string, Map<string, IndexedRunningQuery>>();\n readonly #queryIDToRoot = new Map<string, string>();\n\n constructor(runningQueries?: ReadonlyMap<string, RunningQuery>) {\n if (runningQueries) {\n for (const [queryID, query] of runningQueries) {\n this.add(queryID, query);\n }\n }\n }\n\n add(queryID: string, query: RunningQuery): void {\n this.remove(queryID);\n\n const normalizedAst = normalizeAST(query.transformedAst);\n const root = rootKey(normalizedAst);\n getOrInsertComputed(this.#byRoot, root, newMap).set(queryID, {\n ...query,\n normalizedAst,\n });\n this.#queryIDToRoot.set(queryID, root);\n }\n\n remove(queryID: string): void {\n const root = this.#queryIDToRoot.get(queryID);\n if (root === undefined) {\n return;\n }\n\n this.#queryIDToRoot.delete(queryID);\n const queries = this.#byRoot.get(root);\n if (!queries) {\n return;\n }\n\n queries.delete(queryID);\n if (queries.size === 0) {\n this.#byRoot.delete(root);\n }\n }\n\n findCoveringQuery(\n coveredQueryID: string,\n coveredAst: AST,\n ): CoveringQuery | undefined {\n const normalizedCoveredAst = normalizeAST(coveredAst);\n const queries = this.#byRoot.get(rootKey(normalizedCoveredAst));\n if (!queries) {\n return undefined;\n }\n\n for (const [queryID, query] of queries) {\n if (queryID === coveredQueryID) {\n continue;\n }\n if (astCoveredBy(normalizedCoveredAst, query.normalizedAst)) {\n return {\n queryID,\n transformationHash: query.transformationHash,\n ...(query.queryName !== undefined && {queryName: query.queryName}),\n };\n }\n }\n return undefined;\n }\n}\n\nfunction rootKey(ast: NormalizedAST): string {\n return JSON.stringify([ast.schema, ast.table, ast.alias]);\n}\n\nfunction astCoveredBy(\n covered: NormalizedAST,\n covering: NormalizedAST,\n): boolean {\n return (\n covered.schema === covering.schema &&\n covered.table === covering.table &&\n covered.alias === covering.alias &&\n conditionImplies(covered.where, covering.where) &&\n relatedCoveredBy(covered.related, covering.related) &&\n boundsCoveredBy(covered, covering)\n );\n}\n\nfunction boundsCoveredBy(\n covered: NormalizedAST,\n covering: NormalizedAST,\n): boolean {\n if (covering.limit === undefined) {\n if (covering.start === undefined) {\n return true;\n }\n return (\n jsonEqual(covered.start, covering.start) &&\n jsonEqual(covered.orderBy, covering.orderBy)\n );\n }\n\n if (covered.limit === undefined || covering.limit < covered.limit) {\n return false;\n }\n\n // A limited broader query does not necessarily contain a limited narrower\n // query. The ordered input to the limit must be equivalent.\n return (\n conditionEquivalent(covered.where, covering.where) &&\n jsonEqual(covered.start, covering.start) &&\n jsonEqual(covered.orderBy, covering.orderBy)\n );\n}\n\nfunction relatedCoveredBy(\n covered: readonly CorrelatedSubquery[] | undefined,\n covering: readonly CorrelatedSubquery[] | undefined,\n): boolean {\n if (covered === undefined || covered.length === 0) {\n return true;\n }\n if (covering === undefined) {\n return false;\n }\n return covered.every(coveredRelated =>\n covering.some(\n coveringRelated =>\n sameRelatedEdge(coveredRelated, coveringRelated) &&\n astCoveredBy(\n coveredRelated.subquery as NormalizedAST,\n coveringRelated.subquery as NormalizedAST,\n ),\n ),\n );\n}\n\nfunction conditionEquivalent(\n a: Condition | undefined,\n b: Condition | undefined,\n): boolean {\n return conditionImplies(a, b) && conditionImplies(b, a);\n}\n\nfunction conditionImplies(\n covered: Condition | undefined,\n covering: Condition | undefined,\n): boolean {\n if (covering === undefined) {\n return true;\n }\n if (covered === undefined) {\n return false;\n }\n if (jsonEqual(covered, covering)) {\n return true;\n }\n\n if (covered.type === 'or') {\n return covered.conditions.every(c => conditionImplies(c, covering));\n }\n if (covering.type === 'or') {\n return covering.conditions.some(c => conditionImplies(covered, c));\n }\n if (covering.type === 'and') {\n return covering.conditions.every(c => conditionImplies(covered, c));\n }\n if (covered.type === 'and') {\n return covered.conditions.some(c => conditionImplies(c, covering));\n }\n if (covered.type === 'simple' && covering.type === 'simple') {\n return simpleConditionImplies(covered, covering);\n }\n if (\n covered.type === 'correlatedSubquery' &&\n covering.type === 'correlatedSubquery'\n ) {\n return correlatedConditionImplies(covered, covering);\n }\n return false;\n}\n\nfunction correlatedConditionImplies(\n covered: CorrelatedSubqueryCondition,\n covering: CorrelatedSubqueryCondition,\n): boolean {\n if (\n covered.op !== covering.op ||\n covered.scalar !== covering.scalar ||\n !sameRelatedEdge(covered.related, covering.related)\n ) {\n return false;\n }\n\n if (covered.op === 'EXISTS') {\n return astCoveredBy(\n covered.related.subquery as NormalizedAST,\n covering.related.subquery as NormalizedAST,\n );\n }\n\n return astCoveredBy(\n covering.related.subquery as NormalizedAST,\n covered.related.subquery as NormalizedAST,\n );\n}\n\nfunction sameRelatedEdge(\n a: CorrelatedSubquery,\n b: CorrelatedSubquery,\n): boolean {\n return (\n jsonEqual(a.correlation, b.correlation) &&\n a.hidden === b.hidden &&\n a.system === b.system &&\n a.subquery.alias === b.subquery.alias\n );\n}\n\nfunction simpleConditionImplies(\n covered: SimpleCondition,\n covering: SimpleCondition,\n): boolean {\n const coveredParts = columnLiteralParts(covered);\n const coveringParts = columnLiteralParts(covering);\n if (!coveredParts || !coveringParts) {\n return false;\n }\n if (coveredParts.column !== coveringParts.column) {\n return false;\n }\n\n const {op: coveredOp, value: coveredValue} = coveredParts;\n const {op: coveringOp, value: coveringValue} = coveringParts;\n\n if (isEqualityOp(coveredOp) && isNonNullScalarLiteralValue(coveredValue)) {\n return equalityImplies(coveredValue, coveringOp, coveringValue);\n }\n\n if (\n coveredOp === 'IN' &&\n coveringOp === 'IN' &&\n Array.isArray(coveredValue) &&\n Array.isArray(coveringValue)\n ) {\n return coveredValue.every(v => literalArrayIncludes(coveringValue, v));\n }\n\n if (isNumericOrderOp(coveredOp) && isNumericOrderOp(coveringOp)) {\n return orderConditionImplies(\n coveredOp,\n coveredValue,\n coveringOp,\n coveringValue,\n );\n }\n\n return false;\n}\n\nfunction equalityImplies(\n value: NonNullScalarLiteralValue,\n coveringOp: SimpleCondition['op'],\n coveringValue: LiteralValue,\n): boolean {\n switch (coveringOp) {\n case '=':\n case 'IS':\n return jsonEqual(value, coveringValue);\n case '!=':\n case 'IS NOT':\n return !jsonEqual(value, coveringValue);\n case 'IN':\n return (\n Array.isArray(coveringValue) &&\n literalArrayIncludes(coveringValue, value)\n );\n case '<':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value < coveringValue\n );\n case '<=':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value <= coveringValue\n );\n case '>':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value > coveringValue\n );\n case '>=':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value >= coveringValue\n );\n case 'NOT IN':\n case 'LIKE':\n case 'NOT LIKE':\n case 'ILIKE':\n case 'NOT ILIKE':\n return false;\n }\n}\n\nfunction orderConditionImplies(\n coveredOp: '<' | '>' | '<=' | '>=',\n coveredValue: LiteralValue,\n coveringOp: '<' | '>' | '<=' | '>=',\n coveringValue: LiteralValue,\n): boolean {\n if (typeof coveredValue !== 'number' || typeof coveringValue !== 'number') {\n return false;\n }\n\n switch (coveredOp) {\n case '>':\n return (\n (coveringOp === '>' && coveredValue >= coveringValue) ||\n (coveringOp === '>=' && coveredValue >= coveringValue)\n );\n case '>=':\n return (\n (coveringOp === '>' && coveredValue > coveringValue) ||\n (coveringOp === '>=' && coveredValue >= coveringValue)\n );\n case '<':\n return (\n (coveringOp === '<' && coveredValue <= coveringValue) ||\n (coveringOp === '<=' && coveredValue <= coveringValue)\n );\n case '<=':\n return (\n (coveringOp === '<' && coveredValue < coveringValue) ||\n (coveringOp === '<=' && coveredValue <= coveringValue)\n );\n }\n}\n\nfunction columnLiteralParts(condition: SimpleCondition):\n | {\n readonly column: string;\n readonly op: SimpleCondition['op'];\n readonly value: LiteralValue;\n }\n | undefined {\n if (condition.left.type !== 'column' || condition.right.type !== 'literal') {\n return undefined;\n }\n return {\n column: condition.left.name,\n op: condition.op,\n value: condition.right.value,\n };\n}\n\nfunction isEqualityOp(op: SimpleCondition['op']): op is '=' | 'IS' {\n return op === '=' || op === 'IS';\n}\n\nfunction isNumericOrderOp(\n op: SimpleCondition['op'],\n): op is '<' | '>' | '<=' | '>=' {\n return op === '<' || op === '>' || op === '<=' || op === '>=';\n}\n\nfunction isNonNullScalarLiteralValue(\n value: LiteralValue,\n): value is NonNullScalarLiteralValue {\n return value !== null && !Array.isArray(value);\n}\n\nfunction literalArrayIncludes(\n values: ReadonlyArray<string | number | boolean>,\n value: string | number | boolean | null,\n): boolean {\n return values.some(v => jsonEqual(v, value));\n}\n\nfunction jsonEqual(a: unknown, b: unknown): boolean {\n return deepEqual(\n a as ReadonlyJSONValue | undefined,\n b as ReadonlyJSONValue | undefined,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"query-covering.js","names":[],"sources":["../../../../../../zero-cache/src/services/view-syncer/query-covering.ts"],"sourcesContent":["import {\n deepEqual,\n type ReadonlyJSONValue,\n} from '../../../../shared/src/json.ts';\nimport {getOrInsertComputed, newMap} from '../../../../shared/src/map.ts';\nimport {\n normalizeAST,\n type AST,\n type Condition,\n type CorrelatedSubquery,\n type CorrelatedSubqueryCondition,\n type LiteralValue,\n type NormalizedAST,\n type SimpleCondition,\n} from '../../../../zero-protocol/src/ast.ts';\n\nexport type RunningQuery = {\n readonly transformedAst: AST;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\nexport type CoveringQuery = {\n readonly queryID: string;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\ntype NonNullScalarLiteralValue = string | number | boolean;\ntype IndexedRunningQuery = RunningQuery & {\n readonly normalizedAst: NormalizedAST;\n};\n\n/**\n * Returns true when every row that can be produced by `covered` is also\n * produced by `covering`.\n *\n * This is intentionally conservative: unsupported cases return false rather\n * than guessing. It is used for shadow logging only.\n */\nexport function isQueryCoveredBy(covered: AST, covering: AST): boolean {\n return astCoveredBy(normalizeAST(covered), normalizeAST(covering));\n}\n\nexport function findCoveringQuery(\n coveredQueryID: string,\n coveredAst: AST,\n runningQueries: ReadonlyMap<string, RunningQuery>,\n): CoveringQuery | undefined {\n return new QueryCoveringIndex(runningQueries).findCoveringQuery(\n coveredQueryID,\n coveredAst,\n );\n}\n\nexport class QueryCoveringIndex {\n readonly #byRoot = new Map<string, Map<string, IndexedRunningQuery>>();\n readonly #queryIDToRoot = new Map<string, string>();\n\n constructor(runningQueries?: ReadonlyMap<string, RunningQuery>) {\n if (runningQueries) {\n for (const [queryID, query] of runningQueries) {\n this.add(queryID, query);\n }\n }\n }\n\n add(queryID: string, query: RunningQuery): void {\n this.remove(queryID);\n\n const normalizedAst = normalizeAST(query.transformedAst);\n const root = rootKey(normalizedAst);\n getOrInsertComputed(this.#byRoot, root, newMap).set(queryID, {\n ...query,\n normalizedAst,\n });\n this.#queryIDToRoot.set(queryID, root);\n }\n\n remove(queryID: string): void {\n const root = this.#queryIDToRoot.get(queryID);\n if (root === undefined) {\n return;\n }\n\n this.#queryIDToRoot.delete(queryID);\n const queries = this.#byRoot.get(root);\n if (!queries) {\n return;\n }\n\n queries.delete(queryID);\n if (queries.size === 0) {\n this.#byRoot.delete(root);\n }\n }\n\n findCoveringQuery(\n coveredQueryID: string,\n coveredAst: AST,\n ): CoveringQuery | undefined {\n const normalizedCoveredAst = normalizeAST(coveredAst);\n const queries = this.#byRoot.get(rootKey(normalizedCoveredAst));\n if (!queries) {\n return undefined;\n }\n\n for (const [queryID, query] of queries) {\n if (queryID === coveredQueryID) {\n continue;\n }\n if (astCoveredBy(normalizedCoveredAst, query.normalizedAst)) {\n return {\n queryID,\n transformationHash: query.transformationHash,\n ...(query.queryName !== undefined && {queryName: query.queryName}),\n };\n }\n }\n return undefined;\n }\n}\n\nfunction rootKey(ast: NormalizedAST): string {\n return JSON.stringify([ast.schema, ast.table, ast.alias]);\n}\n\nfunction astCoveredBy(\n covered: NormalizedAST,\n covering: NormalizedAST,\n): boolean {\n // The result of an aggregate or a distinct lives in rows synthesized for\n // its own query, so no other query's rows can stand in for it, and such a\n // query does not sync the rows it reduces.\n return (\n covered.aggregate === undefined &&\n covering.aggregate === undefined &&\n covered.groupBy === undefined &&\n covering.groupBy === undefined &&\n covered.schema === covering.schema &&\n covered.table === covering.table &&\n covered.alias === covering.alias &&\n conditionImplies(covered.where, covering.where) &&\n relatedCoveredBy(covered.related, covering.related) &&\n boundsCoveredBy(covered, covering)\n );\n}\n\nfunction boundsCoveredBy(\n covered: NormalizedAST,\n covering: NormalizedAST,\n): boolean {\n if (covering.limit === undefined) {\n if (covering.start === undefined) {\n return true;\n }\n return (\n jsonEqual(covered.start, covering.start) &&\n jsonEqual(covered.orderBy, covering.orderBy)\n );\n }\n\n if (covered.limit === undefined || covering.limit < covered.limit) {\n return false;\n }\n\n // A limited broader query does not necessarily contain a limited narrower\n // query. The ordered input to the limit must be equivalent.\n return (\n conditionEquivalent(covered.where, covering.where) &&\n jsonEqual(covered.start, covering.start) &&\n jsonEqual(covered.orderBy, covering.orderBy)\n );\n}\n\nfunction relatedCoveredBy(\n covered: readonly CorrelatedSubquery[] | undefined,\n covering: readonly CorrelatedSubquery[] | undefined,\n): boolean {\n if (covered === undefined || covered.length === 0) {\n return true;\n }\n if (covering === undefined) {\n return false;\n }\n return covered.every(coveredRelated =>\n covering.some(\n coveringRelated =>\n sameRelatedEdge(coveredRelated, coveringRelated) &&\n astCoveredBy(\n coveredRelated.subquery as NormalizedAST,\n coveringRelated.subquery as NormalizedAST,\n ),\n ),\n );\n}\n\nfunction conditionEquivalent(\n a: Condition | undefined,\n b: Condition | undefined,\n): boolean {\n return conditionImplies(a, b) && conditionImplies(b, a);\n}\n\nfunction conditionImplies(\n covered: Condition | undefined,\n covering: Condition | undefined,\n): boolean {\n if (covering === undefined) {\n return true;\n }\n if (covered === undefined) {\n return false;\n }\n if (jsonEqual(covered, covering)) {\n return true;\n }\n\n if (covered.type === 'or') {\n return covered.conditions.every(c => conditionImplies(c, covering));\n }\n if (covering.type === 'or') {\n return covering.conditions.some(c => conditionImplies(covered, c));\n }\n if (covering.type === 'and') {\n return covering.conditions.every(c => conditionImplies(covered, c));\n }\n if (covered.type === 'and') {\n return covered.conditions.some(c => conditionImplies(c, covering));\n }\n if (covered.type === 'simple' && covering.type === 'simple') {\n return simpleConditionImplies(covered, covering);\n }\n if (\n covered.type === 'correlatedSubquery' &&\n covering.type === 'correlatedSubquery'\n ) {\n return correlatedConditionImplies(covered, covering);\n }\n return false;\n}\n\nfunction correlatedConditionImplies(\n covered: CorrelatedSubqueryCondition,\n covering: CorrelatedSubqueryCondition,\n): boolean {\n if (\n covered.op !== covering.op ||\n covered.scalar !== covering.scalar ||\n !sameRelatedEdge(covered.related, covering.related)\n ) {\n return false;\n }\n\n if (covered.op === 'EXISTS') {\n return astCoveredBy(\n covered.related.subquery as NormalizedAST,\n covering.related.subquery as NormalizedAST,\n );\n }\n\n return astCoveredBy(\n covering.related.subquery as NormalizedAST,\n covered.related.subquery as NormalizedAST,\n );\n}\n\nfunction sameRelatedEdge(\n a: CorrelatedSubquery,\n b: CorrelatedSubquery,\n): boolean {\n return (\n a.aggregate === undefined &&\n b.aggregate === undefined &&\n jsonEqual(a.correlation, b.correlation) &&\n a.hidden === b.hidden &&\n a.system === b.system &&\n a.subquery.alias === b.subquery.alias\n );\n}\n\nfunction simpleConditionImplies(\n covered: SimpleCondition,\n covering: SimpleCondition,\n): boolean {\n const coveredParts = columnLiteralParts(covered);\n const coveringParts = columnLiteralParts(covering);\n if (!coveredParts || !coveringParts) {\n return false;\n }\n if (coveredParts.column !== coveringParts.column) {\n return false;\n }\n\n const {op: coveredOp, value: coveredValue} = coveredParts;\n const {op: coveringOp, value: coveringValue} = coveringParts;\n\n if (isEqualityOp(coveredOp) && isNonNullScalarLiteralValue(coveredValue)) {\n return equalityImplies(coveredValue, coveringOp, coveringValue);\n }\n\n if (\n coveredOp === 'IN' &&\n coveringOp === 'IN' &&\n Array.isArray(coveredValue) &&\n Array.isArray(coveringValue)\n ) {\n return coveredValue.every(v => literalArrayIncludes(coveringValue, v));\n }\n\n if (isNumericOrderOp(coveredOp) && isNumericOrderOp(coveringOp)) {\n return orderConditionImplies(\n coveredOp,\n coveredValue,\n coveringOp,\n coveringValue,\n );\n }\n\n return false;\n}\n\nfunction equalityImplies(\n value: NonNullScalarLiteralValue,\n coveringOp: SimpleCondition['op'],\n coveringValue: LiteralValue,\n): boolean {\n switch (coveringOp) {\n case '=':\n case 'IS':\n return jsonEqual(value, coveringValue);\n case '!=':\n case 'IS NOT':\n return !jsonEqual(value, coveringValue);\n case 'IN':\n return (\n Array.isArray(coveringValue) &&\n literalArrayIncludes(coveringValue, value)\n );\n case '<':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value < coveringValue\n );\n case '<=':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value <= coveringValue\n );\n case '>':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value > coveringValue\n );\n case '>=':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value >= coveringValue\n );\n case 'NOT IN':\n case 'LIKE':\n case 'NOT LIKE':\n case 'ILIKE':\n case 'NOT ILIKE':\n return false;\n }\n}\n\nfunction orderConditionImplies(\n coveredOp: '<' | '>' | '<=' | '>=',\n coveredValue: LiteralValue,\n coveringOp: '<' | '>' | '<=' | '>=',\n coveringValue: LiteralValue,\n): boolean {\n if (typeof coveredValue !== 'number' || typeof coveringValue !== 'number') {\n return false;\n }\n\n switch (coveredOp) {\n case '>':\n return (\n (coveringOp === '>' && coveredValue >= coveringValue) ||\n (coveringOp === '>=' && coveredValue >= coveringValue)\n );\n case '>=':\n return (\n (coveringOp === '>' && coveredValue > coveringValue) ||\n (coveringOp === '>=' && coveredValue >= coveringValue)\n );\n case '<':\n return (\n (coveringOp === '<' && coveredValue <= coveringValue) ||\n (coveringOp === '<=' && coveredValue <= coveringValue)\n );\n case '<=':\n return (\n (coveringOp === '<' && coveredValue < coveringValue) ||\n (coveringOp === '<=' && coveredValue <= coveringValue)\n );\n }\n}\n\nfunction columnLiteralParts(condition: SimpleCondition):\n | {\n readonly column: string;\n readonly op: SimpleCondition['op'];\n readonly value: LiteralValue;\n }\n | undefined {\n if (condition.left.type !== 'column' || condition.right.type !== 'literal') {\n return undefined;\n }\n return {\n column: condition.left.name,\n op: condition.op,\n value: condition.right.value,\n };\n}\n\nfunction isEqualityOp(op: SimpleCondition['op']): op is '=' | 'IS' {\n return op === '=' || op === 'IS';\n}\n\nfunction isNumericOrderOp(\n op: SimpleCondition['op'],\n): op is '<' | '>' | '<=' | '>=' {\n return op === '<' || op === '>' || op === '<=' || op === '>=';\n}\n\nfunction isNonNullScalarLiteralValue(\n value: LiteralValue,\n): value is NonNullScalarLiteralValue {\n return value !== null && !Array.isArray(value);\n}\n\nfunction literalArrayIncludes(\n values: ReadonlyArray<string | number | boolean>,\n value: string | number | boolean | null,\n): boolean {\n return values.some(v => jsonEqual(v, value));\n}\n\nfunction jsonEqual(a: unknown, b: unknown): boolean {\n return deepEqual(\n a as ReadonlyJSONValue | undefined,\n b as ReadonlyJSONValue | undefined,\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { assert } from "../../../../shared/src/asserts.js";
2
2
  import { getOrInsertComputed } from "../../../../shared/src/map.js";
3
3
  import { stringify } from "../../../../shared/src/bigint-json.js";
4
+ import { id } from "../../types/sql.js";
4
5
  import { Database } from "../../../../zqlite/src/db.js";
5
6
  import { recordIndexUsage } from "../../../../zqlite/src/index-usage.js";
6
7
  import { fromSQLiteTypes } from "../../../../zqlite/src/table-source.js";
@@ -9,7 +10,6 @@ import { StatementRunner } from "../../db/statements.js";
9
10
  import { normalizedKeyOrder } from "../../types/row-key.js";
10
11
  import { parseChangeLogEntry } from "../replicator/schema/change-log.js";
11
12
  import { getReplicationState } from "../replicator/schema/replication-state.js";
12
- import { id } from "../../types/sql.js";
13
13
  //#region ../zero-cache/src/services/view-syncer/snapshotter.ts
14
14
  /**
15
15
  * A `Snapshotter` manages the progression of database snapshots for a
@@ -21,8 +21,8 @@ import { Subscription } from "../../types/subscription.js";
21
21
  import { CustomKeyMap } from "../../../../shared/src/custom-key-map.js";
22
22
  import { ttlClockAsNumber, ttlClockFromNumber } from "./ttl-clock.js";
23
23
  import { EMPTY_CVR_VERSION, cmpVersions, versionFromString, versionString, versionToCookie } from "./schema/types.js";
24
- import { isAuthErrorBody } from "../../auth/auth.js";
25
24
  import { LogThrottle } from "../../observability/log-throttle.js";
25
+ import { isAuthErrorBody } from "../../auth/auth.js";
26
26
  import { planWarningMessage } from "./plan-warnings.js";
27
27
  import { shouldCutOffPush } from "./push-cutoff.js";
28
28
  import { queryShape } from "./query-shape.js";
@@ -1,6 +1,6 @@
1
1
  import { pid } from "node:process";
2
2
  import { fork } from "node:child_process";
3
- import EventEmitter from "node:events";
3
+ import EventEmitter$1 from "node:events";
4
4
  import { platform } from "node:os";
5
5
  //#region ../zero-cache/src/types/processes.ts
6
6
  function getMessage(type, data) {
@@ -109,8 +109,8 @@ function forkChildWorkerWithArgs(moduleUrl, env, args) {
109
109
  * in a single process.
110
110
  */
111
111
  function inProcChannel() {
112
- const worker1 = new EventEmitter();
113
- const worker2 = new EventEmitter();
112
+ const worker1 = new EventEmitter$1();
113
+ const worker2 = new EventEmitter$1();
114
114
  const sendTo = (dest) => (message, sendHandle, callback) => {
115
115
  dest.emit("message", message, sendHandle);
116
116
  if (callback) callback(null);
@@ -135,7 +135,7 @@ function inProcChannel() {
135
135
  * `handleProfzRequest`) but needs to reach multiple child workers.
136
136
  */
137
137
  function broadcastWorker(workers) {
138
- const emitter = new EventEmitter();
138
+ const emitter = new EventEmitter$1();
139
139
  for (const w of workers) w.on("message", (message, sendHandle) => emitter.emit("message", message, sendHandle));
140
140
  const send = (message, sendHandle, callback) => {
141
141
  for (const w of workers) w.send(message, sendHandle);
@@ -1 +1 @@
1
- {"version":3,"file":"processes.js","names":[],"sources":["../../../../../zero-cache/src/types/processes.ts"],"sourcesContent":["import {\n type ChildProcess,\n fork,\n type SendHandle,\n type Serializable,\n} from 'node:child_process';\nimport EventEmitter from 'node:events';\nimport {platform} from 'node:os';\nimport {pid} from 'node:process';\n\n/**\n * Central registry of message type names, which are used to identify\n * the payload in {@link Message} objects sent between processes. The\n * payloads themselves are implementation specific and defined in each\n * component; only the type name is reserved here to avoid collisions.\n *\n * Receiving logic can call {@link getMessage()} with the name of\n * the message of interest to filter messages to those of interest.\n */\nexport const MESSAGE_TYPES = {\n handoff: 'handoff',\n status: 'status',\n clientGroupStatus: 'clientGroupStatus',\n subscribe: 'subscribe',\n notify: 'notify',\n ready: 'ready',\n backupWatermarkUpdate: 'backupWatermakUpdate',\n drainRequested: 'drainRequested',\n profile: 'profile',\n profileResponse: 'profileResponse',\n stop: 'stop',\n start: 'start',\n init: 'init',\n} as const;\n\nexport type ClientGroupStatus = {\n readonly clientGroupID: string;\n readonly active: boolean;\n readonly generation?: number | undefined;\n};\n\nexport type ClientGroupStatusMessage = ['clientGroupStatus', ClientGroupStatus];\n\nexport type ProfileRequest = {\n readonly id: string;\n readonly durationMs: number;\n readonly worker?: string | undefined;\n readonly workerIndex?: number | undefined;\n};\n\nexport type ProfileResponse = {\n readonly id: string;\n readonly name: string;\n readonly profile?: unknown | undefined;\n readonly error?: string | undefined;\n};\n\nexport type ProfileMessage = ['profile', ProfileRequest];\nexport type ProfileResponseMessage = ['profileResponse', ProfileResponse];\n\n/**\n * Sent by a worker (and relayed by `main` to the runner) to request that\n * the server drain itself for a restart, e.g. by a view-syncer's replicator\n * that has been granted permission to restore a new replica. See\n * `SelfDrain`.\n */\nexport type DrainRequestedMessage = ['drainRequested', object];\n\nexport type Message<Payload> = [keyof typeof MESSAGE_TYPES, Payload];\n\nfunction getMessage<M extends Message<unknown>>(\n type: M[0],\n data: unknown,\n): M[1] | null {\n if (Array.isArray(data) && data.length === 2 && data[0] === type) {\n return data[1] as M[1];\n }\n return null;\n}\n\n/**\n * Subscribes the `handler` to messages of the given `type` and returns a\n * function that unsubscribes it. Use this (rather than\n * {@link Receiver.onMessageType()}) for handlers scoped to a request or\n * operation rather than to the lifetime of the {@link Worker}; the\n * `'message'` listener is otherwise retained by the Worker forever.\n */\nexport function subscribeToMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n): () => void {\n const listener = (data: unknown, sendHandle?: SendHandle) => {\n const msg = getMessage(type, data);\n if (msg) {\n handler(msg, sendHandle);\n }\n };\n e.on('message', listener);\n return () => {\n e.off('message', listener);\n };\n}\n\nfunction onMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n) {\n subscribeToMessageType(e, type, handler);\n return e;\n}\n\nfunction onceMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n) {\n const listener = (data: unknown, sendHandle: SendHandle) => {\n const msg = getMessage(type, data);\n if (msg) {\n e.off('message', listener);\n handler(msg, sendHandle);\n }\n };\n return e.on('message', listener);\n}\n\nexport interface Sender {\n send<M extends Message<unknown>>(\n message: M,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ): boolean;\n\n kill(signal?: NodeJS.Signals): void;\n}\n\nexport interface Subprocess extends Sender, EventEmitter {\n pid?: number | undefined;\n}\n\nexport interface Receiver extends EventEmitter {\n /**\n * The receiving side of {@link Sender.send()} that is a wrapper around\n * {@link on}('message', ...) that invokes the `handler` for messages of\n * the specified `type`.\n */\n onMessageType<M extends Message<unknown>>(\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n ): this;\n\n /**\n * The receiving side of {@link Sender.send()} that behaves like\n * {@link once}('message', ...) that invokes the `handler` for the next\n * message of the specified `type` and then unsubscribes.\n */\n onceMessageType<M extends Message<unknown>>(\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n ): this;\n}\n\nexport interface Worker extends Subprocess, Receiver {}\n\n/**\n * Adds the {@link Receiver.onMessageType()} and {@link Receiver.onceMessageType()}\n * methods to convert the given `EventEmitter` to a `Receiver`.\n */\nfunction wrap<P extends EventEmitter>(proc: P): P & Receiver {\n return new Proxy(proc, {\n get(target: P, prop: string | symbol, receiver: unknown) {\n switch (prop) {\n case 'onMessageType':\n return (\n type: keyof typeof MESSAGE_TYPES,\n handler: (msg: unknown, sendHandle?: SendHandle) => void,\n ) => {\n onMessageType(target, type, handler);\n return receiver; // this\n };\n case 'onceMessageType':\n return (\n type: keyof typeof MESSAGE_TYPES,\n handler: (msg: unknown, sendHandle?: SendHandle) => void,\n ) => {\n onceMessageType(target, type, handler);\n return receiver; // this\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n }) as P & Receiver;\n}\n\ntype Proc = Pick<ChildProcess, 'send' | 'kill' | 'pid'> & EventEmitter;\n\n/**\n * The parentWorker for forked processes, or `null` if the process was not forked.\n * (Analogous to the `parentPort: MessagePort | null` of the `\"workers\"` library).\n */\nexport const parentWorker: Worker | null = process.send\n ? wrap(process as Proc)\n : null;\n\nconst SINGLE_PROCESS = 'SINGLE_PROCESS';\nlet singleProcessOverride = false;\nexport function singleProcessMode(): boolean {\n return singleProcessOverride || (process.env[SINGLE_PROCESS] ?? '0') !== '0';\n}\n\nexport function setSingleProcessMode(enabled: boolean = true): void {\n singleProcessOverride = enabled;\n}\n\n/**\n * Determines whether a module loaded as a worker process entry point should\n * start its worker.\n *\n * Most workers are launched via {@link childWorker}, which in single-process\n * mode runs them in-process (no real fork) by invoking their exported\n * `runWorker` directly; those entry points must therefore *not* self-start in\n * single-process mode, or they would run twice.\n *\n * A worker launched via {@link forkChildWorker}, however, is *always* a real\n * forked OS process — even in single-process mode — and it inherits the\n * parent's `SINGLE_PROCESS` env. So {@link singleProcessMode} alone would\n * wrongly suppress its startup, leaving the parent to spin re-forking a child\n * that immediately exits. Such a forked child has a non-null\n * {@link parentWorker} (`process.send` is defined), which is the signal to\n * start regardless. A standalone direct launch (no parent, not single-process)\n * also starts.\n *\n * @param parent The entry point's {@link parentWorker} (non-null iff forked).\n * @param isSingleProcessMode The result of {@link singleProcessMode}.\n */\nexport function shouldStartWorker(\n parent: Worker | null,\n isSingleProcessMode: boolean,\n): boolean {\n return parent !== null || !isSingleProcessMode;\n}\n\n/**\n *\n * @param modulePath Path to the module file, relative to zero-cache/src/, or an absolute file:// URL\n */\nexport function childWorker(\n moduleUrl: URL,\n env?: NodeJS.ProcessEnv,\n ...args: string[]\n): Worker {\n args = workerArgs(args);\n\n if (singleProcessMode()) {\n const [parent, child] = inProcChannel();\n import(moduleUrl.href)\n .then(async ({default: runWorker}) => {\n try {\n await runWorker(parent, env ?? process.env, ...args);\n child.emit('close', 0);\n return;\n } catch (err) {\n child.emit('error', err);\n child.emit('close', -1);\n }\n })\n .catch(err => child.emit('error', err));\n return child;\n }\n return forkChildWorkerWithArgs(moduleUrl, env, args);\n}\n\nexport function forkChildWorker(\n moduleUrl: URL,\n env?: NodeJS.ProcessEnv,\n ...args: string[]\n): Worker {\n return forkChildWorkerWithArgs(moduleUrl, env, workerArgs(args));\n}\n\nfunction workerArgs(args: string[]): string[] {\n return [...args, ...process.argv.slice(2)];\n}\n\nfunction forkChildWorkerWithArgs(\n moduleUrl: URL,\n env: NodeJS.ProcessEnv | undefined,\n args: string[],\n): Worker {\n const child = fork(moduleUrl, args, {\n // For production / non-windows, set `detached` to `true` so that SIGINT is\n // not automatically propagated and graceful shutdown happens as intended.\n // For Win32, detached: true causes all subprocesses to open in separate\n // terminals and breaks inter-process kill signals, so set it to false.\n detached: platform() !== 'win32',\n serialization: 'advanced', // use structured clone for IPC\n env,\n });\n return wrap(child);\n}\n\n/**\n * Creates two connected `Worker` instances such that messages sent to one\n * via the {@link Worker.send()} method are received by the other's\n * `on('message', ...)` handler.\n *\n * This is analogous to the two `MessagePort`s of a `MessageChannel`, and\n * is useful for executing code written for inter-process communication\n * in a single process.\n */\nexport function inProcChannel(): [Worker, Worker] {\n const worker1 = new EventEmitter();\n const worker2 = new EventEmitter();\n\n const sendTo =\n (dest: EventEmitter) =>\n (\n message: Serializable,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ) => {\n dest.emit('message', message, sendHandle);\n if (callback) {\n callback(null);\n }\n return true;\n };\n\n const kill =\n (dest: EventEmitter) =>\n (signal: NodeJS.Signals = 'SIGTERM') =>\n dest.emit(signal, signal);\n\n return [\n wrap(\n Object.assign(worker1, {send: sendTo(worker2), kill: kill(worker2), pid}),\n ),\n wrap(\n Object.assign(worker2, {send: sendTo(worker1), kill: kill(worker1), pid}),\n ),\n ];\n}\n\n/**\n * Creates a {@link Worker} facade that broadcasts `send()` to all provided\n * workers and aggregates their `'message'` events into one stream.\n *\n * This is useful for code that expects a single Worker for IPC (e.g.\n * `handleProfzRequest`) but needs to reach multiple child workers.\n */\nexport function broadcastWorker(workers: Worker[]): Worker {\n const emitter = new EventEmitter();\n\n for (const w of workers) {\n w.on('message', (message: Serializable, sendHandle?: SendHandle) =>\n emitter.emit('message', message, sendHandle),\n );\n }\n\n const send = <M extends Message<unknown>>(\n message: M,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ) => {\n for (const w of workers) {\n w.send(message, sendHandle);\n }\n if (callback) {\n callback(null);\n }\n return true;\n };\n\n const kill = (signal: NodeJS.Signals = 'SIGTERM') => {\n for (const w of workers) {\n w.kill(signal);\n }\n };\n\n return wrap(Object.assign(emitter, {send, kill, pid}));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"processes.js","names":["EventEmitter"],"sources":["../../../../../zero-cache/src/types/processes.ts"],"sourcesContent":["import {\n type ChildProcess,\n fork,\n type SendHandle,\n type Serializable,\n} from 'node:child_process';\nimport EventEmitter from 'node:events';\nimport {platform} from 'node:os';\nimport {pid} from 'node:process';\n\n/**\n * Central registry of message type names, which are used to identify\n * the payload in {@link Message} objects sent between processes. The\n * payloads themselves are implementation specific and defined in each\n * component; only the type name is reserved here to avoid collisions.\n *\n * Receiving logic can call {@link getMessage()} with the name of\n * the message of interest to filter messages to those of interest.\n */\nexport const MESSAGE_TYPES = {\n handoff: 'handoff',\n status: 'status',\n clientGroupStatus: 'clientGroupStatus',\n subscribe: 'subscribe',\n notify: 'notify',\n ready: 'ready',\n backupWatermarkUpdate: 'backupWatermakUpdate',\n drainRequested: 'drainRequested',\n profile: 'profile',\n profileResponse: 'profileResponse',\n stop: 'stop',\n start: 'start',\n init: 'init',\n} as const;\n\nexport type ClientGroupStatus = {\n readonly clientGroupID: string;\n readonly active: boolean;\n readonly generation?: number | undefined;\n};\n\nexport type ClientGroupStatusMessage = ['clientGroupStatus', ClientGroupStatus];\n\nexport type ProfileRequest = {\n readonly id: string;\n readonly durationMs: number;\n readonly worker?: string | undefined;\n readonly workerIndex?: number | undefined;\n};\n\nexport type ProfileResponse = {\n readonly id: string;\n readonly name: string;\n readonly profile?: unknown | undefined;\n readonly error?: string | undefined;\n};\n\nexport type ProfileMessage = ['profile', ProfileRequest];\nexport type ProfileResponseMessage = ['profileResponse', ProfileResponse];\n\n/**\n * Sent by a worker (and relayed by `main` to the runner) to request that\n * the server drain itself for a restart, e.g. by a view-syncer's replicator\n * that has been granted permission to restore a new replica. See\n * `SelfDrain`.\n */\nexport type DrainRequestedMessage = ['drainRequested', object];\n\nexport type Message<Payload> = [keyof typeof MESSAGE_TYPES, Payload];\n\nfunction getMessage<M extends Message<unknown>>(\n type: M[0],\n data: unknown,\n): M[1] | null {\n if (Array.isArray(data) && data.length === 2 && data[0] === type) {\n return data[1] as M[1];\n }\n return null;\n}\n\n/**\n * Subscribes the `handler` to messages of the given `type` and returns a\n * function that unsubscribes it. Use this (rather than\n * {@link Receiver.onMessageType()}) for handlers scoped to a request or\n * operation rather than to the lifetime of the {@link Worker}; the\n * `'message'` listener is otherwise retained by the Worker forever.\n */\nexport function subscribeToMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n): () => void {\n const listener = (data: unknown, sendHandle?: SendHandle) => {\n const msg = getMessage(type, data);\n if (msg) {\n handler(msg, sendHandle);\n }\n };\n e.on('message', listener);\n return () => {\n e.off('message', listener);\n };\n}\n\nfunction onMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n) {\n subscribeToMessageType(e, type, handler);\n return e;\n}\n\nfunction onceMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n) {\n const listener = (data: unknown, sendHandle: SendHandle) => {\n const msg = getMessage(type, data);\n if (msg) {\n e.off('message', listener);\n handler(msg, sendHandle);\n }\n };\n return e.on('message', listener);\n}\n\nexport interface Sender {\n send<M extends Message<unknown>>(\n message: M,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ): boolean;\n\n kill(signal?: NodeJS.Signals): void;\n}\n\nexport interface Subprocess extends Sender, EventEmitter {\n pid?: number | undefined;\n}\n\nexport interface Receiver extends EventEmitter {\n /**\n * The receiving side of {@link Sender.send()} that is a wrapper around\n * {@link on}('message', ...) that invokes the `handler` for messages of\n * the specified `type`.\n */\n onMessageType<M extends Message<unknown>>(\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n ): this;\n\n /**\n * The receiving side of {@link Sender.send()} that behaves like\n * {@link once}('message', ...) that invokes the `handler` for the next\n * message of the specified `type` and then unsubscribes.\n */\n onceMessageType<M extends Message<unknown>>(\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n ): this;\n}\n\nexport interface Worker extends Subprocess, Receiver {}\n\n/**\n * Adds the {@link Receiver.onMessageType()} and {@link Receiver.onceMessageType()}\n * methods to convert the given `EventEmitter` to a `Receiver`.\n */\nfunction wrap<P extends EventEmitter>(proc: P): P & Receiver {\n return new Proxy(proc, {\n get(target: P, prop: string | symbol, receiver: unknown) {\n switch (prop) {\n case 'onMessageType':\n return (\n type: keyof typeof MESSAGE_TYPES,\n handler: (msg: unknown, sendHandle?: SendHandle) => void,\n ) => {\n onMessageType(target, type, handler);\n return receiver; // this\n };\n case 'onceMessageType':\n return (\n type: keyof typeof MESSAGE_TYPES,\n handler: (msg: unknown, sendHandle?: SendHandle) => void,\n ) => {\n onceMessageType(target, type, handler);\n return receiver; // this\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n }) as P & Receiver;\n}\n\ntype Proc = Pick<ChildProcess, 'send' | 'kill' | 'pid'> & EventEmitter;\n\n/**\n * The parentWorker for forked processes, or `null` if the process was not forked.\n * (Analogous to the `parentPort: MessagePort | null` of the `\"workers\"` library).\n */\nexport const parentWorker: Worker | null = process.send\n ? wrap(process as Proc)\n : null;\n\nconst SINGLE_PROCESS = 'SINGLE_PROCESS';\nlet singleProcessOverride = false;\nexport function singleProcessMode(): boolean {\n return singleProcessOverride || (process.env[SINGLE_PROCESS] ?? '0') !== '0';\n}\n\nexport function setSingleProcessMode(enabled: boolean = true): void {\n singleProcessOverride = enabled;\n}\n\n/**\n * Determines whether a module loaded as a worker process entry point should\n * start its worker.\n *\n * Most workers are launched via {@link childWorker}, which in single-process\n * mode runs them in-process (no real fork) by invoking their exported\n * `runWorker` directly; those entry points must therefore *not* self-start in\n * single-process mode, or they would run twice.\n *\n * A worker launched via {@link forkChildWorker}, however, is *always* a real\n * forked OS process — even in single-process mode — and it inherits the\n * parent's `SINGLE_PROCESS` env. So {@link singleProcessMode} alone would\n * wrongly suppress its startup, leaving the parent to spin re-forking a child\n * that immediately exits. Such a forked child has a non-null\n * {@link parentWorker} (`process.send` is defined), which is the signal to\n * start regardless. A standalone direct launch (no parent, not single-process)\n * also starts.\n *\n * @param parent The entry point's {@link parentWorker} (non-null iff forked).\n * @param isSingleProcessMode The result of {@link singleProcessMode}.\n */\nexport function shouldStartWorker(\n parent: Worker | null,\n isSingleProcessMode: boolean,\n): boolean {\n return parent !== null || !isSingleProcessMode;\n}\n\n/**\n *\n * @param modulePath Path to the module file, relative to zero-cache/src/, or an absolute file:// URL\n */\nexport function childWorker(\n moduleUrl: URL,\n env?: NodeJS.ProcessEnv,\n ...args: string[]\n): Worker {\n args = workerArgs(args);\n\n if (singleProcessMode()) {\n const [parent, child] = inProcChannel();\n import(moduleUrl.href)\n .then(async ({default: runWorker}) => {\n try {\n await runWorker(parent, env ?? process.env, ...args);\n child.emit('close', 0);\n return;\n } catch (err) {\n child.emit('error', err);\n child.emit('close', -1);\n }\n })\n .catch(err => child.emit('error', err));\n return child;\n }\n return forkChildWorkerWithArgs(moduleUrl, env, args);\n}\n\nexport function forkChildWorker(\n moduleUrl: URL,\n env?: NodeJS.ProcessEnv,\n ...args: string[]\n): Worker {\n return forkChildWorkerWithArgs(moduleUrl, env, workerArgs(args));\n}\n\nfunction workerArgs(args: string[]): string[] {\n return [...args, ...process.argv.slice(2)];\n}\n\nfunction forkChildWorkerWithArgs(\n moduleUrl: URL,\n env: NodeJS.ProcessEnv | undefined,\n args: string[],\n): Worker {\n const child = fork(moduleUrl, args, {\n // For production / non-windows, set `detached` to `true` so that SIGINT is\n // not automatically propagated and graceful shutdown happens as intended.\n // For Win32, detached: true causes all subprocesses to open in separate\n // terminals and breaks inter-process kill signals, so set it to false.\n detached: platform() !== 'win32',\n serialization: 'advanced', // use structured clone for IPC\n env,\n });\n return wrap(child);\n}\n\n/**\n * Creates two connected `Worker` instances such that messages sent to one\n * via the {@link Worker.send()} method are received by the other's\n * `on('message', ...)` handler.\n *\n * This is analogous to the two `MessagePort`s of a `MessageChannel`, and\n * is useful for executing code written for inter-process communication\n * in a single process.\n */\nexport function inProcChannel(): [Worker, Worker] {\n const worker1 = new EventEmitter();\n const worker2 = new EventEmitter();\n\n const sendTo =\n (dest: EventEmitter) =>\n (\n message: Serializable,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ) => {\n dest.emit('message', message, sendHandle);\n if (callback) {\n callback(null);\n }\n return true;\n };\n\n const kill =\n (dest: EventEmitter) =>\n (signal: NodeJS.Signals = 'SIGTERM') =>\n dest.emit(signal, signal);\n\n return [\n wrap(\n Object.assign(worker1, {send: sendTo(worker2), kill: kill(worker2), pid}),\n ),\n wrap(\n Object.assign(worker2, {send: sendTo(worker1), kill: kill(worker1), pid}),\n ),\n ];\n}\n\n/**\n * Creates a {@link Worker} facade that broadcasts `send()` to all provided\n * workers and aggregates their `'message'` events into one stream.\n *\n * This is useful for code that expects a single Worker for IPC (e.g.\n * `handleProfzRequest`) but needs to reach multiple child workers.\n */\nexport function broadcastWorker(workers: Worker[]): Worker {\n const emitter = new EventEmitter();\n\n for (const w of workers) {\n w.on('message', (message: Serializable, sendHandle?: SendHandle) =>\n emitter.emit('message', message, sendHandle),\n );\n }\n\n const send = <M extends Message<unknown>>(\n message: M,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ) => {\n for (const w of workers) {\n w.send(message, sendHandle);\n }\n if (callback) {\n callback(null);\n }\n return true;\n };\n\n const kill = (signal: NodeJS.Signals = 'SIGTERM') => {\n for (const w of workers) {\n w.kill(signal);\n }\n };\n\n return wrap(Object.assign(emitter, {send, kill, pid}));\n}\n"],"mappings":"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"}
@@ -24,6 +24,13 @@ function normalizedKeyOrder(rowKey) {
24
24
  assert(!empty, "empty row key");
25
25
  return rowKey;
26
26
  }
27
+ /**
28
+ * Returns a normalized string suitable for representing a row key in a form
29
+ * that can be used as a Map key.
30
+ */
31
+ function rowKeyString(key) {
32
+ return stringify(tuples(key));
33
+ }
27
34
  function tuples(key) {
28
35
  return Object.entries(normalizedKeyOrder(key)).flat();
29
36
  }
@@ -43,6 +50,6 @@ function rowIDStringUncached(id) {
43
50
  ]);
44
51
  }
45
52
  //#endregion
46
- export { normalizedKeyOrder, rowIDString };
53
+ export { normalizedKeyOrder, rowIDString, rowKeyString };
47
54
 
48
55
  //# sourceMappingURL=row-key.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"row-key.js","names":[],"sources":["../../../../../zero-cache/src/types/row-key.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {stringify, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\n\nexport type RowKey = Readonly<Record<string, JSONValue>>;\n\nexport type RowID = Readonly<{schema: string; table: string; rowKey: RowKey}>;\n\nexport type RowValue = RowKey;\n\n/**\n * Returns the `RowKey` such that key iteration produces a sorted sequence. If the\n * keys are already sorted, the input is returned as is.\n *\n * Note that the value type is parameterized as `V` so that this method can be used\n * for both (pg) RowKeys and LiteRowKeys.\n */\nexport function normalizedKeyOrder<V>(\n rowKey: Readonly<Record<string, V>>,\n): Readonly<Record<string, V>> {\n let last = '';\n let empty = true;\n for (const col in rowKey) {\n empty = false;\n if (last > col) {\n const entries = Object.entries(rowKey).sort(([a], [b]) =>\n a < b ? -1 : a > b ? 1 : 0,\n );\n assert(entries.length > 0, 'empty row key');\n return Object.fromEntries(entries);\n }\n last = col;\n }\n assert(!empty, 'empty row key');\n // This case iterates over columns and avoids object allocations, which is\n // expected to be the common case (e.g. single column key).\n return rowKey;\n}\n\n/**\n * Returns a normalized string suitable for representing a row key in a form\n * that can be used as a Map key.\n */\nexport function rowKeyString(key: RowKey): string {\n return stringify(tuples(key));\n}\n\nfunction tuples(key: RowKey) {\n return Object.entries(normalizedKeyOrder(key)).flat();\n}\n\nconst rowIDStrings = new WeakMap<RowID, string>();\n\n/**\n * A normalized string representation of a {@link RowID} suitable to use\n * as a Map key.\n */\nexport function rowIDString(id: RowID): string {\n return getOrInsertComputed(rowIDStrings, id, rowIDStringUncached);\n}\n\nfunction rowIDStringUncached(id: RowID): string {\n return stringify([id.schema, id.table, ...tuples(id.rowKey)]);\n}\n"],"mappings":";;;;;;;;;;;AAiBA,SAAgB,mBACd,QAC6B;CAC7B,IAAI,OAAO;CACX,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,QAAQ;EACxB,QAAQ;EACR,IAAI,OAAO,KAAK;GACd,MAAM,UAAU,OAAO,QAAQ,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OACjD,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAC3B;GACA,OAAO,QAAQ,SAAS,GAAG,eAAe;GAC1C,OAAO,OAAO,YAAY,OAAO;EACnC;EACA,OAAO;CACT;CACA,OAAO,CAAC,OAAO,eAAe;CAG9B,OAAO;AACT;AAUA,SAAS,OAAO,KAAa;CAC3B,OAAO,OAAO,QAAQ,mBAAmB,GAAG,CAAC,CAAC,CAAC,KAAK;AACtD;AAEA,IAAM,+BAAe,IAAI,QAAuB;;;;;AAMhD,SAAgB,YAAY,IAAmB;CAC7C,OAAO,oBAAoB,cAAc,IAAI,mBAAmB;AAClE;AAEA,SAAS,oBAAoB,IAAmB;CAC9C,OAAO,UAAU;EAAC,GAAG;EAAQ,GAAG;EAAO,GAAG,OAAO,GAAG,MAAM;CAAC,CAAC;AAC9D"}
1
+ {"version":3,"file":"row-key.js","names":[],"sources":["../../../../../zero-cache/src/types/row-key.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {stringify, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\n\nexport type RowKey = Readonly<Record<string, JSONValue>>;\n\nexport type RowID = Readonly<{schema: string; table: string; rowKey: RowKey}>;\n\nexport type RowValue = RowKey;\n\n/**\n * Returns the `RowKey` such that key iteration produces a sorted sequence. If the\n * keys are already sorted, the input is returned as is.\n *\n * Note that the value type is parameterized as `V` so that this method can be used\n * for both (pg) RowKeys and LiteRowKeys.\n */\nexport function normalizedKeyOrder<V>(\n rowKey: Readonly<Record<string, V>>,\n): Readonly<Record<string, V>> {\n let last = '';\n let empty = true;\n for (const col in rowKey) {\n empty = false;\n if (last > col) {\n const entries = Object.entries(rowKey).sort(([a], [b]) =>\n a < b ? -1 : a > b ? 1 : 0,\n );\n assert(entries.length > 0, 'empty row key');\n return Object.fromEntries(entries);\n }\n last = col;\n }\n assert(!empty, 'empty row key');\n // This case iterates over columns and avoids object allocations, which is\n // expected to be the common case (e.g. single column key).\n return rowKey;\n}\n\n/**\n * Returns a normalized string suitable for representing a row key in a form\n * that can be used as a Map key.\n */\nexport function rowKeyString(key: RowKey): string {\n return stringify(tuples(key));\n}\n\nfunction tuples(key: RowKey) {\n return Object.entries(normalizedKeyOrder(key)).flat();\n}\n\nconst rowIDStrings = new WeakMap<RowID, string>();\n\n/**\n * A normalized string representation of a {@link RowID} suitable to use\n * as a Map key.\n */\nexport function rowIDString(id: RowID): string {\n return getOrInsertComputed(rowIDStrings, id, rowIDStringUncached);\n}\n\nfunction rowIDStringUncached(id: RowID): string {\n return stringify([id.schema, id.table, ...tuples(id.rowKey)]);\n}\n"],"mappings":";;;;;;;;;;;AAiBA,SAAgB,mBACd,QAC6B;CAC7B,IAAI,OAAO;CACX,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,QAAQ;EACxB,QAAQ;EACR,IAAI,OAAO,KAAK;GACd,MAAM,UAAU,OAAO,QAAQ,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OACjD,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAC3B;GACA,OAAO,QAAQ,SAAS,GAAG,eAAe;GAC1C,OAAO,OAAO,YAAY,OAAO;EACnC;EACA,OAAO;CACT;CACA,OAAO,CAAC,OAAO,eAAe;CAG9B,OAAO;AACT;;;;;AAMA,SAAgB,aAAa,KAAqB;CAChD,OAAO,UAAU,OAAO,GAAG,CAAC;AAC9B;AAEA,SAAS,OAAO,KAAa;CAC3B,OAAO,OAAO,QAAQ,mBAAmB,GAAG,CAAC,CAAC,CAAC,KAAK;AACtD;AAEA,IAAM,+BAAe,IAAI,QAAuB;;;;;AAMhD,SAAgB,YAAY,IAAmB;CAC7C,OAAO,oBAAoB,cAAc,IAAI,mBAAmB;AAClE;AAEA,SAAS,oBAAoB,IAAmB;CAC9C,OAAO,UAAU;EAAC,GAAG;EAAQ,GAAG;EAAO,GAAG,OAAO,GAAG,MAAM;CAAC,CAAC;AAC9D"}
@@ -79,9 +79,9 @@ var Connection = class {
79
79
  * will only parse messages with schema(s) of supported protocol versions.
80
80
  */
81
81
  init() {
82
- if (this.#protocolVersion > 53 || this.#protocolVersion < 30) this.#closeWithError({
82
+ if (this.#protocolVersion > 57 || this.#protocolVersion < 30) this.#closeWithError({
83
83
  kind: VersionNotSupported,
84
- message: `server is at sync protocol v53 and does not support v${this.#protocolVersion}. The ${this.#protocolVersion > 53 ? "server" : "client"} must be updated to a newer release.`,
84
+ message: `server is at sync protocol v57 and does not support v${this.#protocolVersion}. The ${this.#protocolVersion > 57 ? "server" : "client"} must be updated to a newer release.`,
85
85
  origin: ZeroCache
86
86
  });
87
87
  else {
@@ -90,7 +90,7 @@ async function prepare(lc, { file, vacuumIntervalHours }, walMode, mode) {
90
90
  }
91
91
  }
92
92
  await setJournalMode(lc, replica, walMode);
93
- const pragmas = getPragmaConfig(mode);
93
+ const pragmas = getPragmaConfig();
94
94
  applyPragmas(replica, pragmas);
95
95
  replica.pragma("optimize = 0x10002");
96
96
  lc.info?.(`optimized ${file}`);
@@ -119,15 +119,20 @@ async function setJournalMode(lc, replica, mode) {
119
119
  throw err;
120
120
  }
121
121
  /**
122
- * Returns the PragmaConfig for a given replica file mode.
122
+ * Returns the PragmaConfig for the replica.
123
123
  * This is used by both the main thread (setupReplica) and
124
124
  * the write worker thread to apply the same pragma settings.
125
+ *
126
+ * The writer never checkpoints, since an automatic checkpoint runs (and
127
+ * syncs the disk) within the COMMIT, stalling replication on a slow disk.
128
+ * Instead, litestream checkpoints the backup replica, and the
129
+ * `ServingCheckpointer` checkpoints the serving replica.
125
130
  */
126
- function getPragmaConfig(mode) {
131
+ function getPragmaConfig() {
127
132
  return {
128
133
  busyTimeout: 3e4,
129
134
  analysisLimit: 1e3,
130
- walAutocheckpoint: mode === "backup" ? 0 : void 0
135
+ walAutocheckpoint: 0
131
136
  };
132
137
  }
133
138
  function setupReplica(lc, mode, replicaOptions) {
@@ -1 +1 @@
1
- {"version":3,"file":"replicator.js","names":["v.literalUnion"],"sources":["../../../../../zero-cache/src/workers/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {ReplicaOptions} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {deleteChangeLogDB} from '../services/replicator/change-log-db.ts';\nimport {Notifier} from '../services/replicator/notifier.ts';\nimport type {\n ReplicaState,\n ReplicaStateNotifier,\n Replicator,\n} from '../services/replicator/replicator.ts';\nimport {\n getAscendingEvents,\n recordEvent,\n} from '../services/replicator/schema/replication-state.ts';\nimport {\n applyPragmas,\n type PragmaConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport type {Worker} from '../types/processes.ts';\n\nexport const replicaFileModeSchema = v.literalUnion(\n 'serving',\n 'serving-copy',\n 'backup',\n);\n\nexport type ReplicaFileMode = v.Infer<typeof replicaFileModeSchema>;\n\nexport type WalMode = 'wal' | 'wal2';\n\nexport function replicaFileName(replicaFile: string, mode: ReplicaFileMode) {\n return mode === 'serving-copy' ? `${replicaFile}-serving-copy` : replicaFile;\n}\n\n/**\n * Whether a replicator worker deletes the change-log file beside its replica at\n * startup.\n *\n * The change log belongs to the change-streamer, so this only ever cleans up a\n * file that nothing is writing. The predicate is deliberately the config flag\n * and nothing else: keying it on anything derived per replica -- a file mode, or\n * the retired `logsChangeStream` -- would make every replicator start unlink the\n * change-streamer's live log, because no replicator writes it any more. Paths\n * coincide whenever `fileMode !== 'serving-copy'`, so that hazard is real in the\n * shipped no-`backupURL` configuration.\n *\n * Deleting is safe when nothing writes the log: it is excluded from the\n * litestream backup, only the writer purges it, and re-enabling the writer\n * reseeds at the resume watermark.\n */\nexport function replicatorDeletesStaleChangeLog(\n sqliteChangeLogMode: string,\n): boolean {\n return sqliteChangeLogMode === 'off';\n}\n\n/**\n * The `mode=off` cleanup itself: deletes the change-log file beside `dbPath`\n * when {@link replicatorDeletesStaleChangeLog} says nothing writes it, and\n * reports whether it did. The decision and the delete live in one function so\n * that the guard test covers the call the replicator actually makes, not just\n * the predicate.\n */\nexport function deleteStaleChangeLog(\n sqliteChangeLogMode: string,\n dbPath: string,\n): boolean {\n if (!replicatorDeletesStaleChangeLog(sqliteChangeLogMode)) {\n return false;\n }\n deleteChangeLogDB(dbPath);\n return true;\n}\n\nconst MILLIS_PER_HOUR = 1000 * 60 * 60;\nconst MB = 1024 * 1024;\n\nasync function prepare(\n lc: LogContext,\n {file, vacuumIntervalHours}: ReplicaOptions,\n walMode: WalMode,\n mode: ReplicaFileMode,\n): Promise<{file: string; walMode: WalMode; pageSize: number}> {\n // Perform any upgrades to the replica in case the backup is an\n // earlier version.\n await upgradeReplica(lc, `${mode}-replica`, file);\n\n const replica = new Database(lc, file);\n let pageSize: number;\n try {\n // Switching between WAL modes (e.g. from a restored 'wal' replica to\n // 'wal2') goes through 'delete', which folds the WAL(2) files into the\n // main db. Leaving WAL mode requires the WAL not to be persisted (see\n // Database.persistWal()). A replica already in the target mode keeps its\n // WAL, from which a litestream backup may continue.\n const [{journal_mode: journalMode}] = replica.pragma<{\n journal_mode: string;\n }>('journal_mode');\n if (journalMode !== walMode) {\n replica.persistWal(false);\n try {\n await setJournalMode(lc, replica, 'delete');\n } finally {\n replica.persistWal(true);\n }\n }\n\n [{page_size: pageSize}] = replica.pragma<{page_size: number}>('page_size');\n const [{page_count: pageCount}] = replica.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{freelist_count: freelistCount}] = replica.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const dbSize = ((pageCount * pageSize) / MB).toFixed(2);\n const freelistSize = ((freelistCount * pageSize) / MB).toFixed(2);\n\n // TODO: Consider adding a freelist size or ratio based vacuum trigger.\n lc.info?.(`Size of db ${file}: ${dbSize} MB (${freelistSize} MB freeable)`);\n\n // Check for the VACUUM threshold.\n const events = getAscendingEvents(replica);\n lc.debug?.(`Runtime events for db ${file}`, {events});\n if (vacuumIntervalHours !== undefined) {\n const millisSinceLastEvent =\n Date.now() - (events.at(-1)?.timestamp.getTime() ?? 0);\n if (millisSinceLastEvent / MILLIS_PER_HOUR > vacuumIntervalHours) {\n lc.info?.(`Performing maintenance cleanup on ${file}`);\n const t0 = performance.now();\n replica.unsafeMode(true);\n // Leaving WAL mode requires the WAL not to be persisted (see\n // Database.persistWal()). The VACUUM rewrites the database outside\n // of the WAL, so a litestream backup cannot continue from it anyway.\n replica.persistWal(false);\n try {\n replica.pragma('journal_mode = OFF');\n replica.exec('VACUUM');\n recordEvent(replica, 'vacuum');\n const t1 = performance.now();\n lc.info?.(`VACUUM completed (${t1 - t0} ms)`);\n } catch (e) {\n // Another connection holds the replica open. The VACUUM is retried\n // at the next startup.\n if (!(e instanceof SqliteError && e.code === 'SQLITE_BUSY')) {\n throw e;\n }\n lc.warn?.(`Skipping VACUUM of ${file}: the database is locked`, e);\n } finally {\n replica.persistWal(true);\n replica.unsafeMode(false);\n }\n }\n }\n\n await setJournalMode(lc, replica, walMode);\n\n const pragmas = getPragmaConfig(mode);\n applyPragmas(replica, pragmas);\n\n replica.pragma('optimize = 0x10002');\n lc.info?.(`optimized ${file}`);\n } finally {\n replica.close();\n }\n return {file, walMode, pageSize};\n}\n\n// Setting the journal_mode requires an exclusive lock on the replica.\n// Add resilience against random replica reads (for stats, etc.) by\n// retrying if the database is locked. Note that the busy_timeout doesn't\n// work here.\nasync function setJournalMode(\n lc: LogContext,\n replica: Database,\n mode: 'delete' | 'wal' | 'wal2',\n) {\n lc.info?.(`setting ${replica.name} to ${mode} mode`);\n let err: unknown;\n for (let i = 0; i < 5; i++) {\n try {\n replica.pragma(`journal_mode = ${mode}`);\n return;\n } catch (e) {\n lc.warn?.(`error setting journal_mode to ${mode} (attempt ${i + 1})`, e);\n err = e;\n }\n await sleep(500);\n }\n throw err;\n}\n\n/**\n * Returns the PragmaConfig for a given replica file mode.\n * This is used by both the main thread (setupReplica) and\n * the write worker thread to apply the same pragma settings.\n */\nexport function getPragmaConfig(mode: ReplicaFileMode): PragmaConfig {\n return {\n busyTimeout: 30000,\n analysisLimit: 1000,\n walAutocheckpoint: mode === 'backup' ? 0 : undefined,\n };\n}\n\nexport function setupReplica(\n lc: LogContext,\n mode: ReplicaFileMode,\n replicaOptions: ReplicaOptions,\n) {\n lc.info?.(`setting up ${mode} replica`);\n\n switch (mode) {\n case 'backup':\n return prepare(lc, replicaOptions, 'wal', mode);\n\n case 'serving-copy': {\n // In 'serving-copy' mode, the original file is being used for 'backup'\n // mode, so we make a copy for servicing sync requests.\n const {file} = replicaOptions;\n const copyLocation = replicaFileName(file, mode);\n deleteLiteDB(copyLocation);\n\n const start = Date.now();\n lc.info?.(`copying ${file} to ${copyLocation}`);\n const replica = new Database(lc, file);\n replica.prepare(`VACUUM INTO ?`).run(copyLocation);\n replica.close();\n lc.info?.(`finished copy (${Date.now() - start} ms)`);\n\n return prepare(lc, {...replicaOptions, file: copyLocation}, 'wal2', mode);\n }\n\n case 'serving':\n return prepare(lc, replicaOptions, 'wal2', mode);\n\n default:\n throw new Error(`Invalid ReplicaMode ${mode}`);\n }\n}\n\nexport function setUpMessageHandlers(\n lc: LogContext,\n replicator: Replicator,\n parent: Worker,\n) {\n handleSubscriptionsFrom(lc, parent, replicator);\n}\n\ntype Notification = ['notify', ReplicaState];\n\nexport function handleSubscriptionsFrom(\n lc: LogContext,\n subscriber: Worker,\n notifier: ReplicaStateNotifier,\n) {\n subscriber.onMessageType('subscribe', async () => {\n const subscription = notifier.subscribe();\n\n subscriber.on('close', () => {\n lc.debug?.(`closing replication subscription from ${subscriber.pid}`);\n subscription.cancel();\n });\n\n for await (const msg of subscription) {\n try {\n subscriber.send<Notification>(['notify', msg]);\n } catch (e) {\n const log =\n e instanceof Error &&\n 'code' in e &&\n // This can happen in a race condition if the subscribing process\n // is closed before the 'close' message is processed.\n e.code === 'ERR_IPC_CHANNEL_CLOSED'\n ? 'warn'\n : 'error';\n\n lc[log]?.(\n `error sending replicator notification to ${subscriber.pid}: ${String(e)}`,\n e,\n );\n }\n }\n });\n}\n\n/**\n * Creates a Notifier to relay notifications the notifier of another Worker.\n * This does not send the initial subscription message. Use {@link subscribeTo}\n * to initiate the subscription.\n */\nexport function createNotifierFrom(\n _lc: LogContext,\n source: Worker,\n onNotify?: (state: ReplicaState) => void,\n): Notifier {\n const notifier = new Notifier();\n source.onMessageType<Notification>('notify', msg => {\n onNotify?.(msg);\n void notifier.notifySubscribers(msg);\n });\n return notifier;\n}\n\nexport function subscribeTo(_lc: LogContext, source: Worker) {\n source.send(['subscribe', {}]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"replicator.js","names":["v.literalUnion"],"sources":["../../../../../zero-cache/src/workers/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {ReplicaOptions} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {deleteChangeLogDB} from '../services/replicator/change-log-db.ts';\nimport {Notifier} from '../services/replicator/notifier.ts';\nimport type {\n ReplicaState,\n ReplicaStateNotifier,\n Replicator,\n} from '../services/replicator/replicator.ts';\nimport {\n getAscendingEvents,\n recordEvent,\n} from '../services/replicator/schema/replication-state.ts';\nimport {\n applyPragmas,\n type PragmaConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport type {Worker} from '../types/processes.ts';\n\nexport const replicaFileModeSchema = v.literalUnion(\n 'serving',\n 'serving-copy',\n 'backup',\n);\n\nexport type ReplicaFileMode = v.Infer<typeof replicaFileModeSchema>;\n\nexport type WalMode = 'wal' | 'wal2';\n\nexport function replicaFileName(replicaFile: string, mode: ReplicaFileMode) {\n return mode === 'serving-copy' ? `${replicaFile}-serving-copy` : replicaFile;\n}\n\n/**\n * Whether a replicator worker deletes the change-log file beside its replica at\n * startup.\n *\n * The change log belongs to the change-streamer, so this only ever cleans up a\n * file that nothing is writing. The predicate is deliberately the config flag\n * and nothing else: keying it on anything derived per replica -- a file mode, or\n * the retired `logsChangeStream` -- would make every replicator start unlink the\n * change-streamer's live log, because no replicator writes it any more. Paths\n * coincide whenever `fileMode !== 'serving-copy'`, so that hazard is real in the\n * shipped no-`backupURL` configuration.\n *\n * Deleting is safe when nothing writes the log: it is excluded from the\n * litestream backup, only the writer purges it, and re-enabling the writer\n * reseeds at the resume watermark.\n */\nexport function replicatorDeletesStaleChangeLog(\n sqliteChangeLogMode: string,\n): boolean {\n return sqliteChangeLogMode === 'off';\n}\n\n/**\n * The `mode=off` cleanup itself: deletes the change-log file beside `dbPath`\n * when {@link replicatorDeletesStaleChangeLog} says nothing writes it, and\n * reports whether it did. The decision and the delete live in one function so\n * that the guard test covers the call the replicator actually makes, not just\n * the predicate.\n */\nexport function deleteStaleChangeLog(\n sqliteChangeLogMode: string,\n dbPath: string,\n): boolean {\n if (!replicatorDeletesStaleChangeLog(sqliteChangeLogMode)) {\n return false;\n }\n deleteChangeLogDB(dbPath);\n return true;\n}\n\nconst MILLIS_PER_HOUR = 1000 * 60 * 60;\nconst MB = 1024 * 1024;\n\nasync function prepare(\n lc: LogContext,\n {file, vacuumIntervalHours}: ReplicaOptions,\n walMode: WalMode,\n mode: ReplicaFileMode,\n): Promise<{file: string; walMode: WalMode; pageSize: number}> {\n // Perform any upgrades to the replica in case the backup is an\n // earlier version.\n await upgradeReplica(lc, `${mode}-replica`, file);\n\n const replica = new Database(lc, file);\n let pageSize: number;\n try {\n // Switching between WAL modes (e.g. from a restored 'wal' replica to\n // 'wal2') goes through 'delete', which folds the WAL(2) files into the\n // main db. Leaving WAL mode requires the WAL not to be persisted (see\n // Database.persistWal()). A replica already in the target mode keeps its\n // WAL, from which a litestream backup may continue.\n const [{journal_mode: journalMode}] = replica.pragma<{\n journal_mode: string;\n }>('journal_mode');\n if (journalMode !== walMode) {\n replica.persistWal(false);\n try {\n await setJournalMode(lc, replica, 'delete');\n } finally {\n replica.persistWal(true);\n }\n }\n\n [{page_size: pageSize}] = replica.pragma<{page_size: number}>('page_size');\n const [{page_count: pageCount}] = replica.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{freelist_count: freelistCount}] = replica.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const dbSize = ((pageCount * pageSize) / MB).toFixed(2);\n const freelistSize = ((freelistCount * pageSize) / MB).toFixed(2);\n\n // TODO: Consider adding a freelist size or ratio based vacuum trigger.\n lc.info?.(`Size of db ${file}: ${dbSize} MB (${freelistSize} MB freeable)`);\n\n // Check for the VACUUM threshold.\n const events = getAscendingEvents(replica);\n lc.debug?.(`Runtime events for db ${file}`, {events});\n if (vacuumIntervalHours !== undefined) {\n const millisSinceLastEvent =\n Date.now() - (events.at(-1)?.timestamp.getTime() ?? 0);\n if (millisSinceLastEvent / MILLIS_PER_HOUR > vacuumIntervalHours) {\n lc.info?.(`Performing maintenance cleanup on ${file}`);\n const t0 = performance.now();\n replica.unsafeMode(true);\n // Leaving WAL mode requires the WAL not to be persisted (see\n // Database.persistWal()). The VACUUM rewrites the database outside\n // of the WAL, so a litestream backup cannot continue from it anyway.\n replica.persistWal(false);\n try {\n replica.pragma('journal_mode = OFF');\n replica.exec('VACUUM');\n recordEvent(replica, 'vacuum');\n const t1 = performance.now();\n lc.info?.(`VACUUM completed (${t1 - t0} ms)`);\n } catch (e) {\n // Another connection holds the replica open. The VACUUM is retried\n // at the next startup.\n if (!(e instanceof SqliteError && e.code === 'SQLITE_BUSY')) {\n throw e;\n }\n lc.warn?.(`Skipping VACUUM of ${file}: the database is locked`, e);\n } finally {\n replica.persistWal(true);\n replica.unsafeMode(false);\n }\n }\n }\n\n await setJournalMode(lc, replica, walMode);\n\n const pragmas = getPragmaConfig();\n applyPragmas(replica, pragmas);\n\n replica.pragma('optimize = 0x10002');\n lc.info?.(`optimized ${file}`);\n } finally {\n replica.close();\n }\n return {file, walMode, pageSize};\n}\n\n// Setting the journal_mode requires an exclusive lock on the replica.\n// Add resilience against random replica reads (for stats, etc.) by\n// retrying if the database is locked. Note that the busy_timeout doesn't\n// work here.\nasync function setJournalMode(\n lc: LogContext,\n replica: Database,\n mode: 'delete' | 'wal' | 'wal2',\n) {\n lc.info?.(`setting ${replica.name} to ${mode} mode`);\n let err: unknown;\n for (let i = 0; i < 5; i++) {\n try {\n replica.pragma(`journal_mode = ${mode}`);\n return;\n } catch (e) {\n lc.warn?.(`error setting journal_mode to ${mode} (attempt ${i + 1})`, e);\n err = e;\n }\n await sleep(500);\n }\n throw err;\n}\n\n/**\n * Returns the PragmaConfig for the replica.\n * This is used by both the main thread (setupReplica) and\n * the write worker thread to apply the same pragma settings.\n *\n * The writer never checkpoints, since an automatic checkpoint runs (and\n * syncs the disk) within the COMMIT, stalling replication on a slow disk.\n * Instead, litestream checkpoints the backup replica, and the\n * `ServingCheckpointer` checkpoints the serving replica.\n */\nexport function getPragmaConfig(): PragmaConfig {\n return {\n busyTimeout: 30000,\n analysisLimit: 1000,\n walAutocheckpoint: 0,\n };\n}\n\nexport function setupReplica(\n lc: LogContext,\n mode: ReplicaFileMode,\n replicaOptions: ReplicaOptions,\n) {\n lc.info?.(`setting up ${mode} replica`);\n\n switch (mode) {\n case 'backup':\n return prepare(lc, replicaOptions, 'wal', mode);\n\n case 'serving-copy': {\n // In 'serving-copy' mode, the original file is being used for 'backup'\n // mode, so we make a copy for servicing sync requests.\n const {file} = replicaOptions;\n const copyLocation = replicaFileName(file, mode);\n deleteLiteDB(copyLocation);\n\n const start = Date.now();\n lc.info?.(`copying ${file} to ${copyLocation}`);\n const replica = new Database(lc, file);\n replica.prepare(`VACUUM INTO ?`).run(copyLocation);\n replica.close();\n lc.info?.(`finished copy (${Date.now() - start} ms)`);\n\n return prepare(lc, {...replicaOptions, file: copyLocation}, 'wal2', mode);\n }\n\n case 'serving':\n return prepare(lc, replicaOptions, 'wal2', mode);\n\n default:\n throw new Error(`Invalid ReplicaMode ${mode}`);\n }\n}\n\nexport function setUpMessageHandlers(\n lc: LogContext,\n replicator: Replicator,\n parent: Worker,\n) {\n handleSubscriptionsFrom(lc, parent, replicator);\n}\n\ntype Notification = ['notify', ReplicaState];\n\nexport function handleSubscriptionsFrom(\n lc: LogContext,\n subscriber: Worker,\n notifier: ReplicaStateNotifier,\n) {\n subscriber.onMessageType('subscribe', async () => {\n const subscription = notifier.subscribe();\n\n subscriber.on('close', () => {\n lc.debug?.(`closing replication subscription from ${subscriber.pid}`);\n subscription.cancel();\n });\n\n for await (const msg of subscription) {\n try {\n subscriber.send<Notification>(['notify', msg]);\n } catch (e) {\n const log =\n e instanceof Error &&\n 'code' in e &&\n // This can happen in a race condition if the subscribing process\n // is closed before the 'close' message is processed.\n e.code === 'ERR_IPC_CHANNEL_CLOSED'\n ? 'warn'\n : 'error';\n\n lc[log]?.(\n `error sending replicator notification to ${subscriber.pid}: ${String(e)}`,\n e,\n );\n }\n }\n });\n}\n\n/**\n * Creates a Notifier to relay notifications the notifier of another Worker.\n * This does not send the initial subscription message. Use {@link subscribeTo}\n * to initiate the subscription.\n */\nexport function createNotifierFrom(\n _lc: LogContext,\n source: Worker,\n onNotify?: (state: ReplicaState) => void,\n): Notifier {\n const notifier = new Notifier();\n source.onMessageType<Notification>('notify', msg => {\n onNotify?.(msg);\n void notifier.notifySubscribers(msg);\n });\n return notifier;\n}\n\nexport function subscribeTo(_lc: LogContext, source: Worker) {\n source.send(['subscribe', {}]);\n}\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ export type { Immutable } from '../../../shared/src/immutable.ts';
4
4
  export { must } from '../../../shared/src/must.ts';
5
5
  export { skipYields } from '../../../zql/src/ivm/operator.ts';
6
6
  export { consume } from '../../../zql/src/ivm/stream.ts';
7
- export { applyChange, idSymbol, refCountSymbol, type ViewChange, } from '../../../zql/src/ivm/view-apply-change.ts';
7
+ export { applyChange, emptyViewValue, idSymbol, refCountSymbol, type ViewChange, } from '../../../zql/src/ivm/view-apply-change.ts';
8
8
  export type { QueryDelegate } from '../../../zql/src/query/query-delegate.ts';
9
9
  export { newQuery, type QueryImpl } from '../../../zql/src/query/query-impl.ts';
10
10
  export { asQueryInternals, queryInternalsTag, } from '../../../zql/src/query/query-internals.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/bindings.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,MAAM,EAAE,WAAW,EAAC,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAC,SAAS,EAAC,MAAM,mCAAmC,CAAC;AAC5D,YAAY,EAAC,SAAS,EAAC,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AACjD,OAAO,EAAC,UAAU,EAAC,MAAM,kCAAkC,CAAC;AAC5D,OAAO,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AACvD,OAAO,EACL,WAAW,EACX,QAAQ,EACR,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,2CAA2C,CAAC;AACnD,YAAY,EAAC,aAAa,EAAC,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAC,QAAQ,EAAE,KAAK,SAAS,EAAC,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EACL,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAC,iBAAiB,EAAC,MAAM,0CAA0C,CAAC;AAC3E,OAAO,EAAC,cAAc,EAAC,MAAM,+BAA+B,CAAC"}
1
+ {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/bindings.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,MAAM,EAAE,WAAW,EAAC,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAC,SAAS,EAAC,MAAM,mCAAmC,CAAC;AAC5D,YAAY,EAAC,SAAS,EAAC,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AACjD,OAAO,EAAC,UAAU,EAAC,MAAM,kCAAkC,CAAC;AAC5D,OAAO,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AACvD,OAAO,EACL,WAAW,EACX,cAAc,EACd,QAAQ,EACR,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,2CAA2C,CAAC;AACnD,YAAY,EAAC,aAAa,EAAC,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAC,QAAQ,EAAE,KAAK,SAAS,EAAC,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EACL,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAC,iBAAiB,EAAC,MAAM,0CAA0C,CAAC;AAC3E,OAAO,EAAC,cAAc,EAAC,MAAM,+BAA+B,CAAC"}
@@ -1,11 +1,11 @@
1
1
  import { assert, unreachable } from "../../../shared/src/asserts.js";
2
2
  import { must } from "../../../shared/src/must.js";
3
+ import { skipYields } from "../../../zql/src/ivm/skip-yields.js";
4
+ import { consume } from "../../../zql/src/ivm/stream.js";
3
5
  import { asQueryInternals, queryInternalsTag } from "../../../zql/src/query/query-internals.js";
4
6
  import { newQuery } from "../../../zql/src/query/query-impl.js";
5
7
  import { addContextToQuery } from "../../../zql/src/query/query-registry.js";
6
- import { skipYields } from "../../../zql/src/ivm/skip-yields.js";
7
- import { consume } from "../../../zql/src/ivm/stream.js";
8
- import { applyChange, idSymbol, refCountSymbol } from "../../../zql/src/ivm/view-apply-change.js";
8
+ import { applyChange, emptyViewValue, idSymbol, refCountSymbol } from "../../../zql/src/ivm/view-apply-change.js";
9
9
  import { DEFAULT_TTL_MS } from "../../../zql/src/query/ttl.js";
10
10
  import { deepClone } from "../../../shared/src/deep-clone.js";
11
- export { DEFAULT_TTL_MS, addContextToQuery, applyChange, asQueryInternals, assert, consume, deepClone, idSymbol, must, newQuery, queryInternalsTag, refCountSymbol, skipYields, unreachable };
11
+ export { DEFAULT_TTL_MS, addContextToQuery, applyChange, asQueryInternals, assert, consume, deepClone, emptyViewValue, idSymbol, must, newQuery, queryInternalsTag, refCountSymbol, skipYields, unreachable };
@@ -2,7 +2,10 @@ import type { LogContext } from '@rocicorp/logger';
2
2
  import type { NoIndexDiff } from '../../../replicache/src/btree/node.ts';
3
3
  import type { Hash } from '../../../replicache/src/hash.ts';
4
4
  import type { DocumentVisibilityWatcher } from '../../../shared/src/document-visible.ts';
5
- import type { AST } from '../../../zero-protocol/src/ast.ts';
5
+ import type { AST, CompoundKey } from '../../../zero-protocol/src/ast.ts';
6
+ import type { PrimaryKey } from '../../../zero-protocol/src/primary-key.ts';
7
+ import type { SchemaValue } from '../../../zero-types/src/schema-value.ts';
8
+ import type { AggregateOptimisticDelta } from '../../../zql/src/builder/builder.ts';
6
9
  import type { DebugDelegate } from '../../../zql/src/builder/debug-delegate.ts';
7
10
  import type { Input } from '../../../zql/src/ivm/operator.ts';
8
11
  import type { Source, SourceInput } from '../../../zql/src/ivm/source.ts';
@@ -39,6 +42,7 @@ export declare class ZeroContext extends QueryDelegateBase {
39
42
  constructor(lc: LogContext, mainSources: IVMSourceBranch, addQuery: AddQuery, addCustomQuery: AddCustomQuery, updateQuery: UpdateQuery, updateCustomQuery: UpdateCustomQuery, flushQueryChanges: () => void, batchViewUpdates: (applyViewUpdates: () => void) => void, addMetric: MetricsDelegate['addMetric'], assertValidRunOptions: (options?: RunOptions) => void, visibilityWatcher?: DocumentVisibilityWatcher | undefined);
40
43
  applyFiltersAnyway?: boolean | undefined;
41
44
  debug?: DebugDelegate | undefined;
45
+ readonly aggregatesFromSource: boolean;
42
46
  getSource(name: string): Source | undefined;
43
47
  get pipelinesReady(): boolean;
44
48
  onPipelinesReady(cb: AttachPipeline): () => void;
@@ -64,6 +68,8 @@ export declare class ZeroContext extends QueryDelegateBase {
64
68
  * promise resolves.
65
69
  */
66
70
  hydratePendingPipelines(sliceMs?: number, yieldToEventLoop?: () => Promise<void>): Promise<void>;
71
+ getAggregateSource(name: string, columns: Record<string, SchemaValue>, primaryKey: PrimaryKey, optimisticDelta?: AggregateOptimisticDelta): Source;
72
+ aggregateKeyColumns(table: string, columns: CompoundKey): CompoundKey;
67
73
  mapAst(ast: AST): AST;
68
74
  decorateSourceInput(input: SourceInput, queryID: string): Input;
69
75
  onTransactionCommit(cb: CommitListener): () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uCAAuC,CAAC;AACvE,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,iCAAiC,CAAC;AAG1D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,yCAAyC,CAAC;AACvF,OAAO,KAAK,EAAC,GAAG,EAAE,WAAW,EAAC,MAAM,mCAAmC,CAAC;AAExE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2CAA2C,CAAC;AAC1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,4CAA4C,CAAC;AAC9E,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,kCAAkC,CAAC;AAC5D,OAAO,KAAK,EAAC,MAAM,EAAE,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAExE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,4CAA4C,CAAC;AAChF,OAAO,EAAC,iBAAiB,EAAC,MAAM,+CAA+C,CAAC;AAChF,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAC,KAAK,eAAe,EAAC,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAC,KAAK,YAAY,EAAqB,MAAM,oBAAoB,CAAC;AAEzE,MAAM,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;AACjD,MAAM,MAAM,cAAc,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;AAEvD,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;AACvD,MAAM,MAAM,iBAAiB,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;AAC7D,MAAM,MAAM,iBAAiB,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;AAS3D;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,iBAAiB;;IAShD,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC;IACxC,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAC9C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAevC,QAAQ,CAAC,qBAAqB,EAAE,CAAC,OAAO,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAE/D;;;OAGG;IACH,QAAQ,CAAC,oBAAoB,SAAS;IAEtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;IAEjD,YACE,EAAE,EAAE,UAAU,EACd,WAAW,EAAE,eAAe,EAC5B,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,WAAW,EACxB,iBAAiB,EAAE,iBAAiB,EACpC,iBAAiB,EAAE,MAAM,IAAI,EAC7B,gBAAgB,EAAE,CAAC,gBAAgB,EAAE,MAAM,IAAI,KAAK,IAAI,EACxD,SAAS,EAAE,eAAe,CAAC,WAAW,CAAC,EACvC,qBAAqB,EAAE,CAAC,OAAO,CAAC,EAAE,UAAU,KAAK,IAAI,EACrD,iBAAiB,CAAC,EAAE,yBAAyB,GAAG,SAAS,EAc1D;IAED,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAEzC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAOlC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAQ;IAE9C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE1C;IAED,IAAa,cAAc,IAAI,OAAO,CAErC;IAEQ,gBAAgB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,IAAI,CASxD;IAED;;;;;;;;OAQG;IACH,kBAAkB,IAAI,IAAI,CAezB;IAED;;;;;;;;;;OAUG;IACG,uBAAuB,CAC3B,OAAO,SAAmB,EAC1B,gBAAgB,GAAE,MAAM,OAAO,CAAC,IAAI,CAA0B,GAC7D,OAAO,CAAC,IAAI,CAAC,CAuBf;IA8DD,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,eAAe,CAAC,EAAE,wBAAwB,GACzC,MAAM,CAOR;IAED,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,WAAW,CAEpE;IAED,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAEpB;IAEQ,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAQvE;IAED,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,IAAI,CAKlD;IAEQ,gBAAgB,CAAC,CAAC,EAAE,gBAAgB,EAAE,MAAM,CAAC,GAWnC,CAAC,CACnB;IAED,cAAc,CACZ,YAAY,EAAE,IAAI,GAAG,SAAS,EAC9B,OAAO,EAAE,IAAI,EACb,OAAO,EAAE,WAAW,QAcrB;CAkBF"}
@@ -54,6 +54,7 @@ var ZeroContext = class extends QueryDelegateBase {
54
54
  }
55
55
  applyFiltersAnyway;
56
56
  debug;
57
+ aggregatesFromSource = true;
57
58
  getSource(name) {
58
59
  return this.#mainSources.getSource(name);
59
60
  }
@@ -155,6 +156,12 @@ var ZeroContext = class extends QueryDelegateBase {
155
156
  this.#endTransaction();
156
157
  }
157
158
  }
159
+ getAggregateSource(name, columns, primaryKey, optimisticDelta) {
160
+ return this.#mainSources.getOrCreateAggregateSource(name, columns, primaryKey, optimisticDelta);
161
+ }
162
+ aggregateKeyColumns(table, columns) {
163
+ return this.#mainSources.serverColumnNames(table, columns);
164
+ }
158
165
  mapAst(ast) {
159
166
  return ast;
160
167
  }
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":["ErrorKind.Internal"],"sources":["../../../../../zero-client/src/client/context.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {NoIndexDiff} from '../../../replicache/src/btree/node.ts';\nimport type {Hash} from '../../../replicache/src/hash.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {getBrowserGlobal} from '../../../shared/src/browser-env.ts';\nimport type {DocumentVisibilityWatcher} from '../../../shared/src/document-visible.ts';\nimport type {AST} from '../../../zero-protocol/src/ast.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport type {DebugDelegate} from '../../../zql/src/builder/debug-delegate.ts';\nimport type {Input} from '../../../zql/src/ivm/operator.ts';\nimport type {Source, SourceInput} from '../../../zql/src/ivm/source.ts';\nimport {MeasurePushOperator} from '../../../zql/src/query/measure-push-operator.ts';\nimport type {MetricsDelegate} from '../../../zql/src/query/metrics-delegate.ts';\nimport {QueryDelegateBase} from '../../../zql/src/query/query-delegate-base.ts';\nimport type {\n AttachPipeline,\n CommitListener,\n} from '../../../zql/src/query/query-delegate.ts';\nimport type {RunOptions} from '../../../zql/src/query/query.ts';\nimport {type IVMSourceBranch} from './ivm-branch.ts';\nimport {type QueryManager, UPDATE_SAMPLE_RATE} from './query-manager.ts';\n\nexport type AddQuery = QueryManager['addLegacy'];\nexport type AddCustomQuery = QueryManager['addCustom'];\n\nexport type UpdateQuery = QueryManager['updateLegacy'];\nexport type UpdateCustomQuery = QueryManager['updateCustom'];\nexport type FlushQueryChanges = QueryManager['flushBatch'];\n\n/**\n * How long pipelines are hydrated for before yielding to the event loop. Short\n * enough to keep a frame or two, long enough that the yields themselves (a\n * `setTimeout(0)` is clamped to 4ms in browsers) stay in the noise.\n */\nconst HYDRATE_SLICE_MS = 12;\n\n/**\n * ZeroContext glues together zql and Replicache. It listens to changes in\n * Replicache data and pushes them into IVM and on tells the server about new\n * queries.\n */\nexport class ZeroContext extends QueryDelegateBase {\n readonly #lc: LogContext;\n\n // It is a bummer to have to maintain separate MemorySources here and copy the\n // data in from the Replicache db. But we want the data to be accessible via\n // pipelines *synchronously* and the core Replicache infra is all async. So\n // that needs to be fixed.\n readonly #mainSources: IVMSourceBranch;\n\n readonly addServerQuery: AddQuery;\n readonly addCustomQuery: AddCustomQuery;\n readonly updateServerQuery: UpdateQuery;\n readonly updateCustomQuery: UpdateCustomQuery;\n readonly flushQueryChanges: () => void;\n readonly #batchViewUpdates: (applyViewUpdates: () => void) => void;\n readonly #commitListeners: Set<CommitListener> = new Set();\n\n // Pipeline construction is deferred until `markPipelinesReady()` is called.\n // Zero calls it once the replica has been loaded into the IVM sources, so\n // cold boot loads the sources once instead of pushing every row through\n // every already-materialized pipeline. Contexts whose sources are already\n // populated at construction (custom mutator transactions, tests) call it\n // right away.\n #pipelinesReady = false;\n readonly #pendingPipelines: Set<AttachPipeline> = new Set();\n #hydratingPipelines = false;\n readonly #visibilityWatcher: DocumentVisibilityWatcher | undefined;\n\n readonly assertValidRunOptions: (options?: RunOptions) => void;\n\n /**\n * Client-side queries start out as \"unknown\" and are then updated to\n * \"complete\" once the server has sent back the query result.\n */\n readonly defaultQueryComplete = false;\n\n readonly addMetric: MetricsDelegate['addMetric'];\n\n constructor(\n lc: LogContext,\n mainSources: IVMSourceBranch,\n addQuery: AddQuery,\n addCustomQuery: AddCustomQuery,\n updateQuery: UpdateQuery,\n updateCustomQuery: UpdateCustomQuery,\n flushQueryChanges: () => void,\n batchViewUpdates: (applyViewUpdates: () => void) => void,\n addMetric: MetricsDelegate['addMetric'],\n assertValidRunOptions: (options?: RunOptions) => void,\n visibilityWatcher?: DocumentVisibilityWatcher | undefined,\n ) {\n super();\n this.#lc = lc;\n this.#mainSources = mainSources;\n this.addServerQuery = addQuery;\n this.updateServerQuery = updateQuery;\n this.updateCustomQuery = updateCustomQuery;\n this.#batchViewUpdates = batchViewUpdates;\n this.assertValidRunOptions = assertValidRunOptions;\n this.addCustomQuery = addCustomQuery;\n this.flushQueryChanges = flushQueryChanges;\n this.addMetric = addMetric;\n this.#visibilityWatcher = visibilityWatcher;\n }\n\n applyFiltersAnyway?: boolean | undefined;\n\n debug?: DebugDelegate | undefined;\n\n getSource(name: string): Source | undefined {\n return this.#mainSources.getSource(name);\n }\n\n override get pipelinesReady(): boolean {\n return this.#pipelinesReady;\n }\n\n override onPipelinesReady(cb: AttachPipeline): () => void {\n assert(\n !this.#pipelinesReady,\n 'onPipelinesReady called while pipelines are ready',\n );\n this.#pendingPipelines.add(cb);\n return () => {\n this.#pendingPipelines.delete(cb);\n };\n }\n\n /**\n * Build and hydrate every pipeline deferred since construction, in\n * materialization order, as a single view-update batch. Views materialized\n * before this is called are empty and `unknown` in the meantime, exactly as\n * they would be over empty sources. A pipeline that fails to build is\n * logged and its view reports an error; the rest are still built.\n *\n * Returns `this` so it can be chained onto the constructor call.\n */\n markPipelinesReady(): this {\n if (this.#pipelinesReady) {\n return this;\n }\n assert(!this.#hydratingPipelines, 'Pipelines are already being hydrated');\n if (this.#pendingPipelines.size === 0) {\n this.#pipelinesReady = true;\n return this;\n }\n this.batchViewUpdates(() => {\n const releases: (() => void)[] = [];\n this.#attachPending(releases, () => false);\n this.#release(releases);\n });\n return this;\n }\n\n /**\n * Like {@link markPipelinesReady} but hydrates in time slices, yielding to\n * the event loop in between so a burst of queries registered at startup does\n * not monopolize the thread. It only ever yields *between* pipelines.\n *\n * No view is exposed until all of them are hydrated: they are released, and\n * the commit listeners notified, in one synchronous batch at the end.\n * Queries materialized in the meantime are still deferred and join that\n * batch. The caller must not call {@link processChanges} until the returned\n * promise resolves.\n */\n async hydratePendingPipelines(\n sliceMs = HYDRATE_SLICE_MS,\n yieldToEventLoop: () => Promise<void> = this.#yieldToEventLoop,\n ): Promise<void> {\n if (this.#pipelinesReady) {\n return;\n }\n assert(!this.#hydratingPipelines, 'Pipelines are already being hydrated');\n if (this.#pendingPipelines.size === 0) {\n this.#pipelinesReady = true;\n return;\n }\n this.#hydratingPipelines = true;\n const releases: (() => void)[] = [];\n for (;;) {\n const sliceEnd = performance.now() + sliceMs;\n this.batchViewUpdates(() => {\n this.#attachPending(releases, () => performance.now() >= sliceEnd);\n });\n if (this.#pendingPipelines.size === 0) {\n break;\n }\n await yieldToEventLoop();\n }\n this.#hydratingPipelines = false;\n this.batchViewUpdates(() => this.#release(releases));\n }\n\n #yieldToEventLoop = (): Promise<void> => {\n // Not in React Native or Safari.\n const scheduler = getBrowserGlobal('scheduler');\n if (typeof scheduler?.yield === 'function') {\n return scheduler.yield();\n }\n // Timers are throttled to a second or more in a background tab, which would\n // stretch startup to minutes, and a hidden page has nothing to keep\n // responsive anyway.\n if (this.#visibilityWatcher?.visibilityState === 'hidden') {\n return Promise.resolve();\n }\n return new Promise(resolve => setTimeout(resolve, 0));\n };\n\n /**\n * Attaches pending pipelines, always at least one, until none are left or\n * `shouldStop` says the slice is over.\n */\n #attachPending(releases: (() => void)[], shouldStop: () => boolean) {\n for (const attach of this.#pendingPipelines) {\n this.#pendingPipelines.delete(attach);\n try {\n releases.push(attach());\n } catch (e) {\n // The failing view has already been marked as errored; keep\n // building the others rather than aborting startup.\n this.#lc.error?.(\n ErrorKind.Internal,\n 'Failed to build a deferred query pipeline',\n e,\n );\n }\n if (shouldStop()) {\n return;\n }\n }\n }\n\n /** Must be called inside `batchViewUpdates`. */\n #release(releases: (() => void)[]) {\n this.#pipelinesReady = true;\n try {\n for (const release of releases) {\n try {\n release();\n } catch (e) {\n // One view must not keep the rest from being released.\n this.#lc.error?.(\n ErrorKind.Internal,\n 'Failed to release a deferred query pipeline',\n e,\n );\n }\n }\n } finally {\n this.#endTransaction();\n }\n }\n\n mapAst(ast: AST): AST {\n return ast;\n }\n\n override decorateSourceInput(input: SourceInput, queryID: string): Input {\n return new MeasurePushOperator(\n input,\n queryID,\n this,\n 'query-update-client',\n UPDATE_SAMPLE_RATE,\n );\n }\n\n onTransactionCommit(cb: CommitListener): () => void {\n this.#commitListeners.add(cb);\n return () => {\n this.#commitListeners.delete(cb);\n };\n }\n\n override batchViewUpdates<T>(applyViewUpdates: () => T) {\n let result: T | undefined;\n let viewChangesPerformed = false;\n this.#batchViewUpdates(() => {\n result = applyViewUpdates();\n viewChangesPerformed = true;\n });\n assert(\n viewChangesPerformed,\n 'batchViewUpdates must call applyViewUpdates synchronously.',\n );\n return result as T;\n }\n\n processChanges(\n expectedHead: Hash | undefined,\n newHead: Hash,\n changes: NoIndexDiff,\n ) {\n // Views hydrated so far would flush ahead of the rest of their batch.\n assert(\n !this.#hydratingPipelines,\n 'processChanges called while pipelines are being hydrated',\n );\n this.batchViewUpdates(() => {\n try {\n this.#mainSources.advance(expectedHead, newHead, changes);\n } finally {\n this.#endTransaction();\n }\n });\n }\n\n #endTransaction() {\n for (const listener of this.#commitListeners) {\n try {\n listener();\n } catch (e) {\n // We should not fatal the inner-workings of Zero due to the user's application\n // code throwing an error.\n // Hence we wrap notifications in a try-catch block.\n this.#lc.error?.(\n ErrorKind.Internal,\n 'Failed notifying a commit listener of IVM updates',\n e,\n );\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"context.js","names":["ErrorKind.Internal"],"sources":["../../../../../zero-client/src/client/context.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {NoIndexDiff} from '../../../replicache/src/btree/node.ts';\nimport type {Hash} from '../../../replicache/src/hash.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {getBrowserGlobal} from '../../../shared/src/browser-env.ts';\nimport type {DocumentVisibilityWatcher} from '../../../shared/src/document-visible.ts';\nimport type {AST, CompoundKey} from '../../../zero-protocol/src/ast.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue} from '../../../zero-types/src/schema-value.ts';\nimport type {AggregateOptimisticDelta} from '../../../zql/src/builder/builder.ts';\nimport type {DebugDelegate} from '../../../zql/src/builder/debug-delegate.ts';\nimport type {Input} from '../../../zql/src/ivm/operator.ts';\nimport type {Source, SourceInput} from '../../../zql/src/ivm/source.ts';\nimport {MeasurePushOperator} from '../../../zql/src/query/measure-push-operator.ts';\nimport type {MetricsDelegate} from '../../../zql/src/query/metrics-delegate.ts';\nimport {QueryDelegateBase} from '../../../zql/src/query/query-delegate-base.ts';\nimport type {\n AttachPipeline,\n CommitListener,\n} from '../../../zql/src/query/query-delegate.ts';\nimport type {RunOptions} from '../../../zql/src/query/query.ts';\nimport {type IVMSourceBranch} from './ivm-branch.ts';\nimport {type QueryManager, UPDATE_SAMPLE_RATE} from './query-manager.ts';\n\nexport type AddQuery = QueryManager['addLegacy'];\nexport type AddCustomQuery = QueryManager['addCustom'];\n\nexport type UpdateQuery = QueryManager['updateLegacy'];\nexport type UpdateCustomQuery = QueryManager['updateCustom'];\nexport type FlushQueryChanges = QueryManager['flushBatch'];\n\n/**\n * How long pipelines are hydrated for before yielding to the event loop. Short\n * enough to keep a frame or two, long enough that the yields themselves (a\n * `setTimeout(0)` is clamped to 4ms in browsers) stay in the noise.\n */\nconst HYDRATE_SLICE_MS = 12;\n\n/**\n * ZeroContext glues together zql and Replicache. It listens to changes in\n * Replicache data and pushes them into IVM and on tells the server about new\n * queries.\n */\nexport class ZeroContext extends QueryDelegateBase {\n readonly #lc: LogContext;\n\n // It is a bummer to have to maintain separate MemorySources here and copy the\n // data in from the Replicache db. But we want the data to be accessible via\n // pipelines *synchronously* and the core Replicache infra is all async. So\n // that needs to be fixed.\n readonly #mainSources: IVMSourceBranch;\n\n readonly addServerQuery: AddQuery;\n readonly addCustomQuery: AddCustomQuery;\n readonly updateServerQuery: UpdateQuery;\n readonly updateCustomQuery: UpdateCustomQuery;\n readonly flushQueryChanges: () => void;\n readonly #batchViewUpdates: (applyViewUpdates: () => void) => void;\n readonly #commitListeners: Set<CommitListener> = new Set();\n\n // Pipeline construction is deferred until `markPipelinesReady()` is called.\n // Zero calls it once the replica has been loaded into the IVM sources, so\n // cold boot loads the sources once instead of pushing every row through\n // every already-materialized pipeline. Contexts whose sources are already\n // populated at construction (custom mutator transactions, tests) call it\n // right away.\n #pipelinesReady = false;\n readonly #pendingPipelines: Set<AttachPipeline> = new Set();\n #hydratingPipelines = false;\n readonly #visibilityWatcher: DocumentVisibilityWatcher | undefined;\n\n readonly assertValidRunOptions: (options?: RunOptions) => void;\n\n /**\n * Client-side queries start out as \"unknown\" and are then updated to\n * \"complete\" once the server has sent back the query result.\n */\n readonly defaultQueryComplete = false;\n\n readonly addMetric: MetricsDelegate['addMetric'];\n\n constructor(\n lc: LogContext,\n mainSources: IVMSourceBranch,\n addQuery: AddQuery,\n addCustomQuery: AddCustomQuery,\n updateQuery: UpdateQuery,\n updateCustomQuery: UpdateCustomQuery,\n flushQueryChanges: () => void,\n batchViewUpdates: (applyViewUpdates: () => void) => void,\n addMetric: MetricsDelegate['addMetric'],\n assertValidRunOptions: (options?: RunOptions) => void,\n visibilityWatcher?: DocumentVisibilityWatcher | undefined,\n ) {\n super();\n this.#lc = lc;\n this.#mainSources = mainSources;\n this.addServerQuery = addQuery;\n this.updateServerQuery = updateQuery;\n this.updateCustomQuery = updateCustomQuery;\n this.#batchViewUpdates = batchViewUpdates;\n this.assertValidRunOptions = assertValidRunOptions;\n this.addCustomQuery = addCustomQuery;\n this.flushQueryChanges = flushQueryChanges;\n this.addMetric = addMetric;\n this.#visibilityWatcher = visibilityWatcher;\n }\n\n applyFiltersAnyway?: boolean | undefined;\n\n debug?: DebugDelegate | undefined;\n\n // The client reads aggregate results from the synthetic source the server\n // streams (`aggregate:<queryID>`), rather than computing them locally — the\n // underlying rows are intentionally never synced, so a local computation would\n // be wrong. See builder.ts `aggregatesFromSource`. A custom mutator computes\n // them when nothing was synced: see custom.ts `MutatorContext`.\n readonly aggregatesFromSource: boolean = true;\n\n getSource(name: string): Source | undefined {\n return this.#mainSources.getSource(name);\n }\n\n override get pipelinesReady(): boolean {\n return this.#pipelinesReady;\n }\n\n override onPipelinesReady(cb: AttachPipeline): () => void {\n assert(\n !this.#pipelinesReady,\n 'onPipelinesReady called while pipelines are ready',\n );\n this.#pendingPipelines.add(cb);\n return () => {\n this.#pendingPipelines.delete(cb);\n };\n }\n\n /**\n * Build and hydrate every pipeline deferred since construction, in\n * materialization order, as a single view-update batch. Views materialized\n * before this is called are empty and `unknown` in the meantime, exactly as\n * they would be over empty sources. A pipeline that fails to build is\n * logged and its view reports an error; the rest are still built.\n *\n * Returns `this` so it can be chained onto the constructor call.\n */\n markPipelinesReady(): this {\n if (this.#pipelinesReady) {\n return this;\n }\n assert(!this.#hydratingPipelines, 'Pipelines are already being hydrated');\n if (this.#pendingPipelines.size === 0) {\n this.#pipelinesReady = true;\n return this;\n }\n this.batchViewUpdates(() => {\n const releases: (() => void)[] = [];\n this.#attachPending(releases, () => false);\n this.#release(releases);\n });\n return this;\n }\n\n /**\n * Like {@link markPipelinesReady} but hydrates in time slices, yielding to\n * the event loop in between so a burst of queries registered at startup does\n * not monopolize the thread. It only ever yields *between* pipelines.\n *\n * No view is exposed until all of them are hydrated: they are released, and\n * the commit listeners notified, in one synchronous batch at the end.\n * Queries materialized in the meantime are still deferred and join that\n * batch. The caller must not call {@link processChanges} until the returned\n * promise resolves.\n */\n async hydratePendingPipelines(\n sliceMs = HYDRATE_SLICE_MS,\n yieldToEventLoop: () => Promise<void> = this.#yieldToEventLoop,\n ): Promise<void> {\n if (this.#pipelinesReady) {\n return;\n }\n assert(!this.#hydratingPipelines, 'Pipelines are already being hydrated');\n if (this.#pendingPipelines.size === 0) {\n this.#pipelinesReady = true;\n return;\n }\n this.#hydratingPipelines = true;\n const releases: (() => void)[] = [];\n for (;;) {\n const sliceEnd = performance.now() + sliceMs;\n this.batchViewUpdates(() => {\n this.#attachPending(releases, () => performance.now() >= sliceEnd);\n });\n if (this.#pendingPipelines.size === 0) {\n break;\n }\n await yieldToEventLoop();\n }\n this.#hydratingPipelines = false;\n this.batchViewUpdates(() => this.#release(releases));\n }\n\n #yieldToEventLoop = (): Promise<void> => {\n // Not in React Native or Safari.\n const scheduler = getBrowserGlobal('scheduler');\n if (typeof scheduler?.yield === 'function') {\n return scheduler.yield();\n }\n // Timers are throttled to a second or more in a background tab, which would\n // stretch startup to minutes, and a hidden page has nothing to keep\n // responsive anyway.\n if (this.#visibilityWatcher?.visibilityState === 'hidden') {\n return Promise.resolve();\n }\n return new Promise(resolve => setTimeout(resolve, 0));\n };\n\n /**\n * Attaches pending pipelines, always at least one, until none are left or\n * `shouldStop` says the slice is over.\n */\n #attachPending(releases: (() => void)[], shouldStop: () => boolean) {\n for (const attach of this.#pendingPipelines) {\n this.#pendingPipelines.delete(attach);\n try {\n releases.push(attach());\n } catch (e) {\n // The failing view has already been marked as errored; keep\n // building the others rather than aborting startup.\n this.#lc.error?.(\n ErrorKind.Internal,\n 'Failed to build a deferred query pipeline',\n e,\n );\n }\n if (shouldStop()) {\n return;\n }\n }\n }\n\n /** Must be called inside `batchViewUpdates`. */\n #release(releases: (() => void)[]) {\n this.#pipelinesReady = true;\n try {\n for (const release of releases) {\n try {\n release();\n } catch (e) {\n // One view must not keep the rest from being released.\n this.#lc.error?.(\n ErrorKind.Internal,\n 'Failed to release a deferred query pipeline',\n e,\n );\n }\n }\n } finally {\n this.#endTransaction();\n }\n }\n\n getAggregateSource(\n name: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n optimisticDelta?: AggregateOptimisticDelta,\n ): Source {\n return this.#mainSources.getOrCreateAggregateSource(\n name,\n columns,\n primaryKey,\n optimisticDelta,\n );\n }\n\n aggregateKeyColumns(table: string, columns: CompoundKey): CompoundKey {\n return this.#mainSources.serverColumnNames(table, columns);\n }\n\n mapAst(ast: AST): AST {\n return ast;\n }\n\n override decorateSourceInput(input: SourceInput, queryID: string): Input {\n return new MeasurePushOperator(\n input,\n queryID,\n this,\n 'query-update-client',\n UPDATE_SAMPLE_RATE,\n );\n }\n\n onTransactionCommit(cb: CommitListener): () => void {\n this.#commitListeners.add(cb);\n return () => {\n this.#commitListeners.delete(cb);\n };\n }\n\n override batchViewUpdates<T>(applyViewUpdates: () => T) {\n let result: T | undefined;\n let viewChangesPerformed = false;\n this.#batchViewUpdates(() => {\n result = applyViewUpdates();\n viewChangesPerformed = true;\n });\n assert(\n viewChangesPerformed,\n 'batchViewUpdates must call applyViewUpdates synchronously.',\n );\n return result as T;\n }\n\n processChanges(\n expectedHead: Hash | undefined,\n newHead: Hash,\n changes: NoIndexDiff,\n ) {\n // Views hydrated so far would flush ahead of the rest of their batch.\n assert(\n !this.#hydratingPipelines,\n 'processChanges called while pipelines are being hydrated',\n );\n this.batchViewUpdates(() => {\n try {\n this.#mainSources.advance(expectedHead, newHead, changes);\n } finally {\n this.#endTransaction();\n }\n });\n }\n\n #endTransaction() {\n for (const listener of this.#commitListeners) {\n try {\n listener();\n } catch (e) {\n // We should not fatal the inner-workings of Zero due to the user's application\n // code throwing an error.\n // Hence we wrap notifications in a try-catch block.\n this.#lc.error?.(\n ErrorKind.Internal,\n 'Failed notifying a commit listener of IVM updates',\n e,\n );\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"crud-impl.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/crud-impl.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,wCAAwC,CAAC;AAEhD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAO9D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AAErD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAelE,wBAAsB,MAAM,CAC1B,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GAAG,SAAS,GACrC,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,wBAAsB,MAAM,CAC1B,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,QAAQ,GAAG,QAAQ,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GAAG,SAAS,GACrC,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wBAAsB,MAAM,CAC1B,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GAAG,SAAS,GACrC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,iBAAe,UAAU,CACvB,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GAAG,SAAS,GACrC,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,OAAO,EAAC,UAAU,IAAI,MAAM,EAAC,CAAC"}
1
+ {"version":3,"file":"crud-impl.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/crud-impl.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,wCAAwC,CAAC;AAEhD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAe9D,OAAO,KAAK,EAAC,eAAe,EAAsB,MAAM,iBAAiB,CAAC;AAE1E,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAuRlE,wBAAsB,MAAM,CAC1B,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GAAG,SAAS,GACrC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,MAAM,CAC1B,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,QAAQ,GAAG,QAAQ,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GAAG,SAAS,GACrC,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wBAAsB,MAAM,CAC1B,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GAAG,SAAS,GACrC,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,iBAAe,UAAU,CACvB,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GAAG,SAAS,GACrC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,OAAO,EAAC,UAAU,IAAI,MAAM,EAAC,CAAC"}