@rocicorp/zero 1.4.0-canary.6 → 1.5.0-canary.0

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 (182) hide show
  1. package/out/zero/package.js +1 -1
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/auth/auth.d.ts +1 -1
  4. package/out/zero-cache/src/auth/auth.d.ts.map +1 -1
  5. package/out/zero-cache/src/auth/auth.js +1 -1
  6. package/out/zero-cache/src/auth/auth.js.map +1 -1
  7. package/out/zero-cache/src/auth/write-authorizer.d.ts +1 -1
  8. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  9. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  10. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  11. package/out/zero-cache/src/config/normalize.js +8 -0
  12. package/out/zero-cache/src/config/normalize.js.map +1 -1
  13. package/out/zero-cache/src/config/zero-config.d.ts +8 -4
  14. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  15. package/out/zero-cache/src/config/zero-config.js +28 -6
  16. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  17. package/out/zero-cache/src/custom/fetch.d.ts +1 -1
  18. package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
  19. package/out/zero-cache/src/custom/fetch.js +2 -2
  20. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  21. package/out/zero-cache/src/custom-queries/transform-query.d.ts +21 -7
  22. package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
  23. package/out/zero-cache/src/custom-queries/transform-query.js +26 -9
  24. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  25. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  26. package/out/zero-cache/src/server/change-streamer.js +2 -1
  27. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  28. package/out/zero-cache/src/server/runner/run-worker.d.ts.map +1 -1
  29. package/out/zero-cache/src/server/runner/run-worker.js +5 -2
  30. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  31. package/out/zero-cache/src/server/syncer.js +3 -3
  32. package/out/zero-cache/src/server/syncer.js.map +1 -1
  33. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  34. package/out/zero-cache/src/services/change-source/pg/change-source.js +24 -20
  35. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  36. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +258 -45
  37. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  38. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +119 -83
  39. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  40. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  41. package/out/zero-cache/src/services/change-source/pg/schema/init.js +1 -1
  42. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  43. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +2 -1
  45. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  46. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +1 -0
  47. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  49. package/out/zero-cache/src/services/http-service.d.ts +1 -0
  50. package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
  51. package/out/zero-cache/src/services/http-service.js +5 -4
  52. package/out/zero-cache/src/services/http-service.js.map +1 -1
  53. package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
  54. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/life-cycle.js +1 -2
  56. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  57. package/out/zero-cache/src/services/mutagen/mutagen.d.ts +1 -1
  58. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
  60. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  61. package/out/zero-cache/src/services/mutagen/pusher.d.ts +4 -3
  62. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/mutagen/pusher.js +57 -38
  64. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  65. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js +2 -1
  66. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
  67. package/out/zero-cache/src/services/view-syncer/client-handler.js +1 -1
  68. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  69. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +39 -27
  70. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js +138 -102
  72. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  73. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +6 -0
  74. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  75. package/out/zero-cache/src/services/view-syncer/cvr.js +8 -0
  76. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  77. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +3 -3
  78. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  79. package/out/zero-cache/src/services/view-syncer/view-syncer.js +115 -86
  80. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  81. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts +1 -1
  82. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  83. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +7 -7
  84. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  85. package/out/zero-cache/src/workers/syncer.d.ts +1 -1
  86. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  87. package/out/zero-cache/src/workers/syncer.js +11 -10
  88. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  89. package/out/zero-client/src/client/connection.d.ts +15 -7
  90. package/out/zero-client/src/client/connection.d.ts.map +1 -1
  91. package/out/zero-client/src/client/connection.js.map +1 -1
  92. package/out/zero-client/src/client/crud-impl.d.ts +1 -1
  93. package/out/zero-client/src/client/crud-impl.d.ts.map +1 -1
  94. package/out/zero-client/src/client/crud-impl.js +1 -1
  95. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  96. package/out/zero-client/src/client/crud.d.ts +1 -1
  97. package/out/zero-client/src/client/crud.d.ts.map +1 -1
  98. package/out/zero-client/src/client/crud.js +1 -1
  99. package/out/zero-client/src/client/crud.js.map +1 -1
  100. package/out/zero-client/src/client/keys.d.ts +1 -1
  101. package/out/zero-client/src/client/keys.d.ts.map +1 -1
  102. package/out/zero-client/src/client/keys.js.map +1 -1
  103. package/out/zero-client/src/client/make-replicache-mutators.js +1 -1
  104. package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
  105. package/out/zero-client/src/client/mutation-tracker.d.ts +2 -1
  106. package/out/zero-client/src/client/mutation-tracker.d.ts.map +1 -1
  107. package/out/zero-client/src/client/mutation-tracker.js +3 -3
  108. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  109. package/out/zero-client/src/client/version.js +1 -1
  110. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  111. package/out/zero-client/src/client/zero.js +2 -2
  112. package/out/zero-client/src/client/zero.js.map +1 -1
  113. package/out/zero-client/src/types/client-state.d.ts +1 -1
  114. package/out/zero-client/src/types/client-state.d.ts.map +1 -1
  115. package/out/zero-protocol/src/custom-queries.js +1 -1
  116. package/out/zero-protocol/src/down.js +1 -1
  117. package/out/zero-protocol/src/error-kind-enum.d.ts +1 -2
  118. package/out/zero-protocol/src/error-kind-enum.d.ts.map +1 -1
  119. package/out/zero-protocol/src/error-kind-enum.js.map +1 -1
  120. package/out/zero-protocol/src/mutate-server.d.ts +165 -0
  121. package/out/zero-protocol/src/mutate-server.d.ts.map +1 -0
  122. package/out/zero-protocol/src/mutate-server.js +24 -0
  123. package/out/zero-protocol/src/mutate-server.js.map +1 -0
  124. package/out/zero-protocol/src/mutation.d.ts +229 -0
  125. package/out/zero-protocol/src/mutation.d.ts.map +1 -0
  126. package/out/zero-protocol/src/mutation.js +112 -0
  127. package/out/zero-protocol/src/mutation.js.map +1 -0
  128. package/out/zero-protocol/src/mutations-patch.js +1 -1
  129. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  130. package/out/zero-protocol/src/push.d.ts +3 -234
  131. package/out/zero-protocol/src/push.d.ts.map +1 -1
  132. package/out/zero-protocol/src/push.js +3 -114
  133. package/out/zero-protocol/src/push.js.map +1 -1
  134. package/out/zero-protocol/src/query-server.d.ts +150 -0
  135. package/out/zero-protocol/src/query-server.d.ts.map +1 -0
  136. package/out/zero-protocol/src/query-server.js +16 -0
  137. package/out/zero-protocol/src/query-server.js.map +1 -0
  138. package/out/zero-protocol/src/up.js +1 -1
  139. package/out/zero-server/src/mod.d.ts +4 -2
  140. package/out/zero-server/src/mod.d.ts.map +1 -1
  141. package/out/zero-server/src/process-mutations.d.ts +41 -4
  142. package/out/zero-server/src/process-mutations.d.ts.map +1 -1
  143. package/out/zero-server/src/process-mutations.js +52 -35
  144. package/out/zero-server/src/process-mutations.js.map +1 -1
  145. package/out/zero-server/src/push-processor.d.ts +3 -3
  146. package/out/zero-server/src/push-processor.d.ts.map +1 -1
  147. package/out/zero-server/src/push-processor.js.map +1 -1
  148. package/out/zero-server/src/queries/process-queries.d.ts +22 -52
  149. package/out/zero-server/src/queries/process-queries.d.ts.map +1 -1
  150. package/out/zero-server/src/queries/process-queries.js +50 -49
  151. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  152. package/out/zero-server/src/zql-database.js.map +1 -1
  153. package/out/zero-types/src/default-types.d.ts +1 -0
  154. package/out/zero-types/src/default-types.d.ts.map +1 -1
  155. package/out/zql/src/builder/builder.d.ts.map +1 -1
  156. package/out/zql/src/builder/builder.js +17 -7
  157. package/out/zql/src/builder/builder.js.map +1 -1
  158. package/out/zql/src/ivm/cap.d.ts +32 -0
  159. package/out/zql/src/ivm/cap.d.ts.map +1 -0
  160. package/out/zql/src/ivm/cap.js +205 -0
  161. package/out/zql/src/ivm/cap.js.map +1 -0
  162. package/out/zql/src/ivm/constraint.js +1 -1
  163. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  164. package/out/zql/src/ivm/flipped-join.js +61 -15
  165. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  166. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  167. package/out/zql/src/ivm/memory-source.js +3 -4
  168. package/out/zql/src/ivm/memory-source.js.map +1 -1
  169. package/out/zql/src/ivm/schema.d.ts +8 -0
  170. package/out/zql/src/ivm/schema.d.ts.map +1 -1
  171. package/out/zql/src/ivm/take.js +2 -2
  172. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  173. package/out/zql/src/mutate/mutator.d.ts +11 -2
  174. package/out/zql/src/mutate/mutator.d.ts.map +1 -1
  175. package/out/zql/src/mutate/mutator.js.map +1 -1
  176. package/out/zql/src/query/query-registry.d.ts +9 -2
  177. package/out/zql/src/query/query-registry.d.ts.map +1 -1
  178. package/out/zql/src/query/query-registry.js.map +1 -1
  179. package/out/zqlite/src/table-source.d.ts.map +1 -1
  180. package/out/zqlite/src/table-source.js +4 -1
  181. package/out/zqlite/src/table-source.js.map +1 -1
  182. package/package.json +1 -1
@@ -20,7 +20,7 @@ import { createLogContext } from "../logging.js";
20
20
  * @deprecated Use {@linkcode handleQueryRequest} instead.
21
21
  */
22
22
  function handleGetQueriesRequest(cb, schema, requestOrJsonBody, logLevel = "info") {
23
- return transform(cb, schema, requestOrJsonBody, "getQueries", logLevel);
23
+ return transform(cb, schema, void 0, requestOrJsonBody, "getQueries", logLevel);
24
24
  }
25
25
  /**
26
26
  * Invokes the callback `cb` for each query in the request or JSON body.
@@ -32,24 +32,21 @@ function handleGetQueriesRequest(cb, schema, requestOrJsonBody, logLevel = "info
32
32
  * @deprecated Use {@linkcode handleQueryRequest} instead.
33
33
  */
34
34
  function handleTransformRequest(cb, schema, requestOrJsonBody, logLevel = "info") {
35
- return transform(cb, schema, requestOrJsonBody, "transform", logLevel);
35
+ return transform(cb, schema, void 0, requestOrJsonBody, "transform", logLevel);
36
36
  }
37
- /**
38
- * Processes a transform request by invoking the provided callback for each query.
39
- * The callback should return a Query that is the transformed result.
40
- *
41
- * This function will call `transformQuery` in parallel for each query found in the request.
42
- *
43
- * @param transformQuery - Callback function that takes a query name and args, and returns a Query
44
- * @param schema - The Zero schema
45
- * @param requestOrJsonBody - Either a Request object or the JSON body directly
46
- * @param logLevel - Logging level (defaults to 'info')
47
- * @returns A Promise that resolves to a TransformResponseMessage
48
- */
49
- function handleQueryRequest(transformQuery, schema, requestOrJsonBody, logLevel = "info") {
50
- return transform((name, argsArray) => transformQuery(name, argsArray[0]), schema, requestOrJsonBody, "query", logLevel);
37
+ function handleQueryRequest(transformQuery, schema, requestOrJsonBody, logLevelOrOptions) {
38
+ const options = normalizeQueryRequestOptions(logLevelOrOptions);
39
+ const normalized = {
40
+ requestOrJsonBody,
41
+ userID: "userID" in options ? options.userID ?? null : void 0,
42
+ logLevel: options.logLevel ?? "info"
43
+ };
44
+ return transform((name, argsArray) => transformQuery(name, argsArray[0]), schema, normalized.userID, normalized.requestOrJsonBody, "query", normalized.logLevel);
45
+ }
46
+ function normalizeQueryRequestOptions(logLevelOrOptions) {
47
+ return typeof logLevelOrOptions === "string" ? { logLevel: logLevelOrOptions } : logLevelOrOptions ?? {};
51
48
  }
52
- async function transform(cb, schema, requestOrJsonBody, apiName, logLevel = "info") {
49
+ async function transform(cb, schema, userID, requestOrJsonBody, apiName, logLevel = "info") {
53
50
  const lc = createLogContext(logLevel).withContext("TransformRequest");
54
51
  let parsed;
55
52
  let queryIDs = [];
@@ -63,56 +60,60 @@ async function transform(cb, schema, requestOrJsonBody, apiName, logLevel = "inf
63
60
  lc.error?.(`Failed to parse ${apiName} request`, error);
64
61
  const message = `Failed to parse ${apiName} request: ${getErrorMessage(error)}`;
65
62
  const details = getErrorDetails(error);
66
- return ["transformFailed", {
63
+ return {
67
64
  kind: TransformFailed,
68
65
  origin: Server,
69
66
  reason: Parse,
70
67
  message,
71
68
  queryIDs,
72
69
  ...details ? { details } : {}
73
- }];
70
+ };
74
71
  }
75
72
  try {
76
73
  const nameMapper = clientToServer(schema.tables);
77
- return ["transformed", await Promise.all(parsed[1].map(async (req) => {
78
- let finalQuery;
79
- try {
80
- const result = await cb(req.name, req.args);
81
- finalQuery = "query" in result ? result.query : result;
82
- } catch (error) {
83
- const message = getErrorMessage(error);
84
- const details = getErrorDetails(error);
85
- return {
86
- error: error instanceof QueryParseError ? "parse" : "app",
87
- id: req.id,
88
- name: req.name,
89
- message,
90
- ...details ? { details } : {}
91
- };
92
- }
93
- try {
94
- const ast = mapAST(asQueryInternals(finalQuery).ast, nameMapper);
95
- return {
96
- id: req.id,
97
- name: req.name,
98
- ast
99
- };
100
- } catch (error) {
101
- lc.error?.("Failed to map AST", error);
102
- throw error;
103
- }
104
- }))];
74
+ return {
75
+ kind: "QueryResponse",
76
+ queries: await Promise.all(parsed[1].map(async (req) => {
77
+ let finalQuery;
78
+ try {
79
+ const result = await cb(req.name, req.args);
80
+ finalQuery = "query" in result ? result.query : result;
81
+ } catch (error) {
82
+ const message = getErrorMessage(error);
83
+ const details = getErrorDetails(error);
84
+ return {
85
+ error: error instanceof QueryParseError ? "parse" : "app",
86
+ id: req.id,
87
+ name: req.name,
88
+ message,
89
+ ...details ? { details } : {}
90
+ };
91
+ }
92
+ try {
93
+ const ast = mapAST(asQueryInternals(finalQuery).ast, nameMapper);
94
+ return {
95
+ id: req.id,
96
+ name: req.name,
97
+ ast
98
+ };
99
+ } catch (error) {
100
+ lc.error?.("Failed to map AST", error);
101
+ throw error;
102
+ }
103
+ })),
104
+ ...typeof userID !== "undefined" ? { userID } : {}
105
+ };
105
106
  } catch (e) {
106
107
  const message = getErrorMessage(e);
107
108
  const details = getErrorDetails(e);
108
- return ["transformFailed", {
109
+ return {
109
110
  kind: TransformFailed,
110
111
  origin: Server,
111
112
  reason: Internal,
112
113
  message,
113
114
  queryIDs,
114
115
  ...details ? { details } : {}
115
- }];
116
+ };
116
117
  }
117
118
  }
118
119
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"process-queries.js","names":[],"sources":["../../../../../zero-server/src/queries/process-queries.ts"],"sourcesContent":["import type {LogLevel} from '@rocicorp/logger';\nimport {getErrorDetails, getErrorMessage} from '../../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {mapAST} from '../../../zero-protocol/src/ast.ts';\nimport {\n transformRequestMessageSchema,\n type TransformRequestMessage,\n type TransformResponseBody,\n type TransformResponseMessage,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {clientToServer} from '../../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {QueryParseError} from '../../../zql/src/query/error.ts';\nimport {asQueryInternals} from '../../../zql/src/query/query-internals.ts';\nimport type {AnyQuery} from '../../../zql/src/query/query.ts';\nimport {createLogContext} from '../logging.ts';\n\n/**\n * Invokes the callback `cb` for each query in the request or JSON body.\n * The callback should return a Query or Promise<Query> that is the transformed result.\n *\n * This function will call `cb` in parallel for each query found in the request.\n *\n * If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.\n * @deprecated Use {@linkcode handleQueryRequest} instead.\n */\nexport function handleGetQueriesRequest<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevel: LogLevel = 'info',\n): Promise<TransformResponseMessage> {\n return transform(cb, schema, requestOrJsonBody, 'getQueries', logLevel);\n}\n\n/**\n * Invokes the callback `cb` for each query in the request or JSON body.\n * The callback should return a Query or Promise<Query> that is the transformed result.\n *\n * This function will call `cb` in parallel for each query found in the request.\n *\n * If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.\n * @deprecated Use {@linkcode handleQueryRequest} instead.\n */\nexport function handleTransformRequest<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevel: LogLevel = 'info',\n): Promise<TransformResponseMessage> {\n return transform(cb, schema, requestOrJsonBody, 'transform', logLevel);\n}\n\n/**\n * Processes a transform request by invoking the provided callback for each query.\n * The callback should return a Query that is the transformed result.\n *\n * This function will call `transformQuery` in parallel for each query found in the request.\n *\n * @param transformQuery - Callback function that takes a query name and args, and returns a Query\n * @param schema - The Zero schema\n * @param requestOrJsonBody - Either a Request object or the JSON body directly\n * @param logLevel - Logging level (defaults to 'info')\n * @returns A Promise that resolves to a TransformResponseMessage\n */\nexport function handleQueryRequest<S extends Schema>(\n transformQuery: TransformQueryFunction,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevel: LogLevel = 'info',\n) {\n return transform(\n (name, argsArray) => transformQuery(name, argsArray[0]),\n schema,\n requestOrJsonBody,\n 'query',\n logLevel,\n );\n}\n\nasync function transform<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n apiName: 'query' | 'getQueries' | 'transform',\n logLevel: LogLevel = 'info',\n): Promise<TransformResponseMessage> {\n const lc = createLogContext(logLevel).withContext('TransformRequest');\n let parsed: TransformRequestMessage;\n let queryIDs: string[] = [];\n try {\n let body: ReadonlyJSONValue;\n if (requestOrJsonBody instanceof Request) {\n body = await requestOrJsonBody.json();\n } else {\n body = requestOrJsonBody;\n }\n\n parsed = v.parse(body, transformRequestMessageSchema);\n\n queryIDs = parsed[1].map(r => r.id);\n } catch (error) {\n lc.error?.(`Failed to parse ${apiName} request`, error);\n\n const message = `Failed to parse ${apiName} request: ${getErrorMessage(error)}`;\n const details = getErrorDetails(error);\n\n return [\n 'transformFailed',\n {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Parse,\n message,\n queryIDs,\n ...(details ? {details} : {}),\n },\n ];\n }\n\n try {\n const nameMapper = clientToServer(schema.tables);\n\n const responses: TransformResponseBody = await Promise.all(\n parsed[1].map(async req => {\n let finalQuery: AnyQuery;\n try {\n const result = await cb(req.name, req.args);\n finalQuery = 'query' in result ? result.query : result;\n } catch (error) {\n const message = getErrorMessage(error);\n const details = getErrorDetails(error);\n\n return {\n error: error instanceof QueryParseError ? 'parse' : 'app',\n id: req.id,\n name: req.name,\n message,\n ...(details ? {details} : {}),\n };\n }\n\n try {\n const q = asQueryInternals(finalQuery);\n const ast = mapAST(q.ast, nameMapper);\n\n return {\n id: req.id,\n name: req.name,\n ast,\n };\n } catch (error) {\n lc.error?.('Failed to map AST', error);\n throw error;\n }\n }),\n );\n\n return ['transformed', responses];\n } catch (e) {\n const message = getErrorMessage(e);\n const details = getErrorDetails(e);\n\n return [\n 'transformFailed',\n {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Internal,\n message,\n queryIDs,\n ...(details ? {details} : {}),\n },\n ];\n }\n}\n\n/**\n * A function that transforms a query by name and arguments into a Query object.\n *\n * @param name - The name of the query (can be dot-separated for nested queries)\n * @param args - The arguments to pass to the query (can be undefined)\n * @returns A Query object\n */\nexport type TransformQueryFunction = (\n name: string,\n args: ReadonlyJSONValue | undefined,\n) => AnyQuery;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,wBACd,IAIA,QACA,mBACA,WAAqB,QACc;AACnC,QAAO,UAAU,IAAI,QAAQ,mBAAmB,cAAc,SAAS;;;;;;;;;;;AAYzE,SAAgB,uBACd,IAIA,QACA,mBACA,WAAqB,QACc;AACnC,QAAO,UAAU,IAAI,QAAQ,mBAAmB,aAAa,SAAS;;;;;;;;;;;;;;AAexE,SAAgB,mBACd,gBACA,QACA,mBACA,WAAqB,QACrB;AACA,QAAO,WACJ,MAAM,cAAc,eAAe,MAAM,UAAU,GAAG,EACvD,QACA,mBACA,SACA,SACD;;AAGH,eAAe,UACb,IAIA,QACA,mBACA,SACA,WAAqB,QACc;CACnC,MAAM,KAAK,iBAAiB,SAAS,CAAC,YAAY,mBAAmB;CACrE,IAAI;CACJ,IAAI,WAAqB,EAAE;AAC3B,KAAI;EACF,IAAI;AACJ,MAAI,6BAA6B,QAC/B,QAAO,MAAM,kBAAkB,MAAM;MAErC,QAAO;AAGT,WAAS,MAAQ,MAAM,8BAA8B;AAErD,aAAW,OAAO,GAAG,KAAI,MAAK,EAAE,GAAG;UAC5B,OAAO;AACd,KAAG,QAAQ,mBAAmB,QAAQ,WAAW,MAAM;EAEvD,MAAM,UAAU,mBAAmB,QAAQ,YAAY,gBAAgB,MAAM;EAC7E,MAAM,UAAU,gBAAgB,MAAM;AAEtC,SAAO,CACL,mBACA;GACE,MAAM;GACN,QAAQ;GACR,QAAQ;GACR;GACA;GACA,GAAI,UAAU,EAAC,SAAQ,GAAG,EAAE;GAC7B,CACF;;AAGH,KAAI;EACF,MAAM,aAAa,eAAe,OAAO,OAAO;AAqChD,SAAO,CAAC,eAnCiC,MAAM,QAAQ,IACrD,OAAO,GAAG,IAAI,OAAM,QAAO;GACzB,IAAI;AACJ,OAAI;IACF,MAAM,SAAS,MAAM,GAAG,IAAI,MAAM,IAAI,KAAK;AAC3C,iBAAa,WAAW,SAAS,OAAO,QAAQ;YACzC,OAAO;IACd,MAAM,UAAU,gBAAgB,MAAM;IACtC,MAAM,UAAU,gBAAgB,MAAM;AAEtC,WAAO;KACL,OAAO,iBAAiB,kBAAkB,UAAU;KACpD,IAAI,IAAI;KACR,MAAM,IAAI;KACV;KACA,GAAI,UAAU,EAAC,SAAQ,GAAG,EAAE;KAC7B;;AAGH,OAAI;IAEF,MAAM,MAAM,OADF,iBAAiB,WAAW,CACjB,KAAK,WAAW;AAErC,WAAO;KACL,IAAI,IAAI;KACR,MAAM,IAAI;KACV;KACD;YACM,OAAO;AACd,OAAG,QAAQ,qBAAqB,MAAM;AACtC,UAAM;;IAER,CACH,CAEgC;UAC1B,GAAG;EACV,MAAM,UAAU,gBAAgB,EAAE;EAClC,MAAM,UAAU,gBAAgB,EAAE;AAElC,SAAO,CACL,mBACA;GACE,MAAM;GACN,QAAQ;GACR,QAAQ;GACR;GACA;GACA,GAAI,UAAU,EAAC,SAAQ,GAAG,EAAE;GAC7B,CACF"}
1
+ {"version":3,"file":"process-queries.js","names":[],"sources":["../../../../../zero-server/src/queries/process-queries.ts"],"sourcesContent":["import type {LogLevel} from '@rocicorp/logger';\nimport {getErrorDetails, getErrorMessage} from '../../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {mapAST} from '../../../zero-protocol/src/ast.ts';\nimport {\n transformRequestMessageSchema,\n type TransformRequestMessage,\n type TransformResponseBody,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport type {QueryResponse} from '../../../zero-protocol/src/query-server.ts';\nimport {clientToServer} from '../../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {QueryParseError} from '../../../zql/src/query/error.ts';\nimport {asQueryInternals} from '../../../zql/src/query/query-internals.ts';\nimport type {AnyQuery} from '../../../zql/src/query/query.ts';\nimport {createLogContext} from '../logging.ts';\n\n/**\n * Invokes the callback `cb` for each query in the request or JSON body.\n * The callback should return a Query or Promise<Query> that is the transformed result.\n *\n * This function will call `cb` in parallel for each query found in the request.\n *\n * If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.\n * @deprecated Use {@linkcode handleQueryRequest} instead.\n */\nexport function handleGetQueriesRequest<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevel: LogLevel = 'info',\n): Promise<QueryResponse> {\n return transform(\n cb,\n schema,\n undefined,\n requestOrJsonBody,\n 'getQueries',\n logLevel,\n );\n}\n\n/**\n * Invokes the callback `cb` for each query in the request or JSON body.\n * The callback should return a Query or Promise<Query> that is the transformed result.\n *\n * This function will call `cb` in parallel for each query found in the request.\n *\n * If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.\n * @deprecated Use {@linkcode handleQueryRequest} instead.\n */\nexport function handleTransformRequest<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevel: LogLevel = 'info',\n): Promise<QueryResponse> {\n return transform(\n cb,\n schema,\n undefined,\n requestOrJsonBody,\n 'transform',\n logLevel,\n );\n}\n\nexport type QueryRequestOptions = {\n userID?: string | null | undefined;\n logLevel?: LogLevel | undefined;\n};\n\ntype NormalizedQueryRequestArgs = {\n readonly requestOrJsonBody: Request | ReadonlyJSONValue;\n readonly userID: string | null | undefined;\n readonly logLevel: LogLevel;\n};\n\n/**\n * Process a `/query` request.\n *\n * @param transformQuery - Runs once per requested query with the query name\n * and first JSON argument. Returns a `Query`.\n * @param schema - Schema used when building the returned ASTs.\n * @param request - A Fetch `Request`.\n * @param logLevelOrOptions - Either a log level or additional request\n * options.\n * @returns A `QueryResponse`. Success returns `userID: options.userID ?? null`\n * when `options.userID` is provided. Per-query errors stay in `queries`;\n * malformed requests return `TransformFailed`.\n */\nexport function handleQueryRequest<S extends Schema>(\n transformQuery: TransformQueryFunction,\n schema: S,\n request: Request,\n logLevelOrOptions?: LogLevel | QueryRequestOptions,\n): Promise<QueryResponse>;\n\n/**\n * Process a `/query` request from a parsed JSON body.\n */\nexport function handleQueryRequest<S extends Schema>(\n transformQuery: TransformQueryFunction,\n schema: S,\n jsonBody: ReadonlyJSONValue,\n logLevelOrOptions?: LogLevel | QueryRequestOptions,\n): Promise<QueryResponse>;\n\nexport function handleQueryRequest<S extends Schema>(\n transformQuery: TransformQueryFunction,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevelOrOptions?: LogLevel | QueryRequestOptions,\n): Promise<QueryResponse> {\n const options = normalizeQueryRequestOptions(logLevelOrOptions);\n const normalized: NormalizedQueryRequestArgs = {\n requestOrJsonBody,\n userID: 'userID' in options ? (options.userID ?? null) : undefined,\n logLevel: options.logLevel ?? 'info',\n };\n\n return transform(\n (name, argsArray) => transformQuery(name, argsArray[0]),\n schema,\n normalized.userID,\n normalized.requestOrJsonBody,\n 'query',\n normalized.logLevel,\n );\n}\n\nfunction normalizeQueryRequestOptions(\n logLevelOrOptions: LogLevel | QueryRequestOptions | undefined,\n): QueryRequestOptions {\n return typeof logLevelOrOptions === 'string'\n ? {logLevel: logLevelOrOptions}\n : (logLevelOrOptions ?? {});\n}\n\nasync function transform<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n userID: string | null | undefined,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n apiName: 'query' | 'getQueries' | 'transform',\n logLevel: LogLevel = 'info',\n): Promise<QueryResponse> {\n const lc = createLogContext(logLevel).withContext('TransformRequest');\n let parsed: TransformRequestMessage;\n let queryIDs: string[] = [];\n try {\n let body: ReadonlyJSONValue;\n if (requestOrJsonBody instanceof Request) {\n body = await requestOrJsonBody.json();\n } else {\n body = requestOrJsonBody;\n }\n\n parsed = v.parse(body, transformRequestMessageSchema);\n\n queryIDs = parsed[1].map(r => r.id);\n } catch (error) {\n lc.error?.(`Failed to parse ${apiName} request`, error);\n\n const message = `Failed to parse ${apiName} request: ${getErrorMessage(error)}`;\n const details = getErrorDetails(error);\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Parse,\n message,\n queryIDs,\n ...(details ? {details} : {}),\n };\n }\n\n try {\n const nameMapper = clientToServer(schema.tables);\n\n const responses: TransformResponseBody = await Promise.all(\n parsed[1].map(async req => {\n let finalQuery: AnyQuery;\n try {\n const result = await cb(req.name, req.args);\n finalQuery = 'query' in result ? result.query : result;\n } catch (error) {\n const message = getErrorMessage(error);\n const details = getErrorDetails(error);\n\n return {\n error: error instanceof QueryParseError ? 'parse' : 'app',\n id: req.id,\n name: req.name,\n message,\n ...(details ? {details} : {}),\n };\n }\n\n try {\n const q = asQueryInternals(finalQuery);\n const ast = mapAST(q.ast, nameMapper);\n\n return {\n id: req.id,\n name: req.name,\n ast,\n };\n } catch (error) {\n lc.error?.('Failed to map AST', error);\n throw error;\n }\n }),\n );\n\n return {\n kind: 'QueryResponse',\n queries: responses,\n ...(typeof userID !== 'undefined' ? {userID} : {}),\n } as const satisfies QueryResponse;\n } catch (e) {\n const message = getErrorMessage(e);\n const details = getErrorDetails(e);\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Internal,\n message,\n queryIDs,\n ...(details ? {details} : {}),\n };\n }\n}\n\n/**\n * A function that transforms a query by name and arguments into a Query object.\n *\n * @param name - The name of the query (can be dot-separated for nested queries)\n * @param args - The arguments to pass to the query (can be undefined)\n * @returns A Query object\n */\nexport type TransformQueryFunction = (\n name: string,\n args: ReadonlyJSONValue | undefined,\n) => AnyQuery;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,wBACd,IAIA,QACA,mBACA,WAAqB,QACG;AACxB,QAAO,UACL,IACA,QACA,KAAA,GACA,mBACA,cACA,SACD;;;;;;;;;;;AAYH,SAAgB,uBACd,IAIA,QACA,mBACA,WAAqB,QACG;AACxB,QAAO,UACL,IACA,QACA,KAAA,GACA,mBACA,aACA,SACD;;AA4CH,SAAgB,mBACd,gBACA,QACA,mBACA,mBACwB;CACxB,MAAM,UAAU,6BAA6B,kBAAkB;CAC/D,MAAM,aAAyC;EAC7C;EACA,QAAQ,YAAY,UAAW,QAAQ,UAAU,OAAQ,KAAA;EACzD,UAAU,QAAQ,YAAY;EAC/B;AAED,QAAO,WACJ,MAAM,cAAc,eAAe,MAAM,UAAU,GAAG,EACvD,QACA,WAAW,QACX,WAAW,mBACX,SACA,WAAW,SACZ;;AAGH,SAAS,6BACP,mBACqB;AACrB,QAAO,OAAO,sBAAsB,WAChC,EAAC,UAAU,mBAAkB,GAC5B,qBAAqB,EAAE;;AAG9B,eAAe,UACb,IAIA,QACA,QACA,mBACA,SACA,WAAqB,QACG;CACxB,MAAM,KAAK,iBAAiB,SAAS,CAAC,YAAY,mBAAmB;CACrE,IAAI;CACJ,IAAI,WAAqB,EAAE;AAC3B,KAAI;EACF,IAAI;AACJ,MAAI,6BAA6B,QAC/B,QAAO,MAAM,kBAAkB,MAAM;MAErC,QAAO;AAGT,WAAS,MAAQ,MAAM,8BAA8B;AAErD,aAAW,OAAO,GAAG,KAAI,MAAK,EAAE,GAAG;UAC5B,OAAO;AACd,KAAG,QAAQ,mBAAmB,QAAQ,WAAW,MAAM;EAEvD,MAAM,UAAU,mBAAmB,QAAQ,YAAY,gBAAgB,MAAM;EAC7E,MAAM,UAAU,gBAAgB,MAAM;AAEtC,SAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;GACR;GACA;GACA,GAAI,UAAU,EAAC,SAAQ,GAAG,EAAE;GAC7B;;AAGH,KAAI;EACF,MAAM,aAAa,eAAe,OAAO,OAAO;AAqChD,SAAO;GACL,MAAM;GACN,SArCuC,MAAM,QAAQ,IACrD,OAAO,GAAG,IAAI,OAAM,QAAO;IACzB,IAAI;AACJ,QAAI;KACF,MAAM,SAAS,MAAM,GAAG,IAAI,MAAM,IAAI,KAAK;AAC3C,kBAAa,WAAW,SAAS,OAAO,QAAQ;aACzC,OAAO;KACd,MAAM,UAAU,gBAAgB,MAAM;KACtC,MAAM,UAAU,gBAAgB,MAAM;AAEtC,YAAO;MACL,OAAO,iBAAiB,kBAAkB,UAAU;MACpD,IAAI,IAAI;MACR,MAAM,IAAI;MACV;MACA,GAAI,UAAU,EAAC,SAAQ,GAAG,EAAE;MAC7B;;AAGH,QAAI;KAEF,MAAM,MAAM,OADF,iBAAiB,WAAW,CACjB,KAAK,WAAW;AAErC,YAAO;MACL,IAAI,IAAI;MACR,MAAM,IAAI;MACV;MACD;aACM,OAAO;AACd,QAAG,QAAQ,qBAAqB,MAAM;AACtC,WAAM;;KAER,CACH;GAKC,GAAI,OAAO,WAAW,cAAc,EAAC,QAAO,GAAG,EAAE;GAClD;UACM,GAAG;EACV,MAAM,UAAU,gBAAgB,EAAE;EAClC,MAAM,UAAU,gBAAgB,EAAE;AAElC,SAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;GACR;GACA;GACA,GAAI,UAAU,EAAC,SAAQ,GAAG,EAAE;GAC7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"zql-database.js","names":["#crudFactory","#makeServerTransaction"],"sources":["../../../../zero-server/src/zql-database.ts"],"sourcesContent":["import type {MaybePromise} from '../../shared/src/types.ts';\nimport {formatPg, sql} from '../../z2s/src/sql.ts';\nimport type {CleanupResultsArg} from '../../zero-protocol/src/push.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {DBConnection, DBTransaction} from '../../zql/src/mutate/custom.ts';\nimport type {\n HumanReadable,\n Query,\n RunOptions,\n} from '../../zql/src/query/query.ts';\nimport {CRUDMutatorFactory, type TransactionImpl} from './custom.ts';\nimport type {\n Database,\n TransactionProviderHooks,\n TransactionProviderInput,\n} from './process-mutations.ts';\n\n/**\n * Implements a Database for use with PushProcessor that is backed by Postgres.\n *\n * This implementation also implements the same ZQL interfaces for reading and\n * writing data that the Zero client does, so that mutator functions can be\n * shared across client and server.\n */\nexport class ZQLDatabase<\n TSchema extends Schema,\n TWrappedTransaction,\n> implements Database<TransactionImpl<TSchema, TWrappedTransaction>> {\n readonly connection: DBConnection<TWrappedTransaction>;\n readonly #crudFactory: CRUDMutatorFactory<TSchema>;\n\n constructor(connection: DBConnection<TWrappedTransaction>, schema: TSchema) {\n this.connection = connection;\n this.#crudFactory = new CRUDMutatorFactory(schema);\n }\n\n transaction<R>(\n callback: (\n tx: TransactionImpl<TSchema, TWrappedTransaction>,\n transactionHooks: TransactionProviderHooks,\n ) => MaybePromise<R>,\n transactionInput?: TransactionProviderInput,\n ): Promise<R> {\n // Icky hack. This is just here to have user not have to do this.\n // These interfaces need to be factored better.\n const {\n upstreamSchema = '',\n clientGroupID = '',\n clientID = '',\n mutationID = 0,\n } = transactionInput ?? {};\n return this.connection.transaction(async dbTx => {\n const zeroTx = await this.#makeServerTransaction(\n dbTx,\n clientID,\n mutationID,\n );\n\n return callback(zeroTx, {\n async updateClientMutationID() {\n const formatted = formatPg(\n sql`INSERT INTO ${sql.ident(upstreamSchema)}.clients \n as current (\"clientGroupID\", \"clientID\", \"lastMutationID\")\n VALUES (${clientGroupID}, ${clientID}, ${1})\n ON CONFLICT (\"clientGroupID\", \"clientID\")\n DO UPDATE SET \"lastMutationID\" = current.\"lastMutationID\" + 1\n RETURNING \"lastMutationID\"`,\n );\n\n const [{lastMutationID}] = (await dbTx.query(\n formatted.text,\n formatted.values,\n )) as {lastMutationID: bigint}[];\n\n return {lastMutationID};\n },\n\n async writeMutationResult(result) {\n const formatted = formatPg(\n sql`INSERT INTO ${sql.ident(upstreamSchema)}.mutations\n (\"clientGroupID\", \"clientID\", \"mutationID\", \"result\")\n VALUES (${clientGroupID}, ${result.id.clientID}, ${result.id.id}, ${JSON.stringify(\n result.result,\n )}::text::json)`,\n );\n await dbTx.query(formatted.text, formatted.values);\n },\n\n async deleteMutationResults(args: CleanupResultsArg) {\n if ('type' in args && args.type === 'bulk') {\n // Bulk deletion: delete all mutations for multiple clients\n const formatted = formatPg(\n sql`DELETE FROM ${sql.ident(upstreamSchema)}.\"mutations\"\n WHERE \"clientGroupID\" = ${args.clientGroupID}\n AND \"clientID\" = ANY(${args.clientIDs})`,\n );\n await dbTx.query(formatted.text, formatted.values);\n } else {\n // Single client (explicit 'single' or legacy without type): delete up to mutation ID\n const formatted = formatPg(\n sql`DELETE FROM ${sql.ident(upstreamSchema)}.\"mutations\"\n WHERE \"clientGroupID\" = ${args.clientGroupID}\n AND \"clientID\" = ${args.clientID}\n AND \"mutationID\" <= ${args.upToMutationID}`,\n );\n await dbTx.query(formatted.text, formatted.values);\n }\n },\n });\n });\n }\n\n #makeServerTransaction(\n dbTx: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n ) {\n return this.#crudFactory.createTransaction(dbTx, clientID, mutationID);\n }\n\n run<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TTable, TSchema, TReturn>,\n options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n return this.transaction(tx => tx.run(query, options));\n }\n}\n"],"mappings":";;;;;;;;;;AAwBA,IAAa,cAAb,MAGqE;CACnE;CACA;CAEA,YAAY,YAA+C,QAAiB;AAC1E,OAAK,aAAa;AAClB,QAAA,cAAoB,IAAI,mBAAmB,OAAO;;CAGpD,YACE,UAIA,kBACY;EAGZ,MAAM,EACJ,iBAAiB,IACjB,gBAAgB,IAChB,WAAW,IACX,aAAa,MACX,oBAAoB,EAAE;AAC1B,SAAO,KAAK,WAAW,YAAY,OAAM,SAAQ;AAO/C,UAAO,SANQ,MAAM,MAAA,sBACnB,MACA,UACA,WACD,EAEuB;IACtB,MAAM,yBAAyB;KAC7B,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,eAAe,CAAC;;kCAEtB,cAAc,IAAI,SAAS,IAAI,EAAE;;;gDAIxD;KAED,MAAM,CAAC,EAAC,oBAAoB,MAAM,KAAK,MACrC,UAAU,MACV,UAAU,OACX;AAED,YAAO,EAAC,gBAAe;;IAGzB,MAAM,oBAAoB,QAAQ;KAChC,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,eAAe,CAAC;;0BAE9B,cAAc,IAAI,OAAO,GAAG,SAAS,IAAI,OAAO,GAAG,GAAG,IAAI,KAAK,UACvE,OAAO,OACR,CAAC,eACP;AACD,WAAM,KAAK,MAAM,UAAU,MAAM,UAAU,OAAO;;IAGpD,MAAM,sBAAsB,MAAyB;AACnD,SAAI,UAAU,QAAQ,KAAK,SAAS,QAAQ;MAE1C,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,eAAe,CAAC;4CACd,KAAK,cAAc;2CACpB,KAAK,UAAU,GAC7C;AACD,YAAM,KAAK,MAAM,UAAU,MAAM,UAAU,OAAO;YAC7C;MAEL,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,eAAe,CAAC;4CACd,KAAK,cAAc;uCACxB,KAAK,SAAS;0CACX,KAAK,iBAClC;AACD,YAAM,KAAK,MAAM,UAAU,MAAM,UAAU,OAAO;;;IAGvD,CAAC;IACF;;CAGJ,uBACE,MACA,UACA,YACA;AACA,SAAO,MAAA,YAAkB,kBAAkB,MAAM,UAAU,WAAW;;CAGxE,IACE,OACA,SACiC;AACjC,SAAO,KAAK,aAAY,OAAM,GAAG,IAAI,OAAO,QAAQ,CAAC"}
1
+ {"version":3,"file":"zql-database.js","names":["#crudFactory","#makeServerTransaction"],"sources":["../../../../zero-server/src/zql-database.ts"],"sourcesContent":["import type {MaybePromise} from '../../shared/src/types.ts';\nimport {formatPg, sql} from '../../z2s/src/sql.ts';\nimport type {CleanupResultsArg} from '../../zero-protocol/src/mutation.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {DBConnection, DBTransaction} from '../../zql/src/mutate/custom.ts';\nimport type {\n HumanReadable,\n Query,\n RunOptions,\n} from '../../zql/src/query/query.ts';\nimport {CRUDMutatorFactory, type TransactionImpl} from './custom.ts';\nimport type {\n Database,\n TransactionProviderHooks,\n TransactionProviderInput,\n} from './process-mutations.ts';\n\n/**\n * Implements a Database for use with PushProcessor that is backed by Postgres.\n *\n * This implementation also implements the same ZQL interfaces for reading and\n * writing data that the Zero client does, so that mutator functions can be\n * shared across client and server.\n */\nexport class ZQLDatabase<\n TSchema extends Schema,\n TWrappedTransaction,\n> implements Database<TransactionImpl<TSchema, TWrappedTransaction>> {\n readonly connection: DBConnection<TWrappedTransaction>;\n readonly #crudFactory: CRUDMutatorFactory<TSchema>;\n\n constructor(connection: DBConnection<TWrappedTransaction>, schema: TSchema) {\n this.connection = connection;\n this.#crudFactory = new CRUDMutatorFactory(schema);\n }\n\n transaction<R>(\n callback: (\n tx: TransactionImpl<TSchema, TWrappedTransaction>,\n transactionHooks: TransactionProviderHooks,\n ) => MaybePromise<R>,\n transactionInput?: TransactionProviderInput,\n ): Promise<R> {\n // Icky hack. This is just here to have user not have to do this.\n // These interfaces need to be factored better.\n const {\n upstreamSchema = '',\n clientGroupID = '',\n clientID = '',\n mutationID = 0,\n } = transactionInput ?? {};\n return this.connection.transaction(async dbTx => {\n const zeroTx = await this.#makeServerTransaction(\n dbTx,\n clientID,\n mutationID,\n );\n\n return callback(zeroTx, {\n async updateClientMutationID() {\n const formatted = formatPg(\n sql`INSERT INTO ${sql.ident(upstreamSchema)}.clients \n as current (\"clientGroupID\", \"clientID\", \"lastMutationID\")\n VALUES (${clientGroupID}, ${clientID}, ${1})\n ON CONFLICT (\"clientGroupID\", \"clientID\")\n DO UPDATE SET \"lastMutationID\" = current.\"lastMutationID\" + 1\n RETURNING \"lastMutationID\"`,\n );\n\n const [{lastMutationID}] = (await dbTx.query(\n formatted.text,\n formatted.values,\n )) as {lastMutationID: bigint}[];\n\n return {lastMutationID};\n },\n\n async writeMutationResult(result) {\n const formatted = formatPg(\n sql`INSERT INTO ${sql.ident(upstreamSchema)}.mutations\n (\"clientGroupID\", \"clientID\", \"mutationID\", \"result\")\n VALUES (${clientGroupID}, ${result.id.clientID}, ${result.id.id}, ${JSON.stringify(\n result.result,\n )}::text::json)`,\n );\n await dbTx.query(formatted.text, formatted.values);\n },\n\n async deleteMutationResults(args: CleanupResultsArg) {\n if ('type' in args && args.type === 'bulk') {\n // Bulk deletion: delete all mutations for multiple clients\n const formatted = formatPg(\n sql`DELETE FROM ${sql.ident(upstreamSchema)}.\"mutations\"\n WHERE \"clientGroupID\" = ${args.clientGroupID}\n AND \"clientID\" = ANY(${args.clientIDs})`,\n );\n await dbTx.query(formatted.text, formatted.values);\n } else {\n // Single client (explicit 'single' or legacy without type): delete up to mutation ID\n const formatted = formatPg(\n sql`DELETE FROM ${sql.ident(upstreamSchema)}.\"mutations\"\n WHERE \"clientGroupID\" = ${args.clientGroupID}\n AND \"clientID\" = ${args.clientID}\n AND \"mutationID\" <= ${args.upToMutationID}`,\n );\n await dbTx.query(formatted.text, formatted.values);\n }\n },\n });\n });\n }\n\n #makeServerTransaction(\n dbTx: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n ) {\n return this.#crudFactory.createTransaction(dbTx, clientID, mutationID);\n }\n\n run<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TTable, TSchema, TReturn>,\n options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n return this.transaction(tx => tx.run(query, options));\n }\n}\n"],"mappings":";;;;;;;;;;AAwBA,IAAa,cAAb,MAGqE;CACnE;CACA;CAEA,YAAY,YAA+C,QAAiB;AAC1E,OAAK,aAAa;AAClB,QAAA,cAAoB,IAAI,mBAAmB,OAAO;;CAGpD,YACE,UAIA,kBACY;EAGZ,MAAM,EACJ,iBAAiB,IACjB,gBAAgB,IAChB,WAAW,IACX,aAAa,MACX,oBAAoB,EAAE;AAC1B,SAAO,KAAK,WAAW,YAAY,OAAM,SAAQ;AAO/C,UAAO,SANQ,MAAM,MAAA,sBACnB,MACA,UACA,WACD,EAEuB;IACtB,MAAM,yBAAyB;KAC7B,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,eAAe,CAAC;;kCAEtB,cAAc,IAAI,SAAS,IAAI,EAAE;;;gDAIxD;KAED,MAAM,CAAC,EAAC,oBAAoB,MAAM,KAAK,MACrC,UAAU,MACV,UAAU,OACX;AAED,YAAO,EAAC,gBAAe;;IAGzB,MAAM,oBAAoB,QAAQ;KAChC,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,eAAe,CAAC;;0BAE9B,cAAc,IAAI,OAAO,GAAG,SAAS,IAAI,OAAO,GAAG,GAAG,IAAI,KAAK,UACvE,OAAO,OACR,CAAC,eACP;AACD,WAAM,KAAK,MAAM,UAAU,MAAM,UAAU,OAAO;;IAGpD,MAAM,sBAAsB,MAAyB;AACnD,SAAI,UAAU,QAAQ,KAAK,SAAS,QAAQ;MAE1C,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,eAAe,CAAC;4CACd,KAAK,cAAc;2CACpB,KAAK,UAAU,GAC7C;AACD,YAAM,KAAK,MAAM,UAAU,MAAM,UAAU,OAAO;YAC7C;MAEL,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,eAAe,CAAC;4CACd,KAAK,cAAc;uCACxB,KAAK,SAAS;0CACX,KAAK,iBAClC;AACD,YAAM,KAAK,MAAM,UAAU,MAAM,UAAU,OAAO;;;IAGvD,CAAC;IACF;;CAGJ,uBACE,MACA,UACA,YACA;AACA,SAAO,MAAA,YAAkB,kBAAkB,MAAM,UAAU,WAAW;;CAGxE,IACE,OACA,SACiC;AACjC,SAAO,KAAK,aAAY,OAAM,GAAG,IAAI,OAAO,QAAQ,CAAC"}
@@ -16,6 +16,7 @@ import type { Schema } from './schema.ts';
16
16
  */
17
17
  export interface DefaultTypes {
18
18
  }
19
+ export type IsUnknown<T> = unknown extends T ? [T] extends [unknown] ? true : false : false;
19
20
  export type DefaultSchema<TDefaultTypes = DefaultTypes> = TDefaultTypes extends {
20
21
  readonly schema: infer S extends Schema;
21
22
  } ? S : Schema;
@@ -1 +1 @@
1
- {"version":3,"file":"default-types.d.ts","sourceRoot":"","sources":["../../../../zero-types/src/default-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,4BAA4B,CAAC;AACvD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;CAAG;AAEhC,MAAM,MAAM,aAAa,CAAC,aAAa,GAAG,YAAY,IACpD,aAAa,SAAS;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,MAAM,CAAC;CACzC,GACG,CAAC,GACD,MAAM,CAAC;AAEb,MAAM,MAAM,iBAAiB,CAAC,aAAa,GAAG,YAAY,IACxD,aAAa,CAAC,aAAa,CAAC,CAAC;AAE/B,MAAM,MAAM,cAAc,CAAC,aAAa,GAAG,YAAY,IACrD,aAAa,SAAS;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAC3B,GACG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC;AAEd,MAAM,MAAM,kBAAkB,CAAC,aAAa,GAAG,YAAY,IACzD,OAAO,SAAS,cAAc,CAAC,aAAa,CAAC,GAEzC,GAAG,GACH,cAAc,CAAC,aAAa,CAAC,CAAC;AAEpC,MAAM,MAAM,8BAA8B,CAAC,WAAW,IAAI,WAAW,SAAS;IAC5E,WAAW,EAAE,CAAC,CAAC,EAEb,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,YAAY,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAEzD,GAAG,IAAI,EAAE,GAAG,EAAE,KACX,OAAO,CAAC,CAAC,CAAC,CAAC;CACjB,GACG,YAAY,GACZ,OAAO,CAAC;AAEZ,MAAM,MAAM,yBAAyB,CAAC,aAAa,GAAG,YAAY,IAChE,aAAa,SAAS;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,UAAU,CAAC;CACvC,GACG,8BAA8B,CAAC,UAAU,CAAC,SAAS,MAAM,YAAY,GACnE,YAAY,SAAS;IACnB,QAAQ,CAAC,aAAa,EAAE;QACtB,QAAQ,CAAC,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;KACxD,CAAC;CACH,GACC,mBAAmB,GACnB;IACE,KAAK,EAAE,oGAAoG,CAAC;IAC5G,oBAAoB,EAAE,UAAU,CAAC;CAClC,GACH,KAAK,GACP,OAAO,CAAC"}
1
+ {"version":3,"file":"default-types.d.ts","sourceRoot":"","sources":["../../../../zero-types/src/default-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,4BAA4B,CAAC;AACvD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;CAAG;AAEhC,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,OAAO,SAAS,CAAC,GACxC,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,GACnB,IAAI,GACJ,KAAK,GACP,KAAK,CAAC;AAEV,MAAM,MAAM,aAAa,CAAC,aAAa,GAAG,YAAY,IACpD,aAAa,SAAS;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,MAAM,CAAC;CACzC,GACG,CAAC,GACD,MAAM,CAAC;AAEb,MAAM,MAAM,iBAAiB,CAAC,aAAa,GAAG,YAAY,IACxD,aAAa,CAAC,aAAa,CAAC,CAAC;AAE/B,MAAM,MAAM,cAAc,CAAC,aAAa,GAAG,YAAY,IACrD,aAAa,SAAS;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAC3B,GACG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC;AAEd,MAAM,MAAM,kBAAkB,CAAC,aAAa,GAAG,YAAY,IACzD,OAAO,SAAS,cAAc,CAAC,aAAa,CAAC,GAEzC,GAAG,GACH,cAAc,CAAC,aAAa,CAAC,CAAC;AAEpC,MAAM,MAAM,8BAA8B,CAAC,WAAW,IAAI,WAAW,SAAS;IAC5E,WAAW,EAAE,CAAC,CAAC,EAEb,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,YAAY,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAEzD,GAAG,IAAI,EAAE,GAAG,EAAE,KACX,OAAO,CAAC,CAAC,CAAC,CAAC;CACjB,GACG,YAAY,GACZ,OAAO,CAAC;AAEZ,MAAM,MAAM,yBAAyB,CAAC,aAAa,GAAG,YAAY,IAChE,aAAa,SAAS;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,UAAU,CAAC;CACvC,GACG,8BAA8B,CAAC,UAAU,CAAC,SAAS,MAAM,YAAY,GACnE,YAAY,SAAS;IACnB,QAAQ,CAAC,aAAa,EAAE;QACtB,QAAQ,CAAC,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;KACxD,CAAC;CACH,GACC,mBAAmB,GACnB;IACE,KAAK,EAAE,oGAAoG,CAAC;IAC5G,oBAAoB,EAAE,UAAU,CAAC;CAClC,GACH,KAAK,GACP,OAAO,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/builder/builder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/builder/builder.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,8 @@
1
1
  import { assert, unreachable } from "../../../shared/src/asserts.js";
2
2
  import { must } from "../../../shared/src/must.js";
3
+ import { completeOrdering } from "../query/complete-ordering.js";
4
+ import { Take } from "../ivm/take.js";
5
+ import { Cap } from "../ivm/cap.js";
3
6
  import { buildFilterPipeline } from "../ivm/filter-operators.js";
4
7
  import { Exists } from "../ivm/exists.js";
5
8
  import { FanIn } from "../ivm/fan-in.js";
@@ -8,8 +11,6 @@ import { Filter } from "../ivm/filter.js";
8
11
  import { FlippedJoin } from "../ivm/flipped-join.js";
9
12
  import { Join } from "../ivm/join.js";
10
13
  import { Skip } from "../ivm/skip.js";
11
- import { completeOrdering } from "../query/complete-ordering.js";
12
- import { Take } from "../ivm/take.js";
13
14
  import { UnionFanIn } from "../ivm/union-fan-in.js";
14
15
  import { UnionFanOut } from "../ivm/union-fan-out.js";
15
16
  import { planQuery } from "../planner/planner-builder.js";
@@ -112,7 +113,7 @@ function assertNoNotExists(condition) {
112
113
  default: unreachable(condition);
113
114
  }
114
115
  }
115
- function buildPipelineInternal(ast, delegate, queryID, name, partitionKey) {
116
+ function buildPipelineInternal(ast, delegate, queryID, name, partitionKey, isNonFlippedExistsChild) {
116
117
  const source = delegate.getSource(ast.table);
117
118
  if (!source) throw new Error(`Source not found: ${ast.table}`);
118
119
  ast = uniquifyCorrelatedSubqueryConditionAliases(ast);
@@ -125,7 +126,11 @@ function buildPipelineInternal(ast, delegate, queryID, name, partitionKey) {
125
126
  for (const key of csq.related.correlation.parentField) splitEditKeys.add(key);
126
127
  }
127
128
  if (ast.related) for (const csq of ast.related) for (const key of csq.correlation.parentField) splitEditKeys.add(key);
128
- const conn = source.connect(must(ast.orderBy), ast.where, splitEditKeys, delegate.debug);
129
+ if (isNonFlippedExistsChild) {
130
+ assert(ast.start === void 0, "EXISTS subqueries must not have start");
131
+ assert(ast.related === void 0, "EXISTS subqueries must not have related");
132
+ }
133
+ const conn = source.connect(isNonFlippedExistsChild ? void 0 : must(ast.orderBy), ast.where, splitEditKeys, delegate.debug);
129
134
  let end = delegate.decorateSourceInput(conn, queryID);
130
135
  end = delegate.decorateInput(end, `${name}:source(${ast.table})`);
131
136
  const { fullyAppliedFilters } = conn;
@@ -142,7 +147,12 @@ function buildPipelineInternal(ast, delegate, queryID, name, partitionKey) {
142
147
  }
143
148
  }, delegate, queryID, end, name, true);
144
149
  if (ast.where && (!fullyAppliedFilters || delegate.applyFiltersAnyway)) end = applyWhere(end, ast.where, delegate, name);
145
- if (ast.limit !== void 0) {
150
+ if (ast.limit !== void 0) if (isNonFlippedExistsChild) {
151
+ const capName = `${name}:cap`;
152
+ const cap = new Cap(end, delegate.createStorage(capName), ast.limit, partitionKey);
153
+ delegate.addEdge(end, cap);
154
+ end = delegate.decorateInput(cap, capName);
155
+ } else {
146
156
  const takeName = `${name}:take`;
147
157
  const take = new Take(end, delegate.createStorage(takeName), ast.limit, partitionKey);
148
158
  delegate.addEdge(end, take);
@@ -192,7 +202,7 @@ function applyFilterWithFlips(input, condition, delegate, name) {
192
202
  }
193
203
  case "correlatedSubquery": {
194
204
  const sq = condition.related;
195
- const child = buildPipelineInternal(sq.subquery, delegate, "", `${name}.${sq.subquery.alias}`, sq.correlation.childField);
205
+ const child = buildPipelineInternal(sq.subquery, delegate, "", `${name}.${sq.subquery.alias}`, sq.correlation.childField, false);
196
206
  const flippedJoin = new FlippedJoin({
197
207
  parent: end,
198
208
  child,
@@ -275,7 +285,7 @@ function valuePosName(left) {
275
285
  function applyCorrelatedSubQuery(sq, delegate, queryID, end, name, fromCondition) {
276
286
  if (sq.subquery.limit === 0 && fromCondition) return end;
277
287
  assert(sq.subquery.alias, "Subquery must have an alias");
278
- const child = buildPipelineInternal(sq.subquery, delegate, queryID, `${name}.${sq.subquery.alias}`, sq.correlation.childField);
288
+ const child = buildPipelineInternal(sq.subquery, delegate, queryID, `${name}.${sq.subquery.alias}`, sq.correlation.childField, fromCondition);
279
289
  const joinName = `${name}:join(${sq.subquery.alias})`;
280
290
  const join = new Join({
281
291
  parent: end,
@@ -1 +1 @@
1
- {"version":3,"file":"builder.js","names":[],"sources":["../../../../../zql/src/builder/builder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport type {JSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n AST,\n ColumnReference,\n CompoundKey,\n Condition,\n Conjunction,\n CorrelatedSubquery,\n CorrelatedSubqueryCondition,\n Disjunction,\n LiteralValue,\n Ordering,\n Parameter,\n SimpleCondition,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport {Exists} from '../ivm/exists.ts';\nimport {FanIn} from '../ivm/fan-in.ts';\nimport {FanOut} from '../ivm/fan-out.ts';\nimport {\n buildFilterPipeline,\n type FilterInput,\n} from '../ivm/filter-operators.ts';\nimport {Filter} from '../ivm/filter.ts';\nimport {FlippedJoin} from '../ivm/flipped-join.ts';\nimport {Join} from '../ivm/join.ts';\nimport type {Input, InputBase, Storage} from '../ivm/operator.ts';\nimport {Skip} from '../ivm/skip.ts';\nimport type {Source, SourceInput} from '../ivm/source.ts';\nimport {Take} from '../ivm/take.ts';\nimport {UnionFanIn} from '../ivm/union-fan-in.ts';\nimport {UnionFanOut} from '../ivm/union-fan-out.ts';\nimport {planQuery} from '../planner/planner-builder.ts';\nimport type {ConnectionCostModel} from '../planner/planner-connection.ts';\nimport type {PlanDebugger} from '../planner/planner-debug.ts';\nimport {completeOrdering} from '../query/complete-ordering.ts';\nimport type {DebugDelegate} from './debug-delegate.ts';\nimport {createPredicate, type NoSubqueryCondition} from './filter.ts';\n\nexport type StaticQueryParameters = {\n authData: Record<string, JSONValue>;\n preMutationRow?: Row | undefined;\n};\n\n/**\n * Interface required of caller to buildPipeline. Connects to constructed\n * pipeline to delegate environment to provide sources and storage.\n */\nexport interface BuilderDelegate {\n readonly applyFiltersAnyway?: boolean | undefined;\n debug?: DebugDelegate | undefined;\n\n /**\n * When true, allows NOT EXISTS conditions in queries.\n * Defaults to false.\n *\n * We only set this to true on the server.\n * The client-side query engine cannot support NOT EXISTS because:\n * 1. Zero only syncs a subset of data to the client\n * 2. On the client, we can't distinguish between a row not existing vs.\n * a row not being synced to the client\n * 3. NOT EXISTS requires complete knowledge of what doesn't exist\n */\n readonly enableNotExists?: boolean | undefined;\n\n /**\n * Called once for each source needed by the AST.\n * Might be called multiple times with same tableName. It is OK to return\n * same storage instance in that case.\n */\n getSource(tableName: string): Source | undefined;\n\n /**\n * Called once for each operator that requires storage. Should return a new\n * unique storage object for each call.\n */\n createStorage(name: string): Storage;\n\n decorateInput(input: Input, name: string): Input;\n\n addEdge(source: InputBase, dest: InputBase): void;\n\n decorateFilterInput(input: FilterInput, name: string): FilterInput;\n\n decorateSourceInput(input: SourceInput, queryID: string): Input;\n\n /**\n * The AST is mapped on-the-wire between client and server names.\n *\n * There is no \"wire\" for zqlite tests so this function is provided\n * to allow tests to remap the AST.\n */\n mapAst?: ((ast: AST) => AST) | undefined;\n}\n\n/**\n * Builds a pipeline from an AST. Caller must provide a delegate to create source\n * and storage interfaces as necessary.\n *\n * Usage:\n *\n * ```ts\n * class MySink implements Output {\n * readonly #input: Input;\n *\n * constructor(input: Input) {\n * this.#input = input;\n * input.setOutput(this);\n * }\n *\n * push(change: Change, _: Operator) {\n * console.log(change);\n * }\n * }\n *\n * const input = buildPipeline(ast, myDelegate, hash(ast));\n * const sink = new MySink(input);\n * ```\n */\nexport function buildPipeline(\n ast: AST,\n delegate: BuilderDelegate,\n queryID: string,\n costModel?: ConnectionCostModel,\n lc?: LogContext,\n planDebugger?: PlanDebugger,\n): Input {\n ast = delegate.mapAst ? delegate.mapAst(ast) : ast;\n ast = completeOrdering(\n ast,\n tableName => must(delegate.getSource(tableName)).tableSchema.primaryKey,\n );\n\n if (costModel) {\n ast = planQuery(ast, costModel, planDebugger, lc);\n }\n return buildPipelineInternal(ast, delegate, queryID, '');\n}\n\nexport function bindStaticParameters(\n ast: AST,\n staticQueryParameters: StaticQueryParameters | undefined,\n) {\n const visit = (node: AST): AST => ({\n ...node,\n where: node.where ? bindCondition(node.where) : undefined,\n related: node.related?.map(sq => ({\n ...sq,\n subquery: visit(sq.subquery),\n })),\n });\n\n function bindCondition(condition: Condition): Condition {\n if (condition.type === 'simple') {\n return {\n ...condition,\n left: bindValue(condition.left),\n right: bindValue(condition.right) as Exclude<\n ValuePosition,\n ColumnReference\n >,\n };\n }\n if (condition.type === 'correlatedSubquery') {\n return {\n ...condition,\n related: {\n ...condition.related,\n subquery: visit(condition.related.subquery),\n },\n };\n }\n\n return {\n ...condition,\n conditions: condition.conditions.map(bindCondition),\n };\n }\n\n const bindValue = (value: ValuePosition): ValuePosition => {\n if (isParameter(value)) {\n const anchor = must(\n staticQueryParameters,\n 'Static query params do not exist',\n )[value.anchor];\n const resolvedValue = resolveField(anchor, value.field);\n return {\n type: 'literal',\n value: resolvedValue as LiteralValue,\n };\n }\n return value;\n };\n\n return visit(ast);\n}\n\nfunction resolveField(\n anchor: Record<string, JSONValue> | Row | undefined,\n field: string | string[],\n): unknown {\n if (anchor === undefined) {\n return null;\n }\n\n if (Array.isArray(field)) {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return field.reduce((acc, f) => (acc as any)?.[f], anchor) ?? null;\n }\n\n return anchor[field] ?? null;\n}\n\nfunction isParameter(value: ValuePosition): value is Parameter {\n return value.type === 'static';\n}\n\nconst EXISTS_LIMIT = 3;\nconst PERMISSIONS_EXISTS_LIMIT = 1;\n\n/**\n * Checks if a condition tree contains any NOT EXISTS operations.\n * Recursively checks AND/OR branches but does not recurse into nested subqueries\n * (those are checked when buildPipelineInternal processes them).\n */\nexport function assertNoNotExists(condition: Condition): void {\n switch (condition.type) {\n case 'simple':\n return;\n\n case 'correlatedSubquery':\n if (condition.op === 'NOT EXISTS') {\n throw new Error(\n 'not(exists()) is not supported on the client - see https://bugs.rocicorp.dev/issue/3438',\n );\n }\n return;\n\n case 'and':\n case 'or':\n for (const c of condition.conditions) {\n assertNoNotExists(c);\n }\n return;\n default:\n unreachable(condition);\n }\n}\n\nfunction buildPipelineInternal(\n ast: AST,\n delegate: BuilderDelegate,\n queryID: string,\n name: string,\n partitionKey?: CompoundKey,\n): Input {\n const source = delegate.getSource(ast.table);\n if (!source) {\n throw new Error(`Source not found: ${ast.table}`);\n }\n\n ast = uniquifyCorrelatedSubqueryConditionAliases(ast);\n\n if (!delegate.enableNotExists && ast.where) {\n assertNoNotExists(ast.where);\n }\n\n const csqConditions = gatherCorrelatedSubqueryQueryConditions(ast.where);\n const splitEditKeys: Set<string> = partitionKey\n ? new Set(partitionKey)\n : new Set();\n const aliases = new Set<string>();\n for (const csq of csqConditions) {\n aliases.add(csq.related.subquery.alias || '');\n for (const key of csq.related.correlation.parentField) {\n splitEditKeys.add(key);\n }\n }\n if (ast.related) {\n for (const csq of ast.related) {\n for (const key of csq.correlation.parentField) {\n splitEditKeys.add(key);\n }\n }\n }\n const conn = source.connect(\n must(ast.orderBy),\n ast.where,\n splitEditKeys,\n delegate.debug,\n );\n\n let end: Input = delegate.decorateSourceInput(conn, queryID);\n end = delegate.decorateInput(end, `${name}:source(${ast.table})`);\n const {fullyAppliedFilters} = conn;\n\n if (ast.start) {\n const skip = new Skip(end, ast.start);\n delegate.addEdge(end, skip);\n end = delegate.decorateInput(skip, `${name}:skip)`);\n }\n\n for (const csqCondition of csqConditions) {\n // flipped EXISTS are handled in applyWhere\n if (!csqCondition.flip) {\n end = applyCorrelatedSubQuery(\n {\n ...csqCondition.related,\n subquery: {\n ...csqCondition.related.subquery,\n limit:\n csqCondition.related.system === 'permissions'\n ? PERMISSIONS_EXISTS_LIMIT\n : EXISTS_LIMIT,\n },\n },\n delegate,\n queryID,\n end,\n name,\n true,\n );\n }\n }\n\n if (ast.where && (!fullyAppliedFilters || delegate.applyFiltersAnyway)) {\n end = applyWhere(end, ast.where, delegate, name);\n }\n\n if (ast.limit !== undefined) {\n const takeName = `${name}:take`;\n const take = new Take(\n end,\n delegate.createStorage(takeName),\n ast.limit,\n partitionKey,\n );\n delegate.addEdge(end, take);\n end = delegate.decorateInput(take, takeName);\n }\n\n if (ast.related) {\n // Dedupe by alias - last one wins (LWW), like limit(5).limit(10)\n const byAlias = new Map<string, CorrelatedSubquery>();\n for (const csq of ast.related) {\n byAlias.set(csq.subquery.alias ?? '', csq);\n }\n for (const csq of byAlias.values()) {\n end = applyCorrelatedSubQuery(csq, delegate, queryID, end, name, false);\n }\n }\n\n return end;\n}\n\nfunction applyWhere(\n input: Input,\n condition: Condition,\n delegate: BuilderDelegate,\n name: string,\n): Input {\n if (!conditionIncludesFlippedSubqueryAtAnyLevel(condition)) {\n return buildFilterPipeline(input, delegate, filterInput =>\n applyFilter(filterInput, condition, delegate, name),\n );\n }\n\n return applyFilterWithFlips(input, condition, delegate, name);\n}\n\nfunction applyFilterWithFlips(\n input: Input,\n condition: Condition,\n delegate: BuilderDelegate,\n name: string,\n): Input {\n let end = input;\n assert(condition.type !== 'simple', 'Simple conditions cannot have flips');\n\n switch (condition.type) {\n case 'and': {\n const [withFlipped, withoutFlipped] = partitionBranches(\n condition.conditions,\n conditionIncludesFlippedSubqueryAtAnyLevel,\n );\n if (withoutFlipped.length > 0) {\n end = buildFilterPipeline(input, delegate, filterInput =>\n applyAnd(\n filterInput,\n {\n type: 'and',\n conditions: withoutFlipped,\n },\n delegate,\n name,\n ),\n );\n }\n assert(withFlipped.length > 0, 'Impossible to have no flips here');\n for (const cond of withFlipped) {\n end = applyFilterWithFlips(end, cond, delegate, name);\n }\n break;\n }\n case 'or': {\n const [withFlipped, withoutFlipped] = partitionBranches(\n condition.conditions,\n conditionIncludesFlippedSubqueryAtAnyLevel,\n );\n assert(withFlipped.length > 0, 'Impossible to have no flips here');\n\n const ufo = new UnionFanOut(end);\n delegate.addEdge(end, ufo);\n end = delegate.decorateInput(ufo, `${name}:ufo`);\n\n const branches: Input[] = [];\n if (withoutFlipped.length > 0) {\n branches.push(\n buildFilterPipeline(end, delegate, filterInput =>\n applyOr(\n filterInput,\n {\n type: 'or',\n conditions: withoutFlipped,\n },\n delegate,\n name,\n ),\n ),\n );\n }\n\n for (const cond of withFlipped) {\n branches.push(applyFilterWithFlips(end, cond, delegate, name));\n }\n\n const ufi = new UnionFanIn(ufo, branches);\n for (const branch of branches) {\n delegate.addEdge(branch, ufi);\n }\n end = delegate.decorateInput(ufi, `${name}:ufi`);\n\n break;\n }\n case 'correlatedSubquery': {\n const sq = condition.related;\n const child = buildPipelineInternal(\n sq.subquery,\n delegate,\n '',\n `${name}.${sq.subquery.alias}`,\n sq.correlation.childField,\n );\n const flippedJoin = new FlippedJoin({\n parent: end,\n child,\n parentKey: sq.correlation.parentField,\n childKey: sq.correlation.childField,\n relationshipName: must(\n sq.subquery.alias,\n 'Subquery must have an alias',\n ),\n hidden: sq.hidden ?? false,\n system: sq.system ?? 'client',\n });\n delegate.addEdge(end, flippedJoin);\n delegate.addEdge(child, flippedJoin);\n end = delegate.decorateInput(\n flippedJoin,\n `${name}:flipped-join(${sq.subquery.alias})`,\n );\n break;\n }\n }\n\n return end;\n}\n\nfunction applyFilter(\n input: FilterInput,\n condition: Condition,\n delegate: BuilderDelegate,\n name: string,\n): FilterInput {\n switch (condition.type) {\n case 'and':\n return applyAnd(input, condition, delegate, name);\n case 'or':\n return applyOr(input, condition, delegate, name);\n case 'correlatedSubquery':\n return applyCorrelatedSubqueryCondition(input, condition, delegate, name);\n case 'simple':\n return applySimpleCondition(input, delegate, condition);\n }\n}\n\nfunction applyAnd(\n input: FilterInput,\n condition: Conjunction,\n delegate: BuilderDelegate,\n name: string,\n): FilterInput {\n for (const subCondition of condition.conditions) {\n input = applyFilter(input, subCondition, delegate, name);\n }\n return input;\n}\n\nexport function applyOr(\n input: FilterInput,\n condition: Disjunction,\n delegate: BuilderDelegate,\n name: string,\n): FilterInput {\n const [subqueryConditions, otherConditions] =\n groupSubqueryConditions(condition);\n // if there are no subquery conditions, no fan-in / fan-out is needed\n if (subqueryConditions.length === 0) {\n const filter = new Filter(\n input,\n createPredicate({\n type: 'or',\n conditions: otherConditions,\n }),\n );\n delegate.addEdge(input, filter);\n return filter;\n }\n\n const fanOut = new FanOut(input);\n delegate.addEdge(input, fanOut);\n const branches = subqueryConditions.map(subCondition =>\n applyFilter(fanOut, subCondition, delegate, name),\n );\n if (otherConditions.length > 0) {\n const filter = new Filter(\n fanOut,\n createPredicate({\n type: 'or',\n conditions: otherConditions,\n }),\n );\n delegate.addEdge(fanOut, filter);\n branches.push(filter);\n }\n const ret = new FanIn(fanOut, branches);\n for (const branch of branches) {\n delegate.addEdge(branch, ret);\n }\n fanOut.setFanIn(ret);\n return ret;\n}\n\nexport function groupSubqueryConditions(condition: Disjunction) {\n const partitioned: [\n subqueryConditions: Condition[],\n otherConditions: NoSubqueryCondition[],\n ] = [[], []];\n for (const subCondition of condition.conditions) {\n if (isNotAndDoesNotContainSubquery(subCondition)) {\n partitioned[1].push(subCondition);\n } else {\n partitioned[0].push(subCondition);\n }\n }\n return partitioned;\n}\n\nexport function isNotAndDoesNotContainSubquery(\n condition: Condition,\n): condition is NoSubqueryCondition {\n if (condition.type === 'correlatedSubquery') {\n return false;\n }\n if (condition.type === 'simple') {\n return true;\n }\n return condition.conditions.every(isNotAndDoesNotContainSubquery);\n}\n\nfunction applySimpleCondition(\n input: FilterInput,\n delegate: BuilderDelegate,\n condition: SimpleCondition,\n): FilterInput {\n const filter = new Filter(input, createPredicate(condition));\n delegate.decorateFilterInput(\n filter,\n `${valuePosName(condition.left)}:${condition.op}:${valuePosName(condition.right)}`,\n );\n delegate.addEdge(input, filter);\n return filter;\n}\n\nfunction valuePosName(left: ValuePosition) {\n switch (left.type) {\n case 'static':\n return left.field;\n case 'literal':\n return left.value;\n case 'column':\n return left.name;\n }\n}\n\nfunction applyCorrelatedSubQuery(\n sq: CorrelatedSubquery,\n delegate: BuilderDelegate,\n queryID: string,\n end: Input,\n name: string,\n fromCondition: boolean,\n) {\n // TODO: we only omit the join if the CSQ if from a condition since\n // we want to create an empty array for `related` fields that are `limit(0)`\n if (sq.subquery.limit === 0 && fromCondition) {\n return end;\n }\n\n assert(sq.subquery.alias, 'Subquery must have an alias');\n const child = buildPipelineInternal(\n sq.subquery,\n delegate,\n queryID,\n `${name}.${sq.subquery.alias}`,\n sq.correlation.childField,\n );\n\n const joinName = `${name}:join(${sq.subquery.alias})`;\n const join = new Join({\n parent: end,\n child,\n parentKey: sq.correlation.parentField,\n childKey: sq.correlation.childField,\n relationshipName: sq.subquery.alias,\n hidden: sq.hidden ?? false,\n system: sq.system ?? 'client',\n });\n delegate.addEdge(end, join);\n delegate.addEdge(child, join);\n return delegate.decorateInput(join, joinName);\n}\n\nfunction applyCorrelatedSubqueryCondition(\n input: FilterInput,\n condition: CorrelatedSubqueryCondition,\n delegate: BuilderDelegate,\n name: string,\n): FilterInput {\n assert(\n condition.op === 'EXISTS' || condition.op === 'NOT EXISTS',\n 'Expected EXISTS or NOT EXISTS operator',\n );\n if (condition.related.subquery.limit === 0) {\n if (condition.op === 'EXISTS') {\n const filter = new Filter(input, () => false);\n delegate.addEdge(input, filter);\n return filter;\n }\n const filter = new Filter(input, () => true);\n delegate.addEdge(input, filter);\n return filter;\n }\n const existsName = `${name}:exists(${condition.related.subquery.alias})`;\n const exists = new Exists(\n input,\n must(condition.related.subquery.alias),\n condition.related.correlation.parentField,\n condition.op,\n );\n delegate.addEdge(input, exists);\n return delegate.decorateFilterInput(exists, existsName);\n}\n\nfunction gatherCorrelatedSubqueryQueryConditions(\n condition: Condition | undefined,\n) {\n const csqs: CorrelatedSubqueryCondition[] = [];\n const gather = (condition: Condition) => {\n if (condition.type === 'correlatedSubquery') {\n csqs.push(condition);\n return;\n }\n if (condition.type === 'and' || condition.type === 'or') {\n for (const c of condition.conditions) {\n gather(c);\n }\n return;\n }\n };\n if (condition) {\n gather(condition);\n }\n return csqs;\n}\n\nexport function assertOrderingIncludesPK(\n ordering: Ordering,\n pk: PrimaryKey,\n): void {\n // oxlint-disable-next-line unicorn/prefer-set-has -- Array is more appropriate here for small collections\n const orderingFields = ordering.map(([field]) => field);\n const missingFields = pk.filter(pkField => !orderingFields.includes(pkField));\n\n if (missingFields.length > 0) {\n throw new Error(\n `Ordering must include all primary key fields. Missing: ${missingFields.join(\n ', ',\n )}. ZQL automatically appends primary key fields to the ordering if they are missing \n so a common cause of this error is a casing mismatch between Postgres and ZQL.\n E.g., \"userid\" vs \"userID\".\n You may want to add double-quotes around your Postgres column names to prevent Postgres from lower-casing them:\n https://www.postgresql.org/docs/current/sql-syntax-lexical.htm`,\n );\n }\n}\n\nfunction uniquifyCorrelatedSubqueryConditionAliases(ast: AST): AST {\n if (!ast.where) {\n return ast;\n }\n const {where} = ast;\n if (where.type !== 'and' && where.type !== 'or') {\n return ast;\n }\n\n let count = 0;\n const uniquifyCorrelatedSubquery = (csqc: CorrelatedSubqueryCondition) => ({\n ...csqc,\n related: {\n ...csqc.related,\n subquery: {\n ...csqc.related.subquery,\n alias: (csqc.related.subquery.alias ?? '') + '_' + count++,\n },\n },\n });\n\n const uniquify = (cond: Condition): Condition => {\n if (cond.type === 'simple') {\n return cond;\n } else if (cond.type === 'correlatedSubquery') {\n return uniquifyCorrelatedSubquery(cond);\n }\n const conditions = [];\n for (const c of cond.conditions) {\n conditions.push(uniquify(c));\n }\n return {\n type: cond.type,\n conditions,\n };\n };\n\n const result = {\n ...ast,\n where: uniquify(where),\n };\n return result;\n}\n\nexport function conditionIncludesFlippedSubqueryAtAnyLevel(\n cond: Condition,\n): boolean {\n if (cond.type === 'correlatedSubquery') {\n return !!cond.flip;\n }\n if (cond.type === 'and' || cond.type === 'or') {\n return cond.conditions.some(c =>\n conditionIncludesFlippedSubqueryAtAnyLevel(c),\n );\n }\n // simple conditions don't have flips\n return false;\n}\n\nexport function partitionBranches(\n conditions: readonly Condition[],\n predicate: (c: Condition) => boolean,\n) {\n const matched: Condition[] = [];\n const notMatched: Condition[] = [];\n for (const c of conditions) {\n if (predicate(c)) {\n matched.push(c);\n } else {\n notMatched.push(c);\n }\n }\n return [matched, notMatched] as const;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"builder.js","names":[],"sources":["../../../../../zql/src/builder/builder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport type {JSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n AST,\n ColumnReference,\n CompoundKey,\n Condition,\n Conjunction,\n CorrelatedSubquery,\n CorrelatedSubqueryCondition,\n Disjunction,\n LiteralValue,\n Ordering,\n Parameter,\n SimpleCondition,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport {Cap} from '../ivm/cap.ts';\nimport {Exists} from '../ivm/exists.ts';\nimport {FanIn} from '../ivm/fan-in.ts';\nimport {FanOut} from '../ivm/fan-out.ts';\nimport {\n buildFilterPipeline,\n type FilterInput,\n} from '../ivm/filter-operators.ts';\nimport {Filter} from '../ivm/filter.ts';\nimport {FlippedJoin} from '../ivm/flipped-join.ts';\nimport {Join} from '../ivm/join.ts';\nimport type {Input, InputBase, Storage} from '../ivm/operator.ts';\nimport {Skip} from '../ivm/skip.ts';\nimport type {Source, SourceInput} from '../ivm/source.ts';\nimport {Take} from '../ivm/take.ts';\nimport {UnionFanIn} from '../ivm/union-fan-in.ts';\nimport {UnionFanOut} from '../ivm/union-fan-out.ts';\nimport {planQuery} from '../planner/planner-builder.ts';\nimport type {ConnectionCostModel} from '../planner/planner-connection.ts';\nimport type {PlanDebugger} from '../planner/planner-debug.ts';\nimport {completeOrdering} from '../query/complete-ordering.ts';\nimport type {DebugDelegate} from './debug-delegate.ts';\nimport {createPredicate, type NoSubqueryCondition} from './filter.ts';\n\nexport type StaticQueryParameters = {\n authData: Record<string, JSONValue>;\n preMutationRow?: Row | undefined;\n};\n\n/**\n * Interface required of caller to buildPipeline. Connects to constructed\n * pipeline to delegate environment to provide sources and storage.\n */\nexport interface BuilderDelegate {\n readonly applyFiltersAnyway?: boolean | undefined;\n debug?: DebugDelegate | undefined;\n\n /**\n * When true, allows NOT EXISTS conditions in queries.\n * Defaults to false.\n *\n * We only set this to true on the server.\n * The client-side query engine cannot support NOT EXISTS because:\n * 1. Zero only syncs a subset of data to the client\n * 2. On the client, we can't distinguish between a row not existing vs.\n * a row not being synced to the client\n * 3. NOT EXISTS requires complete knowledge of what doesn't exist\n */\n readonly enableNotExists?: boolean | undefined;\n\n /**\n * Called once for each source needed by the AST.\n * Might be called multiple times with same tableName. It is OK to return\n * same storage instance in that case.\n */\n getSource(tableName: string): Source | undefined;\n\n /**\n * Called once for each operator that requires storage. Should return a new\n * unique storage object for each call.\n */\n createStorage(name: string): Storage;\n\n decorateInput(input: Input, name: string): Input;\n\n addEdge(source: InputBase, dest: InputBase): void;\n\n decorateFilterInput(input: FilterInput, name: string): FilterInput;\n\n decorateSourceInput(input: SourceInput, queryID: string): Input;\n\n /**\n * The AST is mapped on-the-wire between client and server names.\n *\n * There is no \"wire\" for zqlite tests so this function is provided\n * to allow tests to remap the AST.\n */\n mapAst?: ((ast: AST) => AST) | undefined;\n}\n\n/**\n * Builds a pipeline from an AST. Caller must provide a delegate to create source\n * and storage interfaces as necessary.\n *\n * Usage:\n *\n * ```ts\n * class MySink implements Output {\n * readonly #input: Input;\n *\n * constructor(input: Input) {\n * this.#input = input;\n * input.setOutput(this);\n * }\n *\n * push(change: Change, _: Operator) {\n * console.log(change);\n * }\n * }\n *\n * const input = buildPipeline(ast, myDelegate, hash(ast));\n * const sink = new MySink(input);\n * ```\n */\nexport function buildPipeline(\n ast: AST,\n delegate: BuilderDelegate,\n queryID: string,\n costModel?: ConnectionCostModel,\n lc?: LogContext,\n planDebugger?: PlanDebugger,\n): Input {\n ast = delegate.mapAst ? delegate.mapAst(ast) : ast;\n ast = completeOrdering(\n ast,\n tableName => must(delegate.getSource(tableName)).tableSchema.primaryKey,\n );\n\n if (costModel) {\n ast = planQuery(ast, costModel, planDebugger, lc);\n }\n return buildPipelineInternal(ast, delegate, queryID, '');\n}\n\nexport function bindStaticParameters(\n ast: AST,\n staticQueryParameters: StaticQueryParameters | undefined,\n) {\n const visit = (node: AST): AST => ({\n ...node,\n where: node.where ? bindCondition(node.where) : undefined,\n related: node.related?.map(sq => ({\n ...sq,\n subquery: visit(sq.subquery),\n })),\n });\n\n function bindCondition(condition: Condition): Condition {\n if (condition.type === 'simple') {\n return {\n ...condition,\n left: bindValue(condition.left),\n right: bindValue(condition.right) as Exclude<\n ValuePosition,\n ColumnReference\n >,\n };\n }\n if (condition.type === 'correlatedSubquery') {\n return {\n ...condition,\n related: {\n ...condition.related,\n subquery: visit(condition.related.subquery),\n },\n };\n }\n\n return {\n ...condition,\n conditions: condition.conditions.map(bindCondition),\n };\n }\n\n const bindValue = (value: ValuePosition): ValuePosition => {\n if (isParameter(value)) {\n const anchor = must(\n staticQueryParameters,\n 'Static query params do not exist',\n )[value.anchor];\n const resolvedValue = resolveField(anchor, value.field);\n return {\n type: 'literal',\n value: resolvedValue as LiteralValue,\n };\n }\n return value;\n };\n\n return visit(ast);\n}\n\nfunction resolveField(\n anchor: Record<string, JSONValue> | Row | undefined,\n field: string | string[],\n): unknown {\n if (anchor === undefined) {\n return null;\n }\n\n if (Array.isArray(field)) {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return field.reduce((acc, f) => (acc as any)?.[f], anchor) ?? null;\n }\n\n return anchor[field] ?? null;\n}\n\nfunction isParameter(value: ValuePosition): value is Parameter {\n return value.type === 'static';\n}\n\nconst EXISTS_LIMIT = 3;\nconst PERMISSIONS_EXISTS_LIMIT = 1;\n\n/**\n * Checks if a condition tree contains any NOT EXISTS operations.\n * Recursively checks AND/OR branches but does not recurse into nested subqueries\n * (those are checked when buildPipelineInternal processes them).\n */\nexport function assertNoNotExists(condition: Condition): void {\n switch (condition.type) {\n case 'simple':\n return;\n\n case 'correlatedSubquery':\n if (condition.op === 'NOT EXISTS') {\n throw new Error(\n 'not(exists()) is not supported on the client - see https://bugs.rocicorp.dev/issue/3438',\n );\n }\n return;\n\n case 'and':\n case 'or':\n for (const c of condition.conditions) {\n assertNoNotExists(c);\n }\n return;\n default:\n unreachable(condition);\n }\n}\n\nfunction buildPipelineInternal(\n ast: AST,\n delegate: BuilderDelegate,\n queryID: string,\n name: string,\n partitionKey?: CompoundKey,\n isNonFlippedExistsChild?: boolean | undefined,\n): Input {\n const source = delegate.getSource(ast.table);\n if (!source) {\n throw new Error(`Source not found: ${ast.table}`);\n }\n\n ast = uniquifyCorrelatedSubqueryConditionAliases(ast);\n\n if (!delegate.enableNotExists && ast.where) {\n assertNoNotExists(ast.where);\n }\n\n const csqConditions = gatherCorrelatedSubqueryQueryConditions(ast.where);\n const splitEditKeys: Set<string> = partitionKey\n ? new Set(partitionKey)\n : new Set();\n const aliases = new Set<string>();\n for (const csq of csqConditions) {\n aliases.add(csq.related.subquery.alias || '');\n for (const key of csq.related.correlation.parentField) {\n splitEditKeys.add(key);\n }\n }\n if (ast.related) {\n for (const csq of ast.related) {\n for (const key of csq.correlation.parentField) {\n splitEditKeys.add(key);\n }\n }\n }\n if (isNonFlippedExistsChild) {\n assert(ast.start === undefined, 'EXISTS subqueries must not have start');\n assert(\n ast.related === undefined,\n 'EXISTS subqueries must not have related',\n );\n }\n\n const conn = source.connect(\n // exists pipelines are unordered — orderBy is ignored here.\n // Non-exists pipelines always have orderBy completed with PKs.\n isNonFlippedExistsChild ? undefined : must(ast.orderBy),\n ast.where,\n splitEditKeys,\n delegate.debug,\n );\n\n let end: Input = delegate.decorateSourceInput(conn, queryID);\n end = delegate.decorateInput(end, `${name}:source(${ast.table})`);\n const {fullyAppliedFilters} = conn;\n\n if (ast.start) {\n const skip = new Skip(end, ast.start);\n delegate.addEdge(end, skip);\n end = delegate.decorateInput(skip, `${name}:skip)`);\n }\n\n for (const csqCondition of csqConditions) {\n // flipped EXISTS are handled in applyWhere\n if (!csqCondition.flip) {\n end = applyCorrelatedSubQuery(\n {\n ...csqCondition.related,\n subquery: {\n ...csqCondition.related.subquery,\n limit:\n csqCondition.related.system === 'permissions'\n ? PERMISSIONS_EXISTS_LIMIT\n : EXISTS_LIMIT,\n },\n },\n delegate,\n queryID,\n end,\n name,\n true,\n );\n }\n }\n\n if (ast.where && (!fullyAppliedFilters || delegate.applyFiltersAnyway)) {\n end = applyWhere(end, ast.where, delegate, name);\n }\n\n if (ast.limit !== undefined) {\n // We end `exists` pipelines with `cap`\n // The reason is that `cap` does not care about the order of the pipeline.\n // This allows SQLite to chose the order and never end up creating temp b-trees.\n // The problem with SQLite creating a temp b-tree is it will incur a scan of the entire\n // result set where exists only needs the first row.\n if (isNonFlippedExistsChild) {\n const capName = `${name}:cap`;\n const cap = new Cap(\n end,\n delegate.createStorage(capName),\n ast.limit,\n partitionKey,\n );\n delegate.addEdge(end, cap);\n end = delegate.decorateInput(cap, capName);\n } else {\n const takeName = `${name}:take`;\n const take = new Take(\n end,\n delegate.createStorage(takeName),\n ast.limit,\n partitionKey,\n );\n delegate.addEdge(end, take);\n end = delegate.decorateInput(take, takeName);\n }\n }\n\n if (ast.related) {\n // Dedupe by alias - last one wins (LWW), like limit(5).limit(10)\n const byAlias = new Map<string, CorrelatedSubquery>();\n for (const csq of ast.related) {\n byAlias.set(csq.subquery.alias ?? '', csq);\n }\n for (const csq of byAlias.values()) {\n end = applyCorrelatedSubQuery(csq, delegate, queryID, end, name, false);\n }\n }\n\n return end;\n}\n\nfunction applyWhere(\n input: Input,\n condition: Condition,\n delegate: BuilderDelegate,\n name: string,\n): Input {\n if (!conditionIncludesFlippedSubqueryAtAnyLevel(condition)) {\n return buildFilterPipeline(input, delegate, filterInput =>\n applyFilter(filterInput, condition, delegate, name),\n );\n }\n\n return applyFilterWithFlips(input, condition, delegate, name);\n}\n\nfunction applyFilterWithFlips(\n input: Input,\n condition: Condition,\n delegate: BuilderDelegate,\n name: string,\n): Input {\n let end = input;\n assert(condition.type !== 'simple', 'Simple conditions cannot have flips');\n\n switch (condition.type) {\n case 'and': {\n const [withFlipped, withoutFlipped] = partitionBranches(\n condition.conditions,\n conditionIncludesFlippedSubqueryAtAnyLevel,\n );\n if (withoutFlipped.length > 0) {\n end = buildFilterPipeline(input, delegate, filterInput =>\n applyAnd(\n filterInput,\n {\n type: 'and',\n conditions: withoutFlipped,\n },\n delegate,\n name,\n ),\n );\n }\n assert(withFlipped.length > 0, 'Impossible to have no flips here');\n for (const cond of withFlipped) {\n end = applyFilterWithFlips(end, cond, delegate, name);\n }\n break;\n }\n case 'or': {\n const [withFlipped, withoutFlipped] = partitionBranches(\n condition.conditions,\n conditionIncludesFlippedSubqueryAtAnyLevel,\n );\n assert(withFlipped.length > 0, 'Impossible to have no flips here');\n\n const ufo = new UnionFanOut(end);\n delegate.addEdge(end, ufo);\n end = delegate.decorateInput(ufo, `${name}:ufo`);\n\n const branches: Input[] = [];\n if (withoutFlipped.length > 0) {\n branches.push(\n buildFilterPipeline(end, delegate, filterInput =>\n applyOr(\n filterInput,\n {\n type: 'or',\n conditions: withoutFlipped,\n },\n delegate,\n name,\n ),\n ),\n );\n }\n\n for (const cond of withFlipped) {\n branches.push(applyFilterWithFlips(end, cond, delegate, name));\n }\n\n const ufi = new UnionFanIn(ufo, branches);\n for (const branch of branches) {\n delegate.addEdge(branch, ufi);\n }\n end = delegate.decorateInput(ufi, `${name}:ufi`);\n\n break;\n }\n case 'correlatedSubquery': {\n const sq = condition.related;\n const child = buildPipelineInternal(\n sq.subquery,\n delegate,\n '',\n `${name}.${sq.subquery.alias}`,\n sq.correlation.childField,\n false,\n );\n const flippedJoin = new FlippedJoin({\n parent: end,\n child,\n parentKey: sq.correlation.parentField,\n childKey: sq.correlation.childField,\n relationshipName: must(\n sq.subquery.alias,\n 'Subquery must have an alias',\n ),\n hidden: sq.hidden ?? false,\n system: sq.system ?? 'client',\n });\n delegate.addEdge(end, flippedJoin);\n delegate.addEdge(child, flippedJoin);\n end = delegate.decorateInput(\n flippedJoin,\n `${name}:flipped-join(${sq.subquery.alias})`,\n );\n break;\n }\n }\n\n return end;\n}\n\nfunction applyFilter(\n input: FilterInput,\n condition: Condition,\n delegate: BuilderDelegate,\n name: string,\n): FilterInput {\n switch (condition.type) {\n case 'and':\n return applyAnd(input, condition, delegate, name);\n case 'or':\n return applyOr(input, condition, delegate, name);\n case 'correlatedSubquery':\n return applyCorrelatedSubqueryCondition(input, condition, delegate, name);\n case 'simple':\n return applySimpleCondition(input, delegate, condition);\n }\n}\n\nfunction applyAnd(\n input: FilterInput,\n condition: Conjunction,\n delegate: BuilderDelegate,\n name: string,\n): FilterInput {\n for (const subCondition of condition.conditions) {\n input = applyFilter(input, subCondition, delegate, name);\n }\n return input;\n}\n\nexport function applyOr(\n input: FilterInput,\n condition: Disjunction,\n delegate: BuilderDelegate,\n name: string,\n): FilterInput {\n const [subqueryConditions, otherConditions] =\n groupSubqueryConditions(condition);\n // if there are no subquery conditions, no fan-in / fan-out is needed\n if (subqueryConditions.length === 0) {\n const filter = new Filter(\n input,\n createPredicate({\n type: 'or',\n conditions: otherConditions,\n }),\n );\n delegate.addEdge(input, filter);\n return filter;\n }\n\n const fanOut = new FanOut(input);\n delegate.addEdge(input, fanOut);\n const branches = subqueryConditions.map(subCondition =>\n applyFilter(fanOut, subCondition, delegate, name),\n );\n if (otherConditions.length > 0) {\n const filter = new Filter(\n fanOut,\n createPredicate({\n type: 'or',\n conditions: otherConditions,\n }),\n );\n delegate.addEdge(fanOut, filter);\n branches.push(filter);\n }\n const ret = new FanIn(fanOut, branches);\n for (const branch of branches) {\n delegate.addEdge(branch, ret);\n }\n fanOut.setFanIn(ret);\n return ret;\n}\n\nexport function groupSubqueryConditions(condition: Disjunction) {\n const partitioned: [\n subqueryConditions: Condition[],\n otherConditions: NoSubqueryCondition[],\n ] = [[], []];\n for (const subCondition of condition.conditions) {\n if (isNotAndDoesNotContainSubquery(subCondition)) {\n partitioned[1].push(subCondition);\n } else {\n partitioned[0].push(subCondition);\n }\n }\n return partitioned;\n}\n\nexport function isNotAndDoesNotContainSubquery(\n condition: Condition,\n): condition is NoSubqueryCondition {\n if (condition.type === 'correlatedSubquery') {\n return false;\n }\n if (condition.type === 'simple') {\n return true;\n }\n return condition.conditions.every(isNotAndDoesNotContainSubquery);\n}\n\nfunction applySimpleCondition(\n input: FilterInput,\n delegate: BuilderDelegate,\n condition: SimpleCondition,\n): FilterInput {\n const filter = new Filter(input, createPredicate(condition));\n delegate.decorateFilterInput(\n filter,\n `${valuePosName(condition.left)}:${condition.op}:${valuePosName(condition.right)}`,\n );\n delegate.addEdge(input, filter);\n return filter;\n}\n\nfunction valuePosName(left: ValuePosition) {\n switch (left.type) {\n case 'static':\n return left.field;\n case 'literal':\n return left.value;\n case 'column':\n return left.name;\n }\n}\n\nfunction applyCorrelatedSubQuery(\n sq: CorrelatedSubquery,\n delegate: BuilderDelegate,\n queryID: string,\n end: Input,\n name: string,\n fromCondition: boolean,\n) {\n // TODO: we only omit the join if the CSQ if from a condition since\n // we want to create an empty array for `related` fields that are `limit(0)`\n if (sq.subquery.limit === 0 && fromCondition) {\n return end;\n }\n\n assert(sq.subquery.alias, 'Subquery must have an alias');\n const child = buildPipelineInternal(\n sq.subquery,\n delegate,\n queryID,\n `${name}.${sq.subquery.alias}`,\n sq.correlation.childField,\n fromCondition,\n );\n\n const joinName = `${name}:join(${sq.subquery.alias})`;\n const join = new Join({\n parent: end,\n child,\n parentKey: sq.correlation.parentField,\n childKey: sq.correlation.childField,\n relationshipName: sq.subquery.alias,\n hidden: sq.hidden ?? false,\n system: sq.system ?? 'client',\n });\n delegate.addEdge(end, join);\n delegate.addEdge(child, join);\n return delegate.decorateInput(join, joinName);\n}\n\nfunction applyCorrelatedSubqueryCondition(\n input: FilterInput,\n condition: CorrelatedSubqueryCondition,\n delegate: BuilderDelegate,\n name: string,\n): FilterInput {\n assert(\n condition.op === 'EXISTS' || condition.op === 'NOT EXISTS',\n 'Expected EXISTS or NOT EXISTS operator',\n );\n if (condition.related.subquery.limit === 0) {\n if (condition.op === 'EXISTS') {\n const filter = new Filter(input, () => false);\n delegate.addEdge(input, filter);\n return filter;\n }\n const filter = new Filter(input, () => true);\n delegate.addEdge(input, filter);\n return filter;\n }\n const existsName = `${name}:exists(${condition.related.subquery.alias})`;\n const exists = new Exists(\n input,\n must(condition.related.subquery.alias),\n condition.related.correlation.parentField,\n condition.op,\n );\n delegate.addEdge(input, exists);\n return delegate.decorateFilterInput(exists, existsName);\n}\n\nfunction gatherCorrelatedSubqueryQueryConditions(\n condition: Condition | undefined,\n) {\n const csqs: CorrelatedSubqueryCondition[] = [];\n const gather = (condition: Condition) => {\n if (condition.type === 'correlatedSubquery') {\n csqs.push(condition);\n return;\n }\n if (condition.type === 'and' || condition.type === 'or') {\n for (const c of condition.conditions) {\n gather(c);\n }\n return;\n }\n };\n if (condition) {\n gather(condition);\n }\n return csqs;\n}\n\nexport function assertOrderingIncludesPK(\n ordering: Ordering,\n pk: PrimaryKey,\n): void {\n // oxlint-disable-next-line unicorn/prefer-set-has -- Array is more appropriate here for small collections\n const orderingFields = ordering.map(([field]) => field);\n const missingFields = pk.filter(pkField => !orderingFields.includes(pkField));\n\n if (missingFields.length > 0) {\n throw new Error(\n `Ordering must include all primary key fields. Missing: ${missingFields.join(\n ', ',\n )}. ZQL automatically appends primary key fields to the ordering if they are missing \n so a common cause of this error is a casing mismatch between Postgres and ZQL.\n E.g., \"userid\" vs \"userID\".\n You may want to add double-quotes around your Postgres column names to prevent Postgres from lower-casing them:\n https://www.postgresql.org/docs/current/sql-syntax-lexical.htm`,\n );\n }\n}\n\nfunction uniquifyCorrelatedSubqueryConditionAliases(ast: AST): AST {\n if (!ast.where) {\n return ast;\n }\n const {where} = ast;\n if (where.type !== 'and' && where.type !== 'or') {\n return ast;\n }\n\n let count = 0;\n const uniquifyCorrelatedSubquery = (csqc: CorrelatedSubqueryCondition) => ({\n ...csqc,\n related: {\n ...csqc.related,\n subquery: {\n ...csqc.related.subquery,\n alias: (csqc.related.subquery.alias ?? '') + '_' + count++,\n },\n },\n });\n\n const uniquify = (cond: Condition): Condition => {\n if (cond.type === 'simple') {\n return cond;\n } else if (cond.type === 'correlatedSubquery') {\n return uniquifyCorrelatedSubquery(cond);\n }\n const conditions = [];\n for (const c of cond.conditions) {\n conditions.push(uniquify(c));\n }\n return {\n type: cond.type,\n conditions,\n };\n };\n\n const result = {\n ...ast,\n where: uniquify(where),\n };\n return result;\n}\n\nexport function conditionIncludesFlippedSubqueryAtAnyLevel(\n cond: Condition,\n): boolean {\n if (cond.type === 'correlatedSubquery') {\n return !!cond.flip;\n }\n if (cond.type === 'and' || cond.type === 'or') {\n return cond.conditions.some(c =>\n conditionIncludesFlippedSubqueryAtAnyLevel(c),\n );\n }\n // simple conditions don't have flips\n return false;\n}\n\nexport function partitionBranches(\n conditions: readonly Condition[],\n predicate: (c: Condition) => boolean,\n) {\n const matched: Condition[] = [];\n const notMatched: Condition[] = [];\n for (const c of conditions) {\n if (predicate(c)) {\n matched.push(c);\n } else {\n notMatched.push(c);\n }\n }\n return [matched, notMatched] as const;\n}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { type Change } from './change.ts';
2
+ import type { Node } from './data.ts';
3
+ import { type FetchRequest, type Input, type Operator, type Output, type Storage } from './operator.ts';
4
+ import type { SourceSchema } from './schema.ts';
5
+ import { type Stream } from './stream.ts';
6
+ import { type PartitionKey } from './take.ts';
7
+ /**
8
+ * The Cap operator is a count-based limiter for EXISTS subqueries that
9
+ * does not require ordering. Unlike Take, it tracks membership by primary
10
+ * key set rather than by a sorted bound. This means:
11
+ *
12
+ * - No comparator needed (no ordering requirement)
13
+ * - No `start` or `reverse` fetch support
14
+ * - No `#rowHiddenFromFetch` complexity (we can defer when adding to the pk set)
15
+ *
16
+ * Cap is used in EXISTS child pipelines where only the count of matching
17
+ * rows matters, not their order. This allows SQLite to skip ORDER BY
18
+ * entirely, enabling much faster query plans.
19
+ *
20
+ * Cap can count rows globally or by unique value of some partition key
21
+ * (same as Take).
22
+ */
23
+ export declare class Cap implements Operator {
24
+ #private;
25
+ constructor(input: Input, storage: Storage, limit: number, partitionKey?: PartitionKey);
26
+ setOutput(output: Output): void;
27
+ getSchema(): SourceSchema;
28
+ fetch(req: FetchRequest): Stream<Node | 'yield'>;
29
+ push(change: Change): Stream<'yield'>;
30
+ destroy(): void;
31
+ }
32
+ //# sourceMappingURL=cap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cap.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/cap.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgB,KAAK,MAAM,EAAkB,MAAM,aAAa,CAAC;AAExE,OAAO,KAAK,EAAa,IAAI,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,OAAO,EACb,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,WAAW,CAAC;AAanB;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,GAAI,YAAW,QAAQ;;gBAWhC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,YAAY;IAa7B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,SAAS,IAAI,YAAY;IAIxB,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC;IA0FhD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;IAsHtC,OAAO,IAAI,IAAI;CAGhB"}