@rocicorp/zero 1.4.0-canary.5 → 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 (200) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/zero/package.js +1 -1
  4. package/out/zero/package.js.map +1 -1
  5. package/out/zero-cache/src/auth/auth.d.ts +1 -1
  6. package/out/zero-cache/src/auth/auth.d.ts.map +1 -1
  7. package/out/zero-cache/src/auth/auth.js +1 -1
  8. package/out/zero-cache/src/auth/auth.js.map +1 -1
  9. package/out/zero-cache/src/auth/write-authorizer.d.ts +1 -1
  10. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  11. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  12. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  13. package/out/zero-cache/src/config/normalize.js +8 -0
  14. package/out/zero-cache/src/config/normalize.js.map +1 -1
  15. package/out/zero-cache/src/config/zero-config.d.ts +8 -4
  16. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  17. package/out/zero-cache/src/config/zero-config.js +28 -6
  18. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  19. package/out/zero-cache/src/custom/fetch.d.ts +1 -1
  20. package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
  21. package/out/zero-cache/src/custom/fetch.js +2 -2
  22. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  23. package/out/zero-cache/src/custom-queries/transform-query.d.ts +21 -7
  24. package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
  25. package/out/zero-cache/src/custom-queries/transform-query.js +26 -9
  26. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  27. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  28. package/out/zero-cache/src/server/change-streamer.js +2 -1
  29. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  30. package/out/zero-cache/src/server/runner/run-worker.d.ts.map +1 -1
  31. package/out/zero-cache/src/server/runner/run-worker.js +5 -2
  32. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  33. package/out/zero-cache/src/server/syncer.js +3 -3
  34. package/out/zero-cache/src/server/syncer.js.map +1 -1
  35. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  36. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  37. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  38. package/out/zero-cache/src/services/change-source/pg/change-source.js +24 -20
  39. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  40. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +258 -45
  41. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  42. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +119 -83
  43. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  44. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  45. package/out/zero-cache/src/services/change-source/pg/schema/init.js +1 -1
  46. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  47. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +11 -2
  49. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  50. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +1 -0
  51. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  52. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -3
  53. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  54. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/change-streamer/storer.js +3 -3
  56. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  57. package/out/zero-cache/src/services/http-service.d.ts +1 -0
  58. package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/http-service.js +5 -4
  60. package/out/zero-cache/src/services/http-service.js.map +1 -1
  61. package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
  62. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/life-cycle.js +1 -2
  64. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  65. package/out/zero-cache/src/services/mutagen/mutagen.d.ts +1 -1
  66. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  67. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
  68. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  69. package/out/zero-cache/src/services/mutagen/pusher.d.ts +4 -3
  70. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/mutagen/pusher.js +57 -38
  72. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  73. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts.map +1 -1
  74. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  75. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js +2 -1
  76. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
  77. package/out/zero-cache/src/services/view-syncer/client-handler.js +1 -1
  78. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  79. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +39 -27
  80. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.map +1 -1
  81. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js +138 -102
  82. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  83. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  84. package/out/zero-cache/src/services/view-syncer/cvr-store.js +22 -2
  85. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  86. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +6 -0
  87. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  88. package/out/zero-cache/src/services/view-syncer/cvr.js +8 -0
  89. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  90. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +27 -3
  92. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  93. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +3 -3
  94. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  95. package/out/zero-cache/src/services/view-syncer/view-syncer.js +115 -86
  96. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  97. package/out/zero-cache/src/workers/connection.js +2 -2
  98. package/out/zero-cache/src/workers/connection.js.map +1 -1
  99. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts +1 -1
  100. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  101. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +7 -7
  102. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  103. package/out/zero-cache/src/workers/syncer.d.ts +1 -1
  104. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  105. package/out/zero-cache/src/workers/syncer.js +11 -10
  106. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  107. package/out/zero-client/src/client/connection.d.ts +15 -7
  108. package/out/zero-client/src/client/connection.d.ts.map +1 -1
  109. package/out/zero-client/src/client/connection.js.map +1 -1
  110. package/out/zero-client/src/client/crud-impl.d.ts +1 -1
  111. package/out/zero-client/src/client/crud-impl.d.ts.map +1 -1
  112. package/out/zero-client/src/client/crud-impl.js +1 -1
  113. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  114. package/out/zero-client/src/client/crud.d.ts +1 -1
  115. package/out/zero-client/src/client/crud.d.ts.map +1 -1
  116. package/out/zero-client/src/client/crud.js +1 -1
  117. package/out/zero-client/src/client/crud.js.map +1 -1
  118. package/out/zero-client/src/client/keys.d.ts +1 -1
  119. package/out/zero-client/src/client/keys.d.ts.map +1 -1
  120. package/out/zero-client/src/client/keys.js.map +1 -1
  121. package/out/zero-client/src/client/make-replicache-mutators.js +1 -1
  122. package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
  123. package/out/zero-client/src/client/mutation-tracker.d.ts +2 -1
  124. package/out/zero-client/src/client/mutation-tracker.d.ts.map +1 -1
  125. package/out/zero-client/src/client/mutation-tracker.js +3 -3
  126. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  127. package/out/zero-client/src/client/version.js +1 -1
  128. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  129. package/out/zero-client/src/client/zero.js +2 -2
  130. package/out/zero-client/src/client/zero.js.map +1 -1
  131. package/out/zero-client/src/types/client-state.d.ts +1 -1
  132. package/out/zero-client/src/types/client-state.d.ts.map +1 -1
  133. package/out/zero-protocol/src/custom-queries.js +1 -1
  134. package/out/zero-protocol/src/down.js +1 -1
  135. package/out/zero-protocol/src/error-kind-enum.d.ts +1 -2
  136. package/out/zero-protocol/src/error-kind-enum.d.ts.map +1 -1
  137. package/out/zero-protocol/src/error-kind-enum.js.map +1 -1
  138. package/out/zero-protocol/src/mutate-server.d.ts +165 -0
  139. package/out/zero-protocol/src/mutate-server.d.ts.map +1 -0
  140. package/out/zero-protocol/src/mutate-server.js +24 -0
  141. package/out/zero-protocol/src/mutate-server.js.map +1 -0
  142. package/out/zero-protocol/src/mutation.d.ts +229 -0
  143. package/out/zero-protocol/src/mutation.d.ts.map +1 -0
  144. package/out/zero-protocol/src/mutation.js +112 -0
  145. package/out/zero-protocol/src/mutation.js.map +1 -0
  146. package/out/zero-protocol/src/mutations-patch.js +1 -1
  147. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  148. package/out/zero-protocol/src/push.d.ts +3 -234
  149. package/out/zero-protocol/src/push.d.ts.map +1 -1
  150. package/out/zero-protocol/src/push.js +3 -114
  151. package/out/zero-protocol/src/push.js.map +1 -1
  152. package/out/zero-protocol/src/query-server.d.ts +150 -0
  153. package/out/zero-protocol/src/query-server.d.ts.map +1 -0
  154. package/out/zero-protocol/src/query-server.js +16 -0
  155. package/out/zero-protocol/src/query-server.js.map +1 -0
  156. package/out/zero-protocol/src/up.js +1 -1
  157. package/out/zero-server/src/mod.d.ts +4 -2
  158. package/out/zero-server/src/mod.d.ts.map +1 -1
  159. package/out/zero-server/src/process-mutations.d.ts +41 -4
  160. package/out/zero-server/src/process-mutations.d.ts.map +1 -1
  161. package/out/zero-server/src/process-mutations.js +52 -35
  162. package/out/zero-server/src/process-mutations.js.map +1 -1
  163. package/out/zero-server/src/push-processor.d.ts +3 -3
  164. package/out/zero-server/src/push-processor.d.ts.map +1 -1
  165. package/out/zero-server/src/push-processor.js.map +1 -1
  166. package/out/zero-server/src/queries/process-queries.d.ts +22 -52
  167. package/out/zero-server/src/queries/process-queries.d.ts.map +1 -1
  168. package/out/zero-server/src/queries/process-queries.js +50 -49
  169. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  170. package/out/zero-server/src/zql-database.js.map +1 -1
  171. package/out/zero-types/src/default-types.d.ts +1 -0
  172. package/out/zero-types/src/default-types.d.ts.map +1 -1
  173. package/out/zql/src/builder/builder.d.ts.map +1 -1
  174. package/out/zql/src/builder/builder.js +17 -7
  175. package/out/zql/src/builder/builder.js.map +1 -1
  176. package/out/zql/src/ivm/cap.d.ts +32 -0
  177. package/out/zql/src/ivm/cap.d.ts.map +1 -0
  178. package/out/zql/src/ivm/cap.js +205 -0
  179. package/out/zql/src/ivm/cap.js.map +1 -0
  180. package/out/zql/src/ivm/constraint.js +1 -1
  181. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  182. package/out/zql/src/ivm/flipped-join.js +61 -15
  183. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  184. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  185. package/out/zql/src/ivm/memory-source.js +3 -4
  186. package/out/zql/src/ivm/memory-source.js.map +1 -1
  187. package/out/zql/src/ivm/schema.d.ts +8 -0
  188. package/out/zql/src/ivm/schema.d.ts.map +1 -1
  189. package/out/zql/src/ivm/take.js +2 -2
  190. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  191. package/out/zql/src/mutate/mutator.d.ts +11 -2
  192. package/out/zql/src/mutate/mutator.d.ts.map +1 -1
  193. package/out/zql/src/mutate/mutator.js.map +1 -1
  194. package/out/zql/src/query/query-registry.d.ts +9 -2
  195. package/out/zql/src/query/query-registry.d.ts.map +1 -1
  196. package/out/zql/src/query/query-registry.js.map +1 -1
  197. package/out/zqlite/src/table-source.d.ts.map +1 -1
  198. package/out/zqlite/src/table-source.js +4 -1
  199. package/out/zqlite/src/table-source.js.map +1 -1
  200. package/package.json +1 -1
@@ -8,7 +8,7 @@ import { parseOptions } from "../../shared/src/options.js";
8
8
  import { ZERO_ENV_VAR_PREFIX } from "../../zero-cache/src/config/zero-config.js";
9
9
  import { Console } from "node:console";
10
10
  import { styleText } from "node:util";
11
- import { WebSocket as WebSocket$1 } from "ws";
11
+ import { WebSocket } from "ws";
12
12
  //#region ../analyze-query/src/analyze-cli.ts
13
13
  var options = {
14
14
  zeroCacheURL: {
@@ -217,7 +217,7 @@ function resolveHandshakeHeaders(config) {
217
217
  return headers;
218
218
  }
219
219
  function installWebSocketHeaderShim(headers) {
220
- class HeaderInjectingWebSocket extends WebSocket$1 {
220
+ class HeaderInjectingWebSocket extends WebSocket {
221
221
  constructor(url, protocols) {
222
222
  super(url, protocols, { headers });
223
223
  }
@@ -1 +1 @@
1
- {"version":3,"file":"analyze-cli.js","names":[],"sources":["../../../../analyze-query/src/analyze-cli.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport {Console} from 'node:console';\nimport {styleText} from 'node:util';\nimport type {LogSink} from '@rocicorp/logger';\nimport {WebSocket as NodeWebSocket} from 'ws';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole} from '../../shared/src/logging.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {ZERO_ENV_VAR_PREFIX} from '../../zero-cache/src/config/zero-config.ts';\nimport {Zero} from '../../zero-client/src/client/zero.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {createBuilder} from '../../zql/src/query/create-builder.ts';\nimport type {AnyQuery} from '../../zql/src/query/query.ts';\nimport type {SchemaQuery} from '../../zql/src/query/schema-query.ts';\n\nexport type AnalyzeCLIOptions = {\n schema: Schema;\n /** Defaults to `process.argv.slice(2)`. */\n argv?: readonly string[] | undefined;\n};\n\nconst options = {\n zeroCacheURL: {\n type: v.string().optional(),\n desc: [\n 'URL of the remote zero-cache to analyze against.',\n 'Accepts http(s):// or ws(s):// (ws(s) is the transport actually used).',\n ],\n },\n adminPassword: {\n type: v.string().optional(),\n desc: [\n 'Admin password for zero-cache.',\n 'Required when the server is configured with one; ignored in dev mode.',\n ],\n },\n authToken: {\n type: v.string().optional(),\n desc: [\n 'Raw JWT forwarded to zero-cache.',\n 'Used server-side to fill permission variables for the query.',\n ],\n },\n cookie: {\n type: v.string().optional(),\n desc: [\n 'Cookie header value sent on the WebSocket upgrade request,',\n 'e.g. `session=abc; foo=bar`. Use this when zero-cache is behind',\n 'a proxy that resolves auth via cookies. Merged with --headers-json',\n '(--cookie wins on conflict).',\n ],\n },\n headersJson: {\n type: v.string().optional(),\n desc: [\n 'JSON object of arbitrary headers to send on the WebSocket upgrade',\n 'request, e.g. `{\"x-api-key\":\"...\"}`. Escape hatch for exotic auth',\n 'schemes; prefer --auth-token or --cookie when possible.',\n ],\n },\n userId: {\n type: v.string().optional(),\n desc: [\n 'Optional userID to report to zero-cache.',\n 'Has no functional effect on analysis; defaults to \"analyze-cli\".',\n ],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded AST. Exactly one of --ast / --query / --query-name is required.',\n 'The AST is sent to the server verbatim — provide it in server (post-mapping) form.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'ZQL query in chain form, e.g. `issue.related(\"comments\").limit(10)`.',\n 'Evaluated against the schema you pass to runAnalyzeCLI.',\n ],\n },\n queryName: {\n type: v.string().optional(),\n desc: [\n 'Name of a server-registered custom (named) query.',\n 'The server resolves the name + args via its registered query handler.',\n ],\n },\n queryArgs: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded array of arguments for --query-name. Defaults to `[]`.',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Include the rows read from the replica to execute the query.',\n 'Each row appears once per read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: ['Include the rows that would be synced to the client.'],\n },\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\ntype QueryPlan =\n | {kind: 'ast'; ast: AST}\n | {kind: 'zql'; text: string}\n | {kind: 'named'; name: string; args: ReadonlyArray<unknown>};\n\n// Route all Zero client log output to stderr so stdout contains only the\n// analyze result. Shell redirection (`2>/dev/null`) can then cleanly silence\n// logs without affecting output.\nconst stderrConsole = new Console({\n stdout: process.stderr,\n stderr: process.stderr,\n});\nconst stderrLogSink: LogSink = {\n log(level, context, ...args) {\n const ctx = context\n ? Object.entries(context).map(([k, v]) =>\n v === undefined ? k : `${k}=${v}`,\n )\n : [];\n stderrConsole[level](...ctx, ...args);\n },\n};\n\n/**\n * Entry point for a user's `cli.ts`. Parses argv, connects to a remote\n * zero-cache by standing up an in-process Zero client (in-memory storage,\n * no subscriptions), calls the inspector's `analyze-query` RPC, and\n * renders the result. Intended to be called as:\n *\n * ```ts\n * import {schema} from './schema.ts';\n * import {runAnalyzeCLI} from '@rocicorp/zero/analyze';\n * await runAnalyzeCLI({schema});\n * ```\n *\n * Exits the process with code 1 on error.\n */\nexport async function runAnalyzeCLI(opts: AnalyzeCLIOptions): Promise<void> {\n const argv = (opts.argv ?? process.argv.slice(2)).map(s =>\n s.replaceAll('\\n', ' '),\n );\n\n const config = parseOptions(options, {\n argv,\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query (remote)',\n content: `Analyze a ZQL query against a remote zero-cache.\n\n Connects to zero-cache's inspector protocol and reports the server-observed\n row scans, SQLite query plans, and timings.`,\n },\n {\n header: 'Examples',\n content: ` tsx cli.ts --zero-cache-url=https://zero.example.com \\\\\n --admin-password=\"$ZERO_ADMIN_PASSWORD\" \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --ast='\\\\{\"table\": \"issue\", \"limit\": 5\\\\}'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --query-name=issueList --query-args='[]'`,\n },\n ],\n });\n\n if (!config.zeroCacheURL) {\n colorConsole.error('--zero-cache-url is required. See --help for usage.');\n process.exit(1);\n }\n\n const plan = buildQueryPlan(config);\n\n const handshakeHeaders = resolveHandshakeHeaders(config);\n if (Object.keys(handshakeHeaders).length > 0) {\n installWebSocketHeaderShim(handshakeHeaders);\n }\n\n // zero-client and replicache reference a build-time `TESTING` global that\n // bundlers replace with a boolean literal; under tsx there's no replacement,\n // so provide a runtime default.\n (globalThis as {TESTING?: boolean}).TESTING ??= false;\n\n const z = new Zero({\n schema: opts.schema,\n server: config.zeroCacheURL,\n auth: config.authToken,\n userID: config.userId ?? 'analyze-cli',\n kvStore: 'mem',\n logLevel: config.log.level,\n logSink: stderrLogSink,\n });\n\n let result: AnalyzeQueryResult;\n try {\n const authOk = await z.inspector.authenticate(config.adminPassword ?? '');\n if (!authOk) {\n throw new Error(\n 'admin password rejected (or --admin-password is required)',\n );\n }\n\n const rpcOptions = {\n vendedRows: config.outputVendedRows,\n syncedRows: config.outputSyncedRows,\n };\n\n if (plan.kind === 'ast') {\n result = await z.inspector.analyzeServerAST(plan.ast, rpcOptions);\n } else if (plan.kind === 'named') {\n result = await z.inspector.analyzeNamedQuery(\n plan.name,\n plan.args as ReadonlyArray<never>,\n rpcOptions,\n );\n } else {\n const built = buildZqlQuery(plan.text, createBuilder(opts.schema));\n result = await z.inspector.analyzeQuery(built, rpcOptions);\n }\n } catch (e) {\n colorConsole.error(e instanceof Error ? e.message : String(e));\n await z.close().catch(() => {});\n process.exit(1);\n }\n\n renderResult(result, {\n outputSyncedRows: config.outputSyncedRows,\n outputVendedRows: config.outputVendedRows,\n });\n\n await z.close();\n}\n\nfunction buildQueryPlan(config: {\n ast?: string | undefined;\n query?: string | undefined;\n queryName?: string | undefined;\n queryArgs?: string | undefined;\n}): QueryPlan {\n const selectors = [\n config.ast !== undefined && 'ast',\n config.query !== undefined && 'query',\n config.queryName !== undefined && 'queryName',\n ].filter(Boolean) as string[];\n\n if (selectors.length === 0) {\n colorConsole.error(\n 'Exactly one of --ast / --query / --query-name is required.',\n );\n process.exit(1);\n }\n if (selectors.length > 1) {\n colorConsole.error(\n `Only one of --ast / --query / --query-name may be provided; got: ${selectors.join(', ')}`,\n );\n process.exit(1);\n }\n\n if (config.ast !== undefined) {\n return {kind: 'ast', ast: JSON.parse(config.ast) as AST};\n }\n if (config.query !== undefined) {\n return {kind: 'zql', text: config.query};\n }\n const args = config.queryArgs\n ? (JSON.parse(config.queryArgs) as ReadonlyArray<unknown>)\n : [];\n return {kind: 'named', name: config.queryName as string, args};\n}\n\nfunction buildZqlQuery(\n queryString: string,\n builder: SchemaQuery<Schema>,\n): AnyQuery {\n const f = new Function('builder', `return builder.${queryString};`);\n return f(builder) as AnyQuery;\n}\n\nfunction resolveHandshakeHeaders(config: {\n cookie?: string | undefined;\n headersJson?: string | undefined;\n}): Record<string, string> {\n const headers: Record<string, string> = {};\n if (config.headersJson !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(config.headersJson);\n } catch (e) {\n colorConsole.error(\n `--headers-json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,\n );\n process.exit(1);\n }\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n colorConsole.error('--headers-json must be a JSON object.');\n process.exit(1);\n }\n for (const [k, val] of Object.entries(parsed)) {\n if (typeof val !== 'string') {\n colorConsole.error(\n `--headers-json values must be strings; got ${typeof val} for \"${k}\".`,\n );\n process.exit(1);\n }\n headers[k] = val;\n }\n }\n if (config.cookie !== undefined) {\n headers.cookie = config.cookie;\n }\n return headers;\n}\n\nfunction installWebSocketHeaderShim(headers: Record<string, string>): void {\n class HeaderInjectingWebSocket extends NodeWebSocket {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols, {headers});\n }\n }\n (globalThis as {WebSocket?: unknown}).WebSocket = HeaderInjectingWebSocket;\n}\n\nfunction renderResult(\n result: AnalyzeQueryResult,\n opts: {outputSyncedRows: boolean; outputVendedRows: boolean},\n) {\n if (opts.outputSyncedRows) {\n colorConsole.log(styleText(['blue', 'bold'], '=== Synced Rows: ===\\n'));\n for (const [table, rows] of Object.entries(result.syncedRows ?? {})) {\n colorConsole.log(styleText('bold', table + ':'), rows);\n }\n }\n\n colorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\n colorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n );\n\n const readRowCountsByQuery = result.readRowCountsByQuery ?? {};\n let totalRowsRead = 0;\n for (const table of Object.keys(readRowCountsByQuery).sort()) {\n const counts = readRowCountsByQuery[table];\n for (const n of Object.values(counts)) {\n totalRowsRead += n;\n }\n colorConsole.log(styleText('bold', `${table} vended:`), counts);\n }\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsRead),\n );\n const duration = result.elapsed ?? result.end - result.start;\n colorConsole.log(styleText('bold', 'time:'), colorTime(duration), 'ms');\n\n if (opts.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const [table, rows] of Object.entries(result.readRows ?? {})) {\n colorConsole.log(styleText('bold', `${table}:`), rows);\n }\n }\n\n colorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n );\n const dbScansByQuery = result.dbScansByQuery ?? {};\n let totalNVisit = 0;\n for (const [table, queries] of Object.entries(dbScansByQuery)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n }\n colorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n );\n\n colorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\n const plans = result.sqlitePlans ?? {};\n for (const [query, plan] of Object.entries(plans)) {\n colorConsole.log(styleText('bold', 'query'), query);\n colorConsole.log(plan.map((row, i) => colorPlanRow(row, i)).join('\\n'));\n colorConsole.log('\\n');\n }\n\n if (result.warnings.length > 0) {\n colorConsole.log(styleText(['yellow', 'bold'], '=== Warnings: ===\\n'));\n for (const w of result.warnings) {\n colorConsole.log(styleText('yellow', w));\n }\n }\n}\n\nfunction colorTime(duration: number) {\n if (duration < 100) {\n return styleText('green', duration.toFixed(2) + 'ms');\n } else if (duration < 1000) {\n return styleText('yellow', duration.toFixed(2) + 'ms');\n }\n return styleText('red', duration.toFixed(2) + 'ms');\n}\n\nfunction colorRowsConsidered(n: number) {\n if (n < 1000) {\n return styleText('green', n.toString());\n } else if (n < 10000) {\n return styleText('yellow', n.toString());\n }\n return styleText('red', n.toString());\n}\n\nfunction colorPlanRow(row: string, i: number) {\n if (row.includes('SCAN')) {\n if (i === 0) {\n return styleText('yellow', row);\n }\n return styleText('red', row);\n }\n return styleText('green', row);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"analyze-cli.js","names":[],"sources":["../../../../analyze-query/src/analyze-cli.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport {Console} from 'node:console';\nimport {styleText} from 'node:util';\nimport type {LogSink} from '@rocicorp/logger';\nimport {WebSocket as NodeWebSocket} from 'ws';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole} from '../../shared/src/logging.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {ZERO_ENV_VAR_PREFIX} from '../../zero-cache/src/config/zero-config.ts';\nimport {Zero} from '../../zero-client/src/client/zero.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {createBuilder} from '../../zql/src/query/create-builder.ts';\nimport type {AnyQuery} from '../../zql/src/query/query.ts';\nimport type {SchemaQuery} from '../../zql/src/query/schema-query.ts';\n\nexport type AnalyzeCLIOptions = {\n schema: Schema;\n /** Defaults to `process.argv.slice(2)`. */\n argv?: readonly string[] | undefined;\n};\n\nconst options = {\n zeroCacheURL: {\n type: v.string().optional(),\n desc: [\n 'URL of the remote zero-cache to analyze against.',\n 'Accepts http(s):// or ws(s):// (ws(s) is the transport actually used).',\n ],\n },\n adminPassword: {\n type: v.string().optional(),\n desc: [\n 'Admin password for zero-cache.',\n 'Required when the server is configured with one; ignored in dev mode.',\n ],\n },\n authToken: {\n type: v.string().optional(),\n desc: [\n 'Raw JWT forwarded to zero-cache.',\n 'Used server-side to fill permission variables for the query.',\n ],\n },\n cookie: {\n type: v.string().optional(),\n desc: [\n 'Cookie header value sent on the WebSocket upgrade request,',\n 'e.g. `session=abc; foo=bar`. Use this when zero-cache is behind',\n 'a proxy that resolves auth via cookies. Merged with --headers-json',\n '(--cookie wins on conflict).',\n ],\n },\n headersJson: {\n type: v.string().optional(),\n desc: [\n 'JSON object of arbitrary headers to send on the WebSocket upgrade',\n 'request, e.g. `{\"x-api-key\":\"...\"}`. Escape hatch for exotic auth',\n 'schemes; prefer --auth-token or --cookie when possible.',\n ],\n },\n userId: {\n type: v.string().optional(),\n desc: [\n 'Optional userID to report to zero-cache.',\n 'Has no functional effect on analysis; defaults to \"analyze-cli\".',\n ],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded AST. Exactly one of --ast / --query / --query-name is required.',\n 'The AST is sent to the server verbatim — provide it in server (post-mapping) form.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'ZQL query in chain form, e.g. `issue.related(\"comments\").limit(10)`.',\n 'Evaluated against the schema you pass to runAnalyzeCLI.',\n ],\n },\n queryName: {\n type: v.string().optional(),\n desc: [\n 'Name of a server-registered custom (named) query.',\n 'The server resolves the name + args via its registered query handler.',\n ],\n },\n queryArgs: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded array of arguments for --query-name. Defaults to `[]`.',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Include the rows read from the replica to execute the query.',\n 'Each row appears once per read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: ['Include the rows that would be synced to the client.'],\n },\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\ntype QueryPlan =\n | {kind: 'ast'; ast: AST}\n | {kind: 'zql'; text: string}\n | {kind: 'named'; name: string; args: ReadonlyArray<unknown>};\n\n// Route all Zero client log output to stderr so stdout contains only the\n// analyze result. Shell redirection (`2>/dev/null`) can then cleanly silence\n// logs without affecting output.\nconst stderrConsole = new Console({\n stdout: process.stderr,\n stderr: process.stderr,\n});\nconst stderrLogSink: LogSink = {\n log(level, context, ...args) {\n const ctx = context\n ? Object.entries(context).map(([k, v]) =>\n v === undefined ? k : `${k}=${v}`,\n )\n : [];\n stderrConsole[level](...ctx, ...args);\n },\n};\n\n/**\n * Entry point for a user's `cli.ts`. Parses argv, connects to a remote\n * zero-cache by standing up an in-process Zero client (in-memory storage,\n * no subscriptions), calls the inspector's `analyze-query` RPC, and\n * renders the result. Intended to be called as:\n *\n * ```ts\n * import {schema} from './schema.ts';\n * import {runAnalyzeCLI} from '@rocicorp/zero/analyze';\n * await runAnalyzeCLI({schema});\n * ```\n *\n * Exits the process with code 1 on error.\n */\nexport async function runAnalyzeCLI(opts: AnalyzeCLIOptions): Promise<void> {\n const argv = (opts.argv ?? process.argv.slice(2)).map(s =>\n s.replaceAll('\\n', ' '),\n );\n\n const config = parseOptions(options, {\n argv,\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query (remote)',\n content: `Analyze a ZQL query against a remote zero-cache.\n\n Connects to zero-cache's inspector protocol and reports the server-observed\n row scans, SQLite query plans, and timings.`,\n },\n {\n header: 'Examples',\n content: ` tsx cli.ts --zero-cache-url=https://zero.example.com \\\\\n --admin-password=\"$ZERO_ADMIN_PASSWORD\" \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --ast='\\\\{\"table\": \"issue\", \"limit\": 5\\\\}'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --query-name=issueList --query-args='[]'`,\n },\n ],\n });\n\n if (!config.zeroCacheURL) {\n colorConsole.error('--zero-cache-url is required. See --help for usage.');\n process.exit(1);\n }\n\n const plan = buildQueryPlan(config);\n\n const handshakeHeaders = resolveHandshakeHeaders(config);\n if (Object.keys(handshakeHeaders).length > 0) {\n installWebSocketHeaderShim(handshakeHeaders);\n }\n\n // zero-client and replicache reference a build-time `TESTING` global that\n // bundlers replace with a boolean literal; under tsx there's no replacement,\n // so provide a runtime default.\n (globalThis as {TESTING?: boolean}).TESTING ??= false;\n\n const z = new Zero({\n schema: opts.schema,\n server: config.zeroCacheURL,\n auth: config.authToken,\n userID: config.userId ?? 'analyze-cli',\n kvStore: 'mem',\n logLevel: config.log.level,\n logSink: stderrLogSink,\n });\n\n let result: AnalyzeQueryResult;\n try {\n const authOk = await z.inspector.authenticate(config.adminPassword ?? '');\n if (!authOk) {\n throw new Error(\n 'admin password rejected (or --admin-password is required)',\n );\n }\n\n const rpcOptions = {\n vendedRows: config.outputVendedRows,\n syncedRows: config.outputSyncedRows,\n };\n\n if (plan.kind === 'ast') {\n result = await z.inspector.analyzeServerAST(plan.ast, rpcOptions);\n } else if (plan.kind === 'named') {\n result = await z.inspector.analyzeNamedQuery(\n plan.name,\n plan.args as ReadonlyArray<never>,\n rpcOptions,\n );\n } else {\n const built = buildZqlQuery(plan.text, createBuilder(opts.schema));\n result = await z.inspector.analyzeQuery(built, rpcOptions);\n }\n } catch (e) {\n colorConsole.error(e instanceof Error ? e.message : String(e));\n await z.close().catch(() => {});\n process.exit(1);\n }\n\n renderResult(result, {\n outputSyncedRows: config.outputSyncedRows,\n outputVendedRows: config.outputVendedRows,\n });\n\n await z.close();\n}\n\nfunction buildQueryPlan(config: {\n ast?: string | undefined;\n query?: string | undefined;\n queryName?: string | undefined;\n queryArgs?: string | undefined;\n}): QueryPlan {\n const selectors = [\n config.ast !== undefined && 'ast',\n config.query !== undefined && 'query',\n config.queryName !== undefined && 'queryName',\n ].filter(Boolean) as string[];\n\n if (selectors.length === 0) {\n colorConsole.error(\n 'Exactly one of --ast / --query / --query-name is required.',\n );\n process.exit(1);\n }\n if (selectors.length > 1) {\n colorConsole.error(\n `Only one of --ast / --query / --query-name may be provided; got: ${selectors.join(', ')}`,\n );\n process.exit(1);\n }\n\n if (config.ast !== undefined) {\n return {kind: 'ast', ast: JSON.parse(config.ast) as AST};\n }\n if (config.query !== undefined) {\n return {kind: 'zql', text: config.query};\n }\n const args = config.queryArgs\n ? (JSON.parse(config.queryArgs) as ReadonlyArray<unknown>)\n : [];\n return {kind: 'named', name: config.queryName as string, args};\n}\n\nfunction buildZqlQuery(\n queryString: string,\n builder: SchemaQuery<Schema>,\n): AnyQuery {\n const f = new Function('builder', `return builder.${queryString};`);\n return f(builder) as AnyQuery;\n}\n\nfunction resolveHandshakeHeaders(config: {\n cookie?: string | undefined;\n headersJson?: string | undefined;\n}): Record<string, string> {\n const headers: Record<string, string> = {};\n if (config.headersJson !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(config.headersJson);\n } catch (e) {\n colorConsole.error(\n `--headers-json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,\n );\n process.exit(1);\n }\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n colorConsole.error('--headers-json must be a JSON object.');\n process.exit(1);\n }\n for (const [k, val] of Object.entries(parsed)) {\n if (typeof val !== 'string') {\n colorConsole.error(\n `--headers-json values must be strings; got ${typeof val} for \"${k}\".`,\n );\n process.exit(1);\n }\n headers[k] = val;\n }\n }\n if (config.cookie !== undefined) {\n headers.cookie = config.cookie;\n }\n return headers;\n}\n\nfunction installWebSocketHeaderShim(headers: Record<string, string>): void {\n class HeaderInjectingWebSocket extends NodeWebSocket {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols, {headers});\n }\n }\n (globalThis as {WebSocket?: unknown}).WebSocket = HeaderInjectingWebSocket;\n}\n\nfunction renderResult(\n result: AnalyzeQueryResult,\n opts: {outputSyncedRows: boolean; outputVendedRows: boolean},\n) {\n if (opts.outputSyncedRows) {\n colorConsole.log(styleText(['blue', 'bold'], '=== Synced Rows: ===\\n'));\n for (const [table, rows] of Object.entries(result.syncedRows ?? {})) {\n colorConsole.log(styleText('bold', table + ':'), rows);\n }\n }\n\n colorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\n colorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n );\n\n const readRowCountsByQuery = result.readRowCountsByQuery ?? {};\n let totalRowsRead = 0;\n for (const table of Object.keys(readRowCountsByQuery).sort()) {\n const counts = readRowCountsByQuery[table];\n for (const n of Object.values(counts)) {\n totalRowsRead += n;\n }\n colorConsole.log(styleText('bold', `${table} vended:`), counts);\n }\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsRead),\n );\n const duration = result.elapsed ?? result.end - result.start;\n colorConsole.log(styleText('bold', 'time:'), colorTime(duration), 'ms');\n\n if (opts.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const [table, rows] of Object.entries(result.readRows ?? {})) {\n colorConsole.log(styleText('bold', `${table}:`), rows);\n }\n }\n\n colorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n );\n const dbScansByQuery = result.dbScansByQuery ?? {};\n let totalNVisit = 0;\n for (const [table, queries] of Object.entries(dbScansByQuery)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n }\n colorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n );\n\n colorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\n const plans = result.sqlitePlans ?? {};\n for (const [query, plan] of Object.entries(plans)) {\n colorConsole.log(styleText('bold', 'query'), query);\n colorConsole.log(plan.map((row, i) => colorPlanRow(row, i)).join('\\n'));\n colorConsole.log('\\n');\n }\n\n if (result.warnings.length > 0) {\n colorConsole.log(styleText(['yellow', 'bold'], '=== Warnings: ===\\n'));\n for (const w of result.warnings) {\n colorConsole.log(styleText('yellow', w));\n }\n }\n}\n\nfunction colorTime(duration: number) {\n if (duration < 100) {\n return styleText('green', duration.toFixed(2) + 'ms');\n } else if (duration < 1000) {\n return styleText('yellow', duration.toFixed(2) + 'ms');\n }\n return styleText('red', duration.toFixed(2) + 'ms');\n}\n\nfunction colorRowsConsidered(n: number) {\n if (n < 1000) {\n return styleText('green', n.toString());\n } else if (n < 10000) {\n return styleText('yellow', n.toString());\n }\n return styleText('red', n.toString());\n}\n\nfunction colorPlanRow(row: string, i: number) {\n if (row.includes('SCAN')) {\n if (i === 0) {\n return styleText('yellow', row);\n }\n return styleText('red', row);\n }\n return styleText('green', row);\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  var package_default = {
2
2
  name: "@rocicorp/zero",
3
- version: "1.4.0-canary.5",
3
+ version: "1.5.0-canary.0",
4
4
  description: "Zero is a web framework for serverless web development.",
5
5
  homepage: "https://zero.rocicorp.dev",
6
6
  bugs: { "url": "https://bugs.rocicorp.dev" },
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.4.0-canary.5\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\n },\n \"license\": \"Apache-2.0\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"bin\": {\n \"analyze-query\": \"./out/zero/src/analyze-query.js\",\n \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\",\n \"zero-build-schema\": \"./out/zero/src/build-schema.js\",\n \"zero-cache\": \"./out/zero/src/cli.js\",\n \"zero-cache-dev\": \"./out/zero/src/zero-cache-dev.js\",\n \"zero-deploy-permissions\": \"./out/zero/src/deploy-permissions.js\",\n \"zero-out\": \"./out/zero/src/zero-out.js\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ],\n \"type\": \"module\",\n \"main\": \"out/zero/src/zero.js\",\n \"module\": \"out/zero/src/zero.js\",\n \"types\": \"out/zero/src/zero.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./out/zero/src/zero.d.ts\",\n \"default\": \"./out/zero/src/zero.js\"\n },\n \"./analyze\": {\n \"types\": \"./out/zero/src/analyze.d.ts\",\n \"default\": \"./out/zero/src/analyze.js\"\n },\n \"./bindings\": {\n \"types\": \"./out/zero/src/bindings.d.ts\",\n \"default\": \"./out/zero/src/bindings.js\"\n },\n \"./change-protocol/v0\": {\n \"types\": \"./out/zero/src/change-protocol/v0.d.ts\",\n \"default\": \"./out/zero/src/change-protocol/v0.js\"\n },\n \"./expo-sqlite\": {\n \"types\": \"./out/zero/src/expo-sqlite.d.ts\",\n \"default\": \"./out/zero/src/expo-sqlite.js\"\n },\n \"./op-sqlite\": {\n \"types\": \"./out/zero/src/op-sqlite.d.ts\",\n \"default\": \"./out/zero/src/op-sqlite.js\"\n },\n \"./pg\": {\n \"types\": \"./out/zero/src/pg.d.ts\",\n \"default\": \"./out/zero/src/pg.js\"\n },\n \"./react\": {\n \"types\": \"./out/zero/src/react.d.ts\",\n \"default\": \"./out/zero/src/react.js\"\n },\n \"./react-native\": {\n \"types\": \"./out/zero/src/react-native.d.ts\",\n \"default\": \"./out/zero/src/react-native.js\"\n },\n \"./server\": {\n \"types\": \"./out/zero/src/server.d.ts\",\n \"default\": \"./out/zero/src/server.js\"\n },\n \"./server/adapters/drizzle\": {\n \"types\": \"./out/zero/src/adapters/drizzle.d.ts\",\n \"default\": \"./out/zero/src/adapters/drizzle.js\"\n },\n \"./server/adapters/kysely\": {\n \"types\": \"./out/zero/src/adapters/kysely.d.ts\",\n \"default\": \"./out/zero/src/adapters/kysely.js\"\n },\n \"./server/adapters/prisma\": {\n \"types\": \"./out/zero/src/adapters/prisma.d.ts\",\n \"default\": \"./out/zero/src/adapters/prisma.js\"\n },\n \"./server/adapters/pg\": {\n \"types\": \"./out/zero/src/adapters/pg.d.ts\",\n \"default\": \"./out/zero/src/adapters/pg.js\"\n },\n \"./server/adapters/postgresjs\": {\n \"types\": \"./out/zero/src/adapters/postgresjs.d.ts\",\n \"default\": \"./out/zero/src/adapters/postgresjs.js\"\n },\n \"./solid\": {\n \"types\": \"./out/zero/src/solid.d.ts\",\n \"default\": \"./out/zero/src/solid.js\"\n },\n \"./sqlite\": {\n \"types\": \"./out/zero/src/sqlite.d.ts\",\n \"default\": \"./out/zero/src/sqlite.js\"\n },\n \"./zqlite\": {\n \"types\": \"./out/zero/src/zqlite.d.ts\",\n \"default\": \"./out/zero/src/zqlite.js\"\n }\n },\n \"scripts\": {\n \"build\": \"node --experimental-strip-types --no-warnings tool/build.ts\",\n \"build:watch\": \"node --experimental-strip-types --no-warnings tool/build.ts --watch\",\n \"check-types\": \"tsc -p tsconfig.client.json && tsc -p tsconfig.server.json\",\n \"check-types:client:watch\": \"tsc -p tsconfig.client.json --watch\",\n \"check-types:server:watch\": \"tsc -p tsconfig.server.json --watch\",\n \"format\": \"oxfmt .\",\n \"check-format\": \"oxfmt --check .\",\n \"lint\": \"oxlint --type-aware src/\",\n \"docs\": \"node --experimental-strip-types --no-warnings tool/generate-docs.ts\",\n \"docs:server\": \"node --watch --experimental-strip-types --no-warnings tool/generate-docs.ts --server\",\n \"fmt\": \"oxfmt .\",\n \"check-fmt\": \"oxfmt --check .\"\n },\n \"dependencies\": {\n \"@badrap/valita\": \"0.3.11\",\n \"@databases/escape-identifier\": \"^1.0.3\",\n \"@databases/sql\": \"^3.3.0\",\n \"@dotenvx/dotenvx\": \"^1.39.0\",\n \"@drdgvhbh/postgres-error-codes\": \"^0.0.6\",\n \"@fastify/cors\": \"^10.0.0\",\n \"@fastify/websocket\": \"^11.0.0\",\n \"@google-cloud/precise-date\": \"^4.0.0\",\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/api-logs\": \"^0.203.0\",\n \"@opentelemetry/auto-instrumentations-node\": \"^0.62.0\",\n \"@opentelemetry/exporter-metrics-otlp-http\": \"^0.203.0\",\n \"@opentelemetry/resources\": \"^2.0.1\",\n \"@opentelemetry/sdk-metrics\": \"^2.0.1\",\n \"@opentelemetry/sdk-node\": \"^0.203.0\",\n \"@opentelemetry/sdk-trace-node\": \"^2.0.1\",\n \"@postgresql-typed/oids\": \"^0.2.0\",\n \"@rocicorp/lock\": \"^1.0.4\",\n \"@rocicorp/logger\": \"^5.4.0\",\n \"@rocicorp/resolver\": \"^1.0.2\",\n \"@rocicorp/zero-sqlite3\": \"^1.0.17\",\n \"@standard-schema/spec\": \"^1.0.0\",\n \"@types/basic-auth\": \"^1.1.8\",\n \"@types/ws\": \"^8.5.12\",\n \"basic-auth\": \"^2.0.1\",\n \"chalk-template\": \"^1.1.0\",\n \"chokidar\": \"^4.0.1\",\n \"cloudevents\": \"^10.0.0\",\n \"command-line-args\": \"^6.0.1\",\n \"command-line-usage\": \"^7.0.3\",\n \"compare-utf8\": \"^0.2.0\",\n \"defu\": \"^6.1.4\",\n \"eventemitter3\": \"^5.0.1\",\n \"fastify\": \"^5.0.0\",\n \"is-in-subnet\": \"^4.0.1\",\n \"jose\": \"^5.9.3\",\n \"js-xxhash\": \"^4.0.0\",\n \"json-custom-numbers\": \"^3.1.1\",\n \"kasi\": \"^1.1.0\",\n \"nanoid\": \"^5.1.2\",\n \"oxfmt\": \"^0.45.0\",\n \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"semver\": \"^7.5.4\",\n \"tsx\": \"^4.21.0\",\n \"url-pattern\": \"^1.0.3\",\n \"urlpattern-polyfill\": \"^10.1.0\",\n \"ws\": \"^8.18.1\"\n },\n \"devDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"@vitest/runner\": \"4.1.3\",\n \"analyze-query\": \"0.0.0\",\n \"ast-to-zql\": \"0.0.0\",\n \"expo-sqlite\": \">=15\",\n \"replicache\": \"15.2.1\",\n \"shared\": \"0.0.0\",\n \"syncpack\": \"^14.3.0\",\n \"typedoc\": \"^0.28.17\",\n \"typedoc-plugin-markdown\": \"^4.10.0\",\n \"typescript\": \"~6.0.2\",\n \"vite\": \"8.0.3\",\n \"vitest\": \"4.1.3\",\n \"zero-cache\": \"0.0.0\",\n \"zero-client\": \"0.0.0\",\n \"zero-pg\": \"0.0.0\",\n \"zero-protocol\": \"0.0.0\",\n \"zero-react\": \"0.0.0\",\n \"zero-server\": \"0.0.0\",\n \"zero-solid\": \"0.0.0\",\n \"zqlite\": \"0.0.0\"\n },\n \"peerDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"expo-sqlite\": \">=15\",\n \"kysely\": \"^0.28.16\"\n },\n \"peerDependenciesMeta\": {\n \"kysely\": {\n \"optional\": true\n },\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n }\n },\n \"engines\": {\n \"node\": \">=22\"\n }\n}"],"mappings":""}
1
+ {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.5.0-canary.0\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\n },\n \"license\": \"Apache-2.0\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"bin\": {\n \"analyze-query\": \"./out/zero/src/analyze-query.js\",\n \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\",\n \"zero-build-schema\": \"./out/zero/src/build-schema.js\",\n \"zero-cache\": \"./out/zero/src/cli.js\",\n \"zero-cache-dev\": \"./out/zero/src/zero-cache-dev.js\",\n \"zero-deploy-permissions\": \"./out/zero/src/deploy-permissions.js\",\n \"zero-out\": \"./out/zero/src/zero-out.js\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ],\n \"type\": \"module\",\n \"main\": \"out/zero/src/zero.js\",\n \"module\": \"out/zero/src/zero.js\",\n \"types\": \"out/zero/src/zero.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./out/zero/src/zero.d.ts\",\n \"default\": \"./out/zero/src/zero.js\"\n },\n \"./analyze\": {\n \"types\": \"./out/zero/src/analyze.d.ts\",\n \"default\": \"./out/zero/src/analyze.js\"\n },\n \"./bindings\": {\n \"types\": \"./out/zero/src/bindings.d.ts\",\n \"default\": \"./out/zero/src/bindings.js\"\n },\n \"./change-protocol/v0\": {\n \"types\": \"./out/zero/src/change-protocol/v0.d.ts\",\n \"default\": \"./out/zero/src/change-protocol/v0.js\"\n },\n \"./expo-sqlite\": {\n \"types\": \"./out/zero/src/expo-sqlite.d.ts\",\n \"default\": \"./out/zero/src/expo-sqlite.js\"\n },\n \"./op-sqlite\": {\n \"types\": \"./out/zero/src/op-sqlite.d.ts\",\n \"default\": \"./out/zero/src/op-sqlite.js\"\n },\n \"./pg\": {\n \"types\": \"./out/zero/src/pg.d.ts\",\n \"default\": \"./out/zero/src/pg.js\"\n },\n \"./react\": {\n \"types\": \"./out/zero/src/react.d.ts\",\n \"default\": \"./out/zero/src/react.js\"\n },\n \"./react-native\": {\n \"types\": \"./out/zero/src/react-native.d.ts\",\n \"default\": \"./out/zero/src/react-native.js\"\n },\n \"./server\": {\n \"types\": \"./out/zero/src/server.d.ts\",\n \"default\": \"./out/zero/src/server.js\"\n },\n \"./server/adapters/drizzle\": {\n \"types\": \"./out/zero/src/adapters/drizzle.d.ts\",\n \"default\": \"./out/zero/src/adapters/drizzle.js\"\n },\n \"./server/adapters/kysely\": {\n \"types\": \"./out/zero/src/adapters/kysely.d.ts\",\n \"default\": \"./out/zero/src/adapters/kysely.js\"\n },\n \"./server/adapters/prisma\": {\n \"types\": \"./out/zero/src/adapters/prisma.d.ts\",\n \"default\": \"./out/zero/src/adapters/prisma.js\"\n },\n \"./server/adapters/pg\": {\n \"types\": \"./out/zero/src/adapters/pg.d.ts\",\n \"default\": \"./out/zero/src/adapters/pg.js\"\n },\n \"./server/adapters/postgresjs\": {\n \"types\": \"./out/zero/src/adapters/postgresjs.d.ts\",\n \"default\": \"./out/zero/src/adapters/postgresjs.js\"\n },\n \"./solid\": {\n \"types\": \"./out/zero/src/solid.d.ts\",\n \"default\": \"./out/zero/src/solid.js\"\n },\n \"./sqlite\": {\n \"types\": \"./out/zero/src/sqlite.d.ts\",\n \"default\": \"./out/zero/src/sqlite.js\"\n },\n \"./zqlite\": {\n \"types\": \"./out/zero/src/zqlite.d.ts\",\n \"default\": \"./out/zero/src/zqlite.js\"\n }\n },\n \"scripts\": {\n \"build\": \"node --experimental-strip-types --no-warnings tool/build.ts\",\n \"build:watch\": \"node --experimental-strip-types --no-warnings tool/build.ts --watch\",\n \"check-types\": \"tsc -p tsconfig.client.json && tsc -p tsconfig.server.json\",\n \"check-types:client:watch\": \"tsc -p tsconfig.client.json --watch\",\n \"check-types:server:watch\": \"tsc -p tsconfig.server.json --watch\",\n \"format\": \"oxfmt .\",\n \"check-format\": \"oxfmt --check .\",\n \"lint\": \"oxlint --type-aware src/\",\n \"docs\": \"node --experimental-strip-types --no-warnings tool/generate-docs.ts\",\n \"docs:server\": \"node --watch --experimental-strip-types --no-warnings tool/generate-docs.ts --server\",\n \"fmt\": \"oxfmt .\",\n \"check-fmt\": \"oxfmt --check .\"\n },\n \"dependencies\": {\n \"@badrap/valita\": \"0.3.11\",\n \"@databases/escape-identifier\": \"^1.0.3\",\n \"@databases/sql\": \"^3.3.0\",\n \"@dotenvx/dotenvx\": \"^1.39.0\",\n \"@drdgvhbh/postgres-error-codes\": \"^0.0.6\",\n \"@fastify/cors\": \"^10.0.0\",\n \"@fastify/websocket\": \"^11.0.0\",\n \"@google-cloud/precise-date\": \"^4.0.0\",\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/api-logs\": \"^0.203.0\",\n \"@opentelemetry/auto-instrumentations-node\": \"^0.62.0\",\n \"@opentelemetry/exporter-metrics-otlp-http\": \"^0.203.0\",\n \"@opentelemetry/resources\": \"^2.0.1\",\n \"@opentelemetry/sdk-metrics\": \"^2.0.1\",\n \"@opentelemetry/sdk-node\": \"^0.203.0\",\n \"@opentelemetry/sdk-trace-node\": \"^2.0.1\",\n \"@postgresql-typed/oids\": \"^0.2.0\",\n \"@rocicorp/lock\": \"^1.0.4\",\n \"@rocicorp/logger\": \"^5.4.0\",\n \"@rocicorp/resolver\": \"^1.0.2\",\n \"@rocicorp/zero-sqlite3\": \"^1.0.17\",\n \"@standard-schema/spec\": \"^1.0.0\",\n \"@types/basic-auth\": \"^1.1.8\",\n \"@types/ws\": \"^8.5.12\",\n \"basic-auth\": \"^2.0.1\",\n \"chalk-template\": \"^1.1.0\",\n \"chokidar\": \"^4.0.1\",\n \"cloudevents\": \"^10.0.0\",\n \"command-line-args\": \"^6.0.1\",\n \"command-line-usage\": \"^7.0.3\",\n \"compare-utf8\": \"^0.2.0\",\n \"defu\": \"^6.1.4\",\n \"eventemitter3\": \"^5.0.1\",\n \"fastify\": \"^5.0.0\",\n \"is-in-subnet\": \"^4.0.1\",\n \"jose\": \"^5.9.3\",\n \"js-xxhash\": \"^4.0.0\",\n \"json-custom-numbers\": \"^3.1.1\",\n \"kasi\": \"^1.1.0\",\n \"nanoid\": \"^5.1.2\",\n \"oxfmt\": \"^0.45.0\",\n \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"semver\": \"^7.5.4\",\n \"tsx\": \"^4.21.0\",\n \"url-pattern\": \"^1.0.3\",\n \"urlpattern-polyfill\": \"^10.1.0\",\n \"ws\": \"^8.18.1\"\n },\n \"devDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"@vitest/runner\": \"4.1.3\",\n \"analyze-query\": \"0.0.0\",\n \"ast-to-zql\": \"0.0.0\",\n \"expo-sqlite\": \">=15\",\n \"replicache\": \"15.2.1\",\n \"shared\": \"0.0.0\",\n \"syncpack\": \"^14.3.0\",\n \"typedoc\": \"^0.28.17\",\n \"typedoc-plugin-markdown\": \"^4.10.0\",\n \"typescript\": \"~6.0.2\",\n \"vite\": \"8.0.3\",\n \"vitest\": \"4.1.3\",\n \"zero-cache\": \"0.0.0\",\n \"zero-client\": \"0.0.0\",\n \"zero-pg\": \"0.0.0\",\n \"zero-protocol\": \"0.0.0\",\n \"zero-react\": \"0.0.0\",\n \"zero-server\": \"0.0.0\",\n \"zero-solid\": \"0.0.0\",\n \"zqlite\": \"0.0.0\"\n },\n \"peerDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"expo-sqlite\": \">=15\",\n \"kysely\": \"^0.28.16\"\n },\n \"peerDependenciesMeta\": {\n \"kysely\": {\n \"optional\": true\n },\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n }\n },\n \"engines\": {\n \"node\": \">=22\"\n }\n}"],"mappings":""}
@@ -20,7 +20,7 @@ export declare function authEquals(a: Auth | undefined, b: Auth | undefined): bo
20
20
  /**
21
21
  * Resolves one auth snapshot transition without binding it to a client group.
22
22
  */
23
- export declare function resolveAuth(lc: LogContext, previousAuth: Auth | undefined, userID: string | undefined, wireAuth: string | undefined, validateLegacyJWT: ValidateLegacyJWT | undefined): Promise<Auth | undefined>;
23
+ export declare function resolveAuth(lc: LogContext, previousAuth: Auth | undefined, userID: string | null, wireAuth: string | undefined, validateLegacyJWT: ValidateLegacyJWT | undefined): Promise<Auth | undefined>;
24
24
  /** @deprecated used only in old JWT validation/rotation auth */
25
25
  export declare function pickToken(lc: LogContext, previousToken: Auth | undefined, newToken: Auth | undefined | null): Auth | undefined;
26
26
  export declare function isAuthErrorBody(ex: unknown): ex is ErrorBody | PushError;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/auth/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,MAAM,CAAC;AAIrC,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAElE,yCAAyC;AACzC,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG,UAAU,GAAG,OAAO,CAAC;AAExC,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAAC,KACvC,OAAO,CAAC,OAAO,CAAC,CAAC;AAMtB,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,EAAE,CAAC,EAAE,IAAI,GAAG,SAAS,WAQlE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,UAAU,EACd,YAAY,EAAE,IAAI,GAAG,SAAS,EAC9B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,GAC/C,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAoE3B;AAED,gEAAgE;AAChE,wBAAgB,SAAS,CACvB,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,IAAI,GAAG,SAAS,EAC/B,QAAQ,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI,oBAgFlC;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,SAAS,GAAG,SAAS,CAgCxE"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/auth/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,MAAM,CAAC;AAIrC,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAElE,yCAAyC;AACzC,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG,UAAU,GAAG,OAAO,CAAC;AAExC,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAAC,KACvC,OAAO,CAAC,OAAO,CAAC,CAAC;AAMtB,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,EAAE,CAAC,EAAE,IAAI,GAAG,SAAS,WAQlE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,UAAU,EACd,YAAY,EAAE,IAAI,GAAG,SAAS,EAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,GAC/C,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAoE3B;AAED,gEAAgE;AAChE,wBAAgB,SAAS,CACvB,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,IAAI,GAAG,SAAS,EAC/B,QAAQ,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI,oBAgFlC;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,SAAS,GAAG,SAAS,CAgCxE"}
@@ -28,7 +28,7 @@ async function resolveAuth(lc, previousAuth, userID, wireAuth, validateLegacyJWT
28
28
  lc.debug?.(`Cleared auth`);
29
29
  return;
30
30
  }
31
- if (userID === void 0) throw new ProtocolError({
31
+ if (userID === null) throw new ProtocolError({
32
32
  kind: Unauthorized,
33
33
  message: "Authenticated connections require a userID.",
34
34
  origin: ZeroCache
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","names":[],"sources":["../../../../../zero-cache/src/auth/auth.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {JWTPayload} from 'jose';\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 {\n isProtocolError,\n ProtocolError,\n type ErrorBody,\n} from '../../../zero-protocol/src/error.ts';\nimport type {PushError} from '../../../zero-protocol/src/push.ts';\n\n/** @deprecated JWT auth is deprecated */\nexport type JWTAuth = {\n readonly type: 'jwt';\n readonly raw: string;\n readonly decoded: JWTPayload;\n};\n\nexport type OpaqueAuth = {\n readonly type: 'opaque';\n readonly raw: string;\n};\n\nexport type Auth = OpaqueAuth | JWTAuth;\n\nexport type ValidateLegacyJWT = (\n token: string,\n ctx: {readonly userID: string | undefined},\n) => Promise<JWTAuth>;\n\nfunction isProvidedAuth(wireAuth: string | undefined): wireAuth is string {\n return wireAuth !== undefined && wireAuth !== '';\n}\n\nexport function authEquals(a: Auth | undefined, b: Auth | undefined) {\n if (a === b) {\n return true;\n }\n if (!a || !b) {\n return false;\n }\n return a.type === b.type && a.raw === b.raw;\n}\n\n/**\n * Resolves one auth snapshot transition without binding it to a client group.\n */\nexport async function resolveAuth(\n lc: LogContext,\n previousAuth: Auth | undefined,\n userID: string | undefined,\n wireAuth: string | undefined,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n): Promise<Auth | undefined> {\n try {\n const hasProvidedAuth = isProvidedAuth(wireAuth);\n\n if (previousAuth) {\n lc.debug?.(`Attempting to update auth from previous value`);\n } else {\n lc.debug?.(`Attempting to initialize auth`);\n }\n\n if (!hasProvidedAuth && previousAuth) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'No token provided. An unauthenticated client cannot connect to an authenticated client group.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (!hasProvidedAuth) {\n lc.debug?.(`Cleared auth`);\n return undefined;\n }\n\n if (userID === undefined) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message: 'Authenticated connections require a userID.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (validateLegacyJWT !== undefined) {\n const verifiedToken = await validateLegacyJWT(wireAuth, {userID});\n const nextAuth = pickToken(lc, previousAuth, verifiedToken);\n lc.debug?.(`Updated auth with JWT`);\n return nextAuth;\n }\n\n if (previousAuth?.type === 'jwt') {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Token type cannot change from JWT to opaque. Connections are pinned to a single token type.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (previousAuth?.type === 'opaque' && previousAuth.raw === wireAuth) {\n lc.debug?.(`Opaque auth unchanged, reusing previous snapshot`);\n return previousAuth;\n }\n\n lc.debug?.(`Updated auth with opaque token`);\n return {\n type: 'opaque',\n raw: wireAuth,\n };\n } catch (e) {\n if (isProtocolError(e)) {\n throw e;\n }\n throw new ProtocolError({\n kind: ErrorKind.AuthInvalidated,\n message: `Failed to decode auth token: ${String(e)}`,\n origin: ErrorOrigin.ZeroCache,\n });\n }\n}\n\n/** @deprecated used only in old JWT validation/rotation auth */\nexport function pickToken(\n lc: LogContext,\n previousToken: Auth | undefined,\n newToken: Auth | undefined | null,\n) {\n if (newToken === null) {\n return undefined;\n }\n\n if (\n previousToken?.type &&\n newToken?.type &&\n previousToken?.type !== newToken?.type\n ) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Token type cannot change. Client groups are pinned to a single token type.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (previousToken === undefined) {\n lc.debug?.(`No previous token, using new token`);\n return newToken;\n }\n\n if (newToken?.type === 'opaque') {\n return newToken;\n }\n\n if (previousToken.type === 'opaque') {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Token type cannot change from opaque to JWT. Client groups are pinned to a single token type.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (newToken) {\n if (previousToken.decoded.sub !== newToken.decoded.sub) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'The user id in the new token does not match the previous token. Client groups are pinned to a single user.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (previousToken.decoded.iat === undefined) {\n lc.debug?.(`No issued at time for the existing token, using new token`);\n // No issued at time for the existing token? We take the most recently received token.\n return newToken;\n }\n\n if (newToken.decoded.iat === undefined) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'The new token does not have an issued at time but the prior token does. Tokens for a client group must either all have issued at times or all not have issued at times',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n // The new token is newer, so we take it.\n if (previousToken.decoded.iat < newToken.decoded.iat) {\n lc.debug?.(`New token is newer, using it`);\n return newToken;\n }\n\n // if the new token is older or the same, we keep the existing token.\n lc.debug?.(`New token is older or the same, using existing token`);\n return previousToken;\n }\n\n // previousToken !== undefined but newToken is undefined\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'No token provided. An unauthenticated client cannot connect to an authenticated client group.',\n origin: ErrorOrigin.ZeroCache,\n });\n}\n\nexport function isAuthErrorBody(ex: unknown): ex is ErrorBody | PushError {\n if (typeof ex !== 'object' || ex === null) {\n return false;\n }\n\n if ('error' in ex) {\n return (\n ex.error === 'http' &&\n 'status' in ex &&\n (ex.status === 401 || ex.status === 403)\n );\n }\n\n if (!('kind' in ex)) {\n return false;\n }\n\n if (\n ex.kind === ErrorKind.AuthInvalidated ||\n ex.kind === ErrorKind.Unauthorized\n ) {\n return true;\n }\n\n return (\n (ex.kind === ErrorKind.PushFailed ||\n ex.kind === ErrorKind.TransformFailed) &&\n 'reason' in ex &&\n ex.reason === ErrorReason.HTTP &&\n 'status' in ex &&\n (ex.status === 401 || ex.status === 403)\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","names":[],"sources":["../../../../../zero-cache/src/auth/auth.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {JWTPayload} from 'jose';\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 {\n isProtocolError,\n ProtocolError,\n type ErrorBody,\n} from '../../../zero-protocol/src/error.ts';\nimport type {PushError} from '../../../zero-protocol/src/push.ts';\n\n/** @deprecated JWT auth is deprecated */\nexport type JWTAuth = {\n readonly type: 'jwt';\n readonly raw: string;\n readonly decoded: JWTPayload;\n};\n\nexport type OpaqueAuth = {\n readonly type: 'opaque';\n readonly raw: string;\n};\n\nexport type Auth = OpaqueAuth | JWTAuth;\n\nexport type ValidateLegacyJWT = (\n token: string,\n ctx: {readonly userID: string | undefined},\n) => Promise<JWTAuth>;\n\nfunction isProvidedAuth(wireAuth: string | undefined): wireAuth is string {\n return wireAuth !== undefined && wireAuth !== '';\n}\n\nexport function authEquals(a: Auth | undefined, b: Auth | undefined) {\n if (a === b) {\n return true;\n }\n if (!a || !b) {\n return false;\n }\n return a.type === b.type && a.raw === b.raw;\n}\n\n/**\n * Resolves one auth snapshot transition without binding it to a client group.\n */\nexport async function resolveAuth(\n lc: LogContext,\n previousAuth: Auth | undefined,\n userID: string | null,\n wireAuth: string | undefined,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n): Promise<Auth | undefined> {\n try {\n const hasProvidedAuth = isProvidedAuth(wireAuth);\n\n if (previousAuth) {\n lc.debug?.(`Attempting to update auth from previous value`);\n } else {\n lc.debug?.(`Attempting to initialize auth`);\n }\n\n if (!hasProvidedAuth && previousAuth) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'No token provided. An unauthenticated client cannot connect to an authenticated client group.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (!hasProvidedAuth) {\n lc.debug?.(`Cleared auth`);\n return undefined;\n }\n\n if (userID === null) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message: 'Authenticated connections require a userID.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (validateLegacyJWT !== undefined) {\n const verifiedToken = await validateLegacyJWT(wireAuth, {userID});\n const nextAuth = pickToken(lc, previousAuth, verifiedToken);\n lc.debug?.(`Updated auth with JWT`);\n return nextAuth;\n }\n\n if (previousAuth?.type === 'jwt') {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Token type cannot change from JWT to opaque. Connections are pinned to a single token type.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (previousAuth?.type === 'opaque' && previousAuth.raw === wireAuth) {\n lc.debug?.(`Opaque auth unchanged, reusing previous snapshot`);\n return previousAuth;\n }\n\n lc.debug?.(`Updated auth with opaque token`);\n return {\n type: 'opaque',\n raw: wireAuth,\n };\n } catch (e) {\n if (isProtocolError(e)) {\n throw e;\n }\n throw new ProtocolError({\n kind: ErrorKind.AuthInvalidated,\n message: `Failed to decode auth token: ${String(e)}`,\n origin: ErrorOrigin.ZeroCache,\n });\n }\n}\n\n/** @deprecated used only in old JWT validation/rotation auth */\nexport function pickToken(\n lc: LogContext,\n previousToken: Auth | undefined,\n newToken: Auth | undefined | null,\n) {\n if (newToken === null) {\n return undefined;\n }\n\n if (\n previousToken?.type &&\n newToken?.type &&\n previousToken?.type !== newToken?.type\n ) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Token type cannot change. Client groups are pinned to a single token type.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (previousToken === undefined) {\n lc.debug?.(`No previous token, using new token`);\n return newToken;\n }\n\n if (newToken?.type === 'opaque') {\n return newToken;\n }\n\n if (previousToken.type === 'opaque') {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Token type cannot change from opaque to JWT. Client groups are pinned to a single token type.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (newToken) {\n if (previousToken.decoded.sub !== newToken.decoded.sub) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'The user id in the new token does not match the previous token. Client groups are pinned to a single user.',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n if (previousToken.decoded.iat === undefined) {\n lc.debug?.(`No issued at time for the existing token, using new token`);\n // No issued at time for the existing token? We take the most recently received token.\n return newToken;\n }\n\n if (newToken.decoded.iat === undefined) {\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'The new token does not have an issued at time but the prior token does. Tokens for a client group must either all have issued at times or all not have issued at times',\n origin: ErrorOrigin.ZeroCache,\n });\n }\n\n // The new token is newer, so we take it.\n if (previousToken.decoded.iat < newToken.decoded.iat) {\n lc.debug?.(`New token is newer, using it`);\n return newToken;\n }\n\n // if the new token is older or the same, we keep the existing token.\n lc.debug?.(`New token is older or the same, using existing token`);\n return previousToken;\n }\n\n // previousToken !== undefined but newToken is undefined\n throw new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'No token provided. An unauthenticated client cannot connect to an authenticated client group.',\n origin: ErrorOrigin.ZeroCache,\n });\n}\n\nexport function isAuthErrorBody(ex: unknown): ex is ErrorBody | PushError {\n if (typeof ex !== 'object' || ex === null) {\n return false;\n }\n\n if ('error' in ex) {\n return (\n ex.error === 'http' &&\n 'status' in ex &&\n (ex.status === 401 || ex.status === 403)\n );\n }\n\n if (!('kind' in ex)) {\n return false;\n }\n\n if (\n ex.kind === ErrorKind.AuthInvalidated ||\n ex.kind === ErrorKind.Unauthorized\n ) {\n return true;\n }\n\n return (\n (ex.kind === ErrorKind.PushFailed ||\n ex.kind === ErrorKind.TransformFailed) &&\n 'reason' in ex &&\n ex.reason === ErrorReason.HTTP &&\n 'status' in ex &&\n (ex.status === 401 || ex.status === 403)\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import type { LogContext } from '@rocicorp/logger';
2
2
  import type { JWTPayload } from 'jose';
3
- import type { CRUDOp, UpsertOp } from '../../../zero-protocol/src/push.ts';
3
+ import type { CRUDOp, UpsertOp } from '../../../zero-protocol/src/mutation.ts';
4
4
  import type { DatabaseStorage } from '../../../zqlite/src/database-storage.ts';
5
5
  import type { Database } from '../../../zqlite/src/db.ts';
6
6
  import type { ZeroConfig } from '../config/zero-config.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"write-authorizer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/auth/write-authorizer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,MAAM,CAAC;AAUrC,OAAO,KAAK,EACV,MAAM,EAIN,QAAQ,EACT,MAAM,oCAAoC,CAAC;AAkB5C,OAAO,KAAK,EAEV,eAAe,EAChB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAC;AAMxD,OAAO,KAAK,EAAY,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAapE,MAAM,WAAW,eAAe;IAC9B,cAAc,CACZ,QAAQ,EAAE,UAAU,GAAG,SAAS,EAChC,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,GAC/B,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,eAAe,CACb,QAAQ,EAAE,UAAU,GAAG,SAAS,EAChC,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,GAC/B,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,iBAAiB,IAAI,IAAI,CAAC;IAC1B,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;IAEzD;;;OAGG;IACH,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACzC;AAED,qBAAa,mBAAoB,YAAW,eAAe;;gBAgBvD,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,iBAAiB,EAAE,eAAe;IAwBpC,iBAAiB;IAUjB,OAAO;IAID,cAAc,CAClB,QAAQ,EAAE,UAAU,GAAG,SAAS,EAChC,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE;IAsB5B,eAAe,CACnB,QAAQ,EAAE,UAAU,GAAG,SAAS,EAChC,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE;IA0ElC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE;IAuBxD,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI;CAgUxC"}
1
+ {"version":3,"file":"write-authorizer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/auth/write-authorizer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,MAAM,CAAC;AAMrC,OAAO,KAAK,EACV,MAAM,EAIN,QAAQ,EACT,MAAM,wCAAwC,CAAC;AAsBhD,OAAO,KAAK,EAEV,eAAe,EAChB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAC;AAMxD,OAAO,KAAK,EAAY,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAapE,MAAM,WAAW,eAAe;IAC9B,cAAc,CACZ,QAAQ,EAAE,UAAU,GAAG,SAAS,EAChC,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,GAC/B,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,eAAe,CACb,QAAQ,EAAE,UAAU,GAAG,SAAS,EAChC,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,GAC/B,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,iBAAiB,IAAI,IAAI,CAAC;IAC1B,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;IAEzD;;;OAGG;IACH,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACzC;AAED,qBAAa,mBAAoB,YAAW,eAAe;;gBAgBvD,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,iBAAiB,EAAE,eAAe;IAwBpC,iBAAiB;IAUjB,OAAO;IAID,cAAc,CAClB,QAAQ,EAAE,UAAU,GAAG,SAAS,EAChC,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE;IAsB5B,eAAe,CACnB,QAAQ,EAAE,UAAU,GAAG,SAAS,EAChC,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE;IA0ElC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE;IAuBxD,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI;CAgUxC"}
@@ -1 +1 @@
1
- {"version":3,"file":"write-authorizer.js","names":["#schema","#replica","#builderDelegate","#tableSpecs","#tables","#statementRunner","#lc","#appID","#logConfig","#cgStorage","#config","#getSource","#loadedPermissions","#canUpdate","#canDelete","#requirePreMutationRow","#canInsert","#getPreMutationRow","#timedCanDo","#canDo","#getPrimaryKey","#passesPolicyGroup","#passesPolicy"],"sources":["../../../../../zero-cache/src/auth/write-authorizer.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport type {MaybePromise} from '@opentelemetry/resources';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {JWTPayload} from 'jose';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {JSONValue, ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {Condition} from '../../../zero-protocol/src/ast.ts';\nimport {\n primaryKeyValueSchema,\n type PrimaryKeyValue,\n} from '../../../zero-protocol/src/primary-key.ts';\nimport type {\n CRUDOp,\n DeleteOp,\n InsertOp,\n UpdateOp,\n UpsertOp,\n} from '../../../zero-protocol/src/push.ts';\nimport type {Policy} from '../../../zero-schema/src/compiled-permissions.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport type {BuilderDelegate} from '../../../zql/src/builder/builder.ts';\nimport {\n bindStaticParameters,\n buildPipeline,\n} from '../../../zql/src/builder/builder.ts';\nimport {\n makeSourceChangeAdd,\n makeSourceChangeEdit,\n makeSourceChangeRemove,\n} from '../../../zql/src/ivm/source.ts';\nimport {consume} from '../../../zql/src/ivm/stream.ts';\nimport {simplifyCondition} from '../../../zql/src/query/expression.ts';\nimport {asQueryInternals} from '../../../zql/src/query/query-internals.ts';\nimport type {Query} from '../../../zql/src/query/query.ts';\nimport {newStaticQuery} from '../../../zql/src/query/static-query.ts';\nimport type {\n ClientGroupStorage,\n DatabaseStorage,\n} from '../../../zqlite/src/database-storage.ts';\nimport type {Database} from '../../../zqlite/src/db.ts';\nimport {compile, sql} from '../../../zqlite/src/internal/sql.ts';\nimport {\n fromSQLiteTypes,\n TableSource,\n} from '../../../zqlite/src/table-source.ts';\nimport type {LogConfig, ZeroConfig} from '../config/zero-config.ts';\nimport {computeZqlSpecs} from '../db/lite-tables.ts';\nimport type {LiteAndZqlSpec} from '../db/specs.ts';\nimport {StatementRunner} from '../db/statements.ts';\nimport {mapLiteDataTypeToZqlSchemaValue} from '../types/lite.ts';\nimport {\n getSchema,\n reloadPermissionsIfChanged,\n type LoadedPermissions,\n} from './load-permissions.ts';\n\ntype Phase = 'preMutation' | 'postMutation';\n\nexport interface WriteAuthorizer {\n canPreMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ): Promise<boolean>;\n canPostMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ): Promise<boolean>;\n reloadPermissions(): void;\n normalizeOps(ops: CRUDOp[]): Exclude<CRUDOp, UpsertOp>[];\n\n /**\n * Validates that all table names in the operations exist in the schema.\n * @throws Error if any table name is invalid\n */\n validateTableNames(ops: CRUDOp[]): void;\n}\n\nexport class WriteAuthorizerImpl implements WriteAuthorizer {\n readonly #schema: Schema;\n readonly #replica: Database;\n readonly #builderDelegate: BuilderDelegate;\n readonly #tableSpecs: Map<string, LiteAndZqlSpec>;\n readonly #tables = new Map<string, TableSource>();\n readonly #statementRunner: StatementRunner;\n readonly #lc: LogContext;\n readonly #appID: string;\n readonly #logConfig: LogConfig;\n readonly #cgStorage: ClientGroupStorage;\n readonly #config: ZeroConfig;\n\n #loadedPermissions: LoadedPermissions | null = null;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n replica: Database,\n appID: string,\n cgID: string,\n writeAuthzStorage: DatabaseStorage,\n ) {\n this.#appID = appID;\n this.#config = config;\n this.#lc = lc.withContext('class', 'WriteAuthorizerImpl');\n this.#logConfig = config.log;\n this.#schema = getSchema(this.#lc, replica);\n this.#replica = replica;\n this.#cgStorage = writeAuthzStorage.createClientGroupStorage(cgID);\n this.#builderDelegate = {\n getSource: name => this.#getSource(name),\n createStorage: () => this.#cgStorage.createStorage(),\n decorateSourceInput: input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n };\n this.#tableSpecs = computeZqlSpecs(this.#lc, replica, {\n includeBackfillingColumns: false,\n });\n this.#statementRunner = new StatementRunner(replica);\n this.reloadPermissions();\n }\n\n reloadPermissions() {\n this.#loadedPermissions = reloadPermissionsIfChanged(\n this.#lc,\n this.#statementRunner,\n this.#appID,\n this.#loadedPermissions,\n this.#config,\n ).permissions;\n }\n\n destroy() {\n this.#cgStorage.destroy();\n }\n\n async canPreMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ) {\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n // insert does not run pre-mutation checks\n break;\n case 'update':\n if (!(await this.#canUpdate('preMutation', authData, op))) {\n return false;\n }\n break;\n case 'delete':\n if (!(await this.#canDelete('preMutation', authData, op))) {\n return false;\n }\n break;\n }\n }\n return true;\n }\n\n async canPostMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ) {\n this.#statementRunner.beginConcurrent();\n let opError: unknown;\n try {\n for (const op of ops) {\n const source = this.#getSource(op.tableName);\n switch (op.op) {\n case 'insert': {\n consume(source.push(makeSourceChangeAdd(op.value)));\n break;\n }\n // TODO(mlaw): what if someone updates the same thing twice?\n // TODO(aa): It seems like it will just work? source.push()\n // is going to push the row into the table source, and then the\n // next requirePreMutationRow will just return the row that was\n // pushed in.\n case 'update': {\n consume(\n source.push(\n makeSourceChangeEdit(op.value, this.#requirePreMutationRow(op)),\n ),\n );\n break;\n }\n case 'delete': {\n consume(\n source.push(\n makeSourceChangeRemove(this.#requirePreMutationRow(op)),\n ),\n );\n break;\n }\n }\n }\n\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n if (!(await this.#canInsert('postMutation', authData, op))) {\n return false;\n }\n break;\n case 'update':\n if (!(await this.#canUpdate('postMutation', authData, op))) {\n return false;\n }\n break;\n case 'delete':\n // delete does not run post-mutation checks.\n break;\n }\n }\n } catch (e) {\n opError = e;\n throw e;\n } finally {\n try {\n this.#statementRunner.rollback();\n } catch (rollbackError) {\n if (opError !== undefined) {\n const combinedError = new Error(\n `canPostMutation failed and rollback also failed: operation error = ${String(opError)}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = opError;\n throw combinedError;\n }\n throw rollbackError;\n }\n }\n\n return true;\n }\n\n normalizeOps(ops: CRUDOp[]): Exclude<CRUDOp, UpsertOp>[] {\n return ops.map(op => {\n if (op.op === 'upsert') {\n const preMutationRow = this.#getPreMutationRow(op);\n if (preMutationRow) {\n return {\n op: 'update',\n tableName: op.tableName,\n primaryKey: op.primaryKey,\n value: op.value,\n };\n }\n return {\n op: 'insert',\n tableName: op.tableName,\n primaryKey: op.primaryKey,\n value: op.value,\n };\n }\n return op;\n });\n }\n\n validateTableNames(ops: CRUDOp[]): void {\n for (const op of ops) {\n if (!this.#tableSpecs.has(op.tableName)) {\n throw new Error(`Table '${op.tableName}' is not a valid table.`);\n }\n }\n }\n\n #canInsert(phase: Phase, authData: JWTPayload | undefined, op: InsertOp) {\n return this.#timedCanDo(phase, 'insert', authData, op);\n }\n\n #canUpdate(phase: Phase, authData: JWTPayload | undefined, op: UpdateOp) {\n return this.#timedCanDo(phase, 'update', authData, op);\n }\n\n #canDelete(phase: Phase, authData: JWTPayload | undefined, op: DeleteOp) {\n return this.#timedCanDo(phase, 'delete', authData, op);\n }\n\n /**\n * Gets schema-defined primary key and validates that operation contains required PK values.\n *\n * @returns Record where keys are column names and values are client-provided values\n * @throws Error if operation value is missing required primary key columns\n */\n #getPrimaryKey(\n tableName: string,\n opValue: Record<string, ReadonlyJSONValue | undefined>,\n ): Record<string, ReadonlyJSONValue> {\n const tableSpec = this.#tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(`Table ${tableName} not found`);\n }\n const columns = tableSpec.tableSpec.primaryKey;\n\n // Extract primary key values from operation value and validate they exist\n const values: Record<string, ReadonlyJSONValue> = {};\n for (const col of columns) {\n const val = opValue[col];\n if (val === undefined) {\n throw new Error(\n `Primary key column '${col}' is missing from operation value for table ${tableName}`,\n );\n }\n values[col] = val;\n }\n\n return values;\n }\n\n #getSource(tableName: string) {\n let source = this.#tables.get(tableName);\n if (source) {\n return source;\n }\n const tableSpec = this.#tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(`Table ${tableName} not found`);\n }\n const {columns, primaryKey} = tableSpec.tableSpec;\n assert(\n primaryKey.length,\n () => `Table ${tableName} must have a primary key`,\n );\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n this.#replica,\n tableName,\n Object.fromEntries(\n Object.entries(columns).map(([name, {dataType}]) => [\n name,\n mapLiteDataTypeToZqlSchemaValue(dataType),\n ]),\n ),\n [primaryKey[0], ...primaryKey.slice(1)],\n );\n this.#tables.set(tableName, source);\n\n return source;\n }\n\n async #timedCanDo<A extends keyof ActionOpMap>(\n phase: Phase,\n action: A,\n authData: JWTPayload | undefined,\n op: ActionOpMap[A],\n ) {\n const start = performance.now();\n try {\n const ret = await this.#canDo(phase, action, authData, op);\n return ret;\n } finally {\n this.#lc.info?.(\n 'action:',\n action,\n 'duration:',\n performance.now() - start,\n 'tableName:',\n op.tableName,\n 'primaryKey:',\n op.primaryKey,\n );\n }\n }\n\n /**\n * Evaluation order is from static to dynamic, broad to specific.\n * table -> column -> row -> cell.\n *\n * If any step fails, the entire operation is denied.\n *\n * That is, table rules supersede column rules, which supersede row rules,\n *\n * All steps must allow for the operation to be allowed.\n */\n async #canDo<A extends keyof ActionOpMap>(\n phase: Phase,\n action: A,\n authData: JWTPayload | undefined,\n op: ActionOpMap[A],\n ) {\n const rules = must(this.#loadedPermissions)?.permissions?.tables?.[\n op.tableName\n ];\n const rowPolicies = rules?.row;\n let rowQuery = newStaticQuery(this.#schema, op.tableName);\n\n const primaryKeyValues = this.#getPrimaryKey(op.tableName, op.value);\n\n for (const pk in primaryKeyValues) {\n rowQuery = rowQuery.where(pk, '=', primaryKeyValues[pk]);\n }\n\n let applicableRowPolicy: Policy | undefined;\n switch (action) {\n case 'insert':\n if (phase === 'postMutation') {\n applicableRowPolicy = rowPolicies?.insert;\n }\n break;\n case 'update':\n if (phase === 'preMutation') {\n applicableRowPolicy = rowPolicies?.update?.preMutation;\n } else if (phase === 'postMutation') {\n applicableRowPolicy = rowPolicies?.update?.postMutation;\n }\n break;\n case 'delete':\n if (phase === 'preMutation') {\n applicableRowPolicy = rowPolicies?.delete;\n }\n break;\n }\n\n const cellPolicies = rules?.cell;\n const applicableCellPolicies: Policy[] = [];\n if (cellPolicies) {\n for (const [column, policy] of Object.entries(cellPolicies)) {\n if (action === 'update' && op.value[column] === undefined) {\n // If the cell is not being updated, we do not need to check\n // the cell rules.\n continue;\n }\n switch (action) {\n case 'insert':\n if (policy.insert && phase === 'postMutation') {\n applicableCellPolicies.push(policy.insert);\n }\n break;\n case 'update':\n if (phase === 'preMutation' && policy.update?.preMutation) {\n applicableCellPolicies.push(policy.update.preMutation);\n }\n if (phase === 'postMutation' && policy.update?.postMutation) {\n applicableCellPolicies.push(policy.update.postMutation);\n }\n break;\n case 'delete':\n if (policy.delete && phase === 'preMutation') {\n applicableCellPolicies.push(policy.delete);\n }\n break;\n }\n }\n }\n\n if (\n !(await this.#passesPolicyGroup(\n applicableRowPolicy,\n applicableCellPolicies,\n authData,\n rowQuery,\n ))\n ) {\n this.#lc.warn?.(\n `Permission check failed for ${JSON.stringify(\n op,\n )}, action ${action}, phase ${phase}, authData: ${JSON.stringify(\n authData,\n )}, rowPolicies: ${JSON.stringify(\n applicableRowPolicy,\n )}, cellPolicies: ${JSON.stringify(applicableCellPolicies)}`,\n );\n return false;\n }\n\n return true;\n }\n\n #getPreMutationRow(op: UpsertOp | UpdateOp | DeleteOp) {\n const {value} = op;\n\n const primaryKeyValues = this.#getPrimaryKey(op.tableName, value);\n\n const spec = this.#tableSpecs.get(op.tableName);\n if (!spec) {\n throw new Error(`Table ${op.tableName} not found`);\n }\n\n const conditions: SQLQuery[] = [];\n const values: PrimaryKeyValue[] = [];\n for (const pk in primaryKeyValues) {\n conditions.push(sql`${sql.ident(pk)}=?`);\n values.push(v.parse(primaryKeyValues[pk], primaryKeyValueSchema));\n }\n\n const ret = this.#statementRunner.get(\n compile(\n sql`SELECT ${sql.join(\n Object.keys(spec.zqlSpec).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(op.tableName)} WHERE ${sql.join(\n conditions,\n sql` AND `,\n )}`,\n ),\n ...values,\n );\n if (ret === undefined) {\n return ret;\n }\n return fromSQLiteTypes(spec.zqlSpec, ret, op.tableName);\n }\n\n #requirePreMutationRow(op: UpdateOp | DeleteOp) {\n const ret = this.#getPreMutationRow(op);\n assert(\n ret !== undefined,\n () => `Pre-mutation row not found for ${JSON.stringify(op.value)}`,\n );\n return ret;\n }\n\n async #passesPolicyGroup(\n applicableRowPolicy: Policy | undefined,\n applicableCellPolicies: Policy[],\n authData: JWTPayload | undefined,\n rowQuery: Query<string, Schema>,\n ) {\n if (!(await this.#passesPolicy(applicableRowPolicy, authData, rowQuery))) {\n return false;\n }\n\n for (const policy of applicableCellPolicies) {\n if (!(await this.#passesPolicy(policy, authData, rowQuery))) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Defaults to *false* if the policy is empty. At least one rule has to pass\n * for the policy to pass.\n */\n #passesPolicy(\n policy: Policy | undefined,\n authData: JWTPayload | undefined,\n rowQuery: Query<string, Schema>,\n ): MaybePromise<boolean> {\n if (policy === undefined) {\n return false;\n }\n if (policy.length === 0) {\n return false;\n }\n let rowQueryAst = asQueryInternals(rowQuery).ast;\n rowQueryAst = bindStaticParameters(\n {\n ...rowQueryAst,\n where: updateWhere(rowQueryAst.where, policy),\n },\n {\n authData: authData as Record<string, JSONValue>,\n preMutationRow: undefined,\n },\n );\n\n // call the compiler directly\n // run the sql against upstream.\n // remove the collecting into json? just need to know if a row comes back.\n\n const input = buildPipeline(rowQueryAst, this.#builderDelegate, 'query-id');\n try {\n const res = input.fetch({});\n for (const _ of res) {\n // if any row is returned at all, the\n // rule passes.\n return true;\n }\n } finally {\n input.destroy();\n }\n\n // no rows returned by any rules? The policy fails.\n return false;\n }\n}\n\nfunction updateWhere(where: Condition | undefined, policy: Policy) {\n assert(where, 'A where condition must exist for RowQuery');\n\n return simplifyCondition({\n type: 'and',\n conditions: [\n where,\n {\n type: 'or',\n conditions: policy.map(([action, rule]) => {\n assert(action, 'action must be defined in policy');\n return rule;\n }),\n },\n ],\n });\n}\n\ntype ActionOpMap = {\n insert: InsertOp;\n update: UpdateOp;\n delete: DeleteOp;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"write-authorizer.js","names":["#schema","#replica","#builderDelegate","#tableSpecs","#tables","#statementRunner","#lc","#appID","#logConfig","#cgStorage","#config","#getSource","#loadedPermissions","#canUpdate","#canDelete","#requirePreMutationRow","#canInsert","#getPreMutationRow","#timedCanDo","#canDo","#getPrimaryKey","#passesPolicyGroup","#passesPolicy"],"sources":["../../../../../zero-cache/src/auth/write-authorizer.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport type {MaybePromise} from '@opentelemetry/resources';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {JWTPayload} from 'jose';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {JSONValue, ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {Condition} from '../../../zero-protocol/src/ast.ts';\nimport type {\n CRUDOp,\n DeleteOp,\n InsertOp,\n UpdateOp,\n UpsertOp,\n} from '../../../zero-protocol/src/mutation.ts';\nimport {\n primaryKeyValueSchema,\n type PrimaryKeyValue,\n} from '../../../zero-protocol/src/primary-key.ts';\nimport type {Policy} from '../../../zero-schema/src/compiled-permissions.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport type {BuilderDelegate} from '../../../zql/src/builder/builder.ts';\nimport {\n bindStaticParameters,\n buildPipeline,\n} from '../../../zql/src/builder/builder.ts';\nimport {\n makeSourceChangeAdd,\n makeSourceChangeEdit,\n makeSourceChangeRemove,\n} from '../../../zql/src/ivm/source.ts';\nimport {consume} from '../../../zql/src/ivm/stream.ts';\nimport {simplifyCondition} from '../../../zql/src/query/expression.ts';\nimport {asQueryInternals} from '../../../zql/src/query/query-internals.ts';\nimport type {Query} from '../../../zql/src/query/query.ts';\nimport {newStaticQuery} from '../../../zql/src/query/static-query.ts';\nimport type {\n ClientGroupStorage,\n DatabaseStorage,\n} from '../../../zqlite/src/database-storage.ts';\nimport type {Database} from '../../../zqlite/src/db.ts';\nimport {compile, sql} from '../../../zqlite/src/internal/sql.ts';\nimport {\n fromSQLiteTypes,\n TableSource,\n} from '../../../zqlite/src/table-source.ts';\nimport type {LogConfig, ZeroConfig} from '../config/zero-config.ts';\nimport {computeZqlSpecs} from '../db/lite-tables.ts';\nimport type {LiteAndZqlSpec} from '../db/specs.ts';\nimport {StatementRunner} from '../db/statements.ts';\nimport {mapLiteDataTypeToZqlSchemaValue} from '../types/lite.ts';\nimport {\n getSchema,\n reloadPermissionsIfChanged,\n type LoadedPermissions,\n} from './load-permissions.ts';\n\ntype Phase = 'preMutation' | 'postMutation';\n\nexport interface WriteAuthorizer {\n canPreMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ): Promise<boolean>;\n canPostMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ): Promise<boolean>;\n reloadPermissions(): void;\n normalizeOps(ops: CRUDOp[]): Exclude<CRUDOp, UpsertOp>[];\n\n /**\n * Validates that all table names in the operations exist in the schema.\n * @throws Error if any table name is invalid\n */\n validateTableNames(ops: CRUDOp[]): void;\n}\n\nexport class WriteAuthorizerImpl implements WriteAuthorizer {\n readonly #schema: Schema;\n readonly #replica: Database;\n readonly #builderDelegate: BuilderDelegate;\n readonly #tableSpecs: Map<string, LiteAndZqlSpec>;\n readonly #tables = new Map<string, TableSource>();\n readonly #statementRunner: StatementRunner;\n readonly #lc: LogContext;\n readonly #appID: string;\n readonly #logConfig: LogConfig;\n readonly #cgStorage: ClientGroupStorage;\n readonly #config: ZeroConfig;\n\n #loadedPermissions: LoadedPermissions | null = null;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n replica: Database,\n appID: string,\n cgID: string,\n writeAuthzStorage: DatabaseStorage,\n ) {\n this.#appID = appID;\n this.#config = config;\n this.#lc = lc.withContext('class', 'WriteAuthorizerImpl');\n this.#logConfig = config.log;\n this.#schema = getSchema(this.#lc, replica);\n this.#replica = replica;\n this.#cgStorage = writeAuthzStorage.createClientGroupStorage(cgID);\n this.#builderDelegate = {\n getSource: name => this.#getSource(name),\n createStorage: () => this.#cgStorage.createStorage(),\n decorateSourceInput: input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n };\n this.#tableSpecs = computeZqlSpecs(this.#lc, replica, {\n includeBackfillingColumns: false,\n });\n this.#statementRunner = new StatementRunner(replica);\n this.reloadPermissions();\n }\n\n reloadPermissions() {\n this.#loadedPermissions = reloadPermissionsIfChanged(\n this.#lc,\n this.#statementRunner,\n this.#appID,\n this.#loadedPermissions,\n this.#config,\n ).permissions;\n }\n\n destroy() {\n this.#cgStorage.destroy();\n }\n\n async canPreMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ) {\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n // insert does not run pre-mutation checks\n break;\n case 'update':\n if (!(await this.#canUpdate('preMutation', authData, op))) {\n return false;\n }\n break;\n case 'delete':\n if (!(await this.#canDelete('preMutation', authData, op))) {\n return false;\n }\n break;\n }\n }\n return true;\n }\n\n async canPostMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ) {\n this.#statementRunner.beginConcurrent();\n let opError: unknown;\n try {\n for (const op of ops) {\n const source = this.#getSource(op.tableName);\n switch (op.op) {\n case 'insert': {\n consume(source.push(makeSourceChangeAdd(op.value)));\n break;\n }\n // TODO(mlaw): what if someone updates the same thing twice?\n // TODO(aa): It seems like it will just work? source.push()\n // is going to push the row into the table source, and then the\n // next requirePreMutationRow will just return the row that was\n // pushed in.\n case 'update': {\n consume(\n source.push(\n makeSourceChangeEdit(op.value, this.#requirePreMutationRow(op)),\n ),\n );\n break;\n }\n case 'delete': {\n consume(\n source.push(\n makeSourceChangeRemove(this.#requirePreMutationRow(op)),\n ),\n );\n break;\n }\n }\n }\n\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n if (!(await this.#canInsert('postMutation', authData, op))) {\n return false;\n }\n break;\n case 'update':\n if (!(await this.#canUpdate('postMutation', authData, op))) {\n return false;\n }\n break;\n case 'delete':\n // delete does not run post-mutation checks.\n break;\n }\n }\n } catch (e) {\n opError = e;\n throw e;\n } finally {\n try {\n this.#statementRunner.rollback();\n } catch (rollbackError) {\n if (opError !== undefined) {\n const combinedError = new Error(\n `canPostMutation failed and rollback also failed: operation error = ${String(opError)}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = opError;\n throw combinedError;\n }\n throw rollbackError;\n }\n }\n\n return true;\n }\n\n normalizeOps(ops: CRUDOp[]): Exclude<CRUDOp, UpsertOp>[] {\n return ops.map(op => {\n if (op.op === 'upsert') {\n const preMutationRow = this.#getPreMutationRow(op);\n if (preMutationRow) {\n return {\n op: 'update',\n tableName: op.tableName,\n primaryKey: op.primaryKey,\n value: op.value,\n };\n }\n return {\n op: 'insert',\n tableName: op.tableName,\n primaryKey: op.primaryKey,\n value: op.value,\n };\n }\n return op;\n });\n }\n\n validateTableNames(ops: CRUDOp[]): void {\n for (const op of ops) {\n if (!this.#tableSpecs.has(op.tableName)) {\n throw new Error(`Table '${op.tableName}' is not a valid table.`);\n }\n }\n }\n\n #canInsert(phase: Phase, authData: JWTPayload | undefined, op: InsertOp) {\n return this.#timedCanDo(phase, 'insert', authData, op);\n }\n\n #canUpdate(phase: Phase, authData: JWTPayload | undefined, op: UpdateOp) {\n return this.#timedCanDo(phase, 'update', authData, op);\n }\n\n #canDelete(phase: Phase, authData: JWTPayload | undefined, op: DeleteOp) {\n return this.#timedCanDo(phase, 'delete', authData, op);\n }\n\n /**\n * Gets schema-defined primary key and validates that operation contains required PK values.\n *\n * @returns Record where keys are column names and values are client-provided values\n * @throws Error if operation value is missing required primary key columns\n */\n #getPrimaryKey(\n tableName: string,\n opValue: Record<string, ReadonlyJSONValue | undefined>,\n ): Record<string, ReadonlyJSONValue> {\n const tableSpec = this.#tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(`Table ${tableName} not found`);\n }\n const columns = tableSpec.tableSpec.primaryKey;\n\n // Extract primary key values from operation value and validate they exist\n const values: Record<string, ReadonlyJSONValue> = {};\n for (const col of columns) {\n const val = opValue[col];\n if (val === undefined) {\n throw new Error(\n `Primary key column '${col}' is missing from operation value for table ${tableName}`,\n );\n }\n values[col] = val;\n }\n\n return values;\n }\n\n #getSource(tableName: string) {\n let source = this.#tables.get(tableName);\n if (source) {\n return source;\n }\n const tableSpec = this.#tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(`Table ${tableName} not found`);\n }\n const {columns, primaryKey} = tableSpec.tableSpec;\n assert(\n primaryKey.length,\n () => `Table ${tableName} must have a primary key`,\n );\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n this.#replica,\n tableName,\n Object.fromEntries(\n Object.entries(columns).map(([name, {dataType}]) => [\n name,\n mapLiteDataTypeToZqlSchemaValue(dataType),\n ]),\n ),\n [primaryKey[0], ...primaryKey.slice(1)],\n );\n this.#tables.set(tableName, source);\n\n return source;\n }\n\n async #timedCanDo<A extends keyof ActionOpMap>(\n phase: Phase,\n action: A,\n authData: JWTPayload | undefined,\n op: ActionOpMap[A],\n ) {\n const start = performance.now();\n try {\n const ret = await this.#canDo(phase, action, authData, op);\n return ret;\n } finally {\n this.#lc.info?.(\n 'action:',\n action,\n 'duration:',\n performance.now() - start,\n 'tableName:',\n op.tableName,\n 'primaryKey:',\n op.primaryKey,\n );\n }\n }\n\n /**\n * Evaluation order is from static to dynamic, broad to specific.\n * table -> column -> row -> cell.\n *\n * If any step fails, the entire operation is denied.\n *\n * That is, table rules supersede column rules, which supersede row rules,\n *\n * All steps must allow for the operation to be allowed.\n */\n async #canDo<A extends keyof ActionOpMap>(\n phase: Phase,\n action: A,\n authData: JWTPayload | undefined,\n op: ActionOpMap[A],\n ) {\n const rules = must(this.#loadedPermissions)?.permissions?.tables?.[\n op.tableName\n ];\n const rowPolicies = rules?.row;\n let rowQuery = newStaticQuery(this.#schema, op.tableName);\n\n const primaryKeyValues = this.#getPrimaryKey(op.tableName, op.value);\n\n for (const pk in primaryKeyValues) {\n rowQuery = rowQuery.where(pk, '=', primaryKeyValues[pk]);\n }\n\n let applicableRowPolicy: Policy | undefined;\n switch (action) {\n case 'insert':\n if (phase === 'postMutation') {\n applicableRowPolicy = rowPolicies?.insert;\n }\n break;\n case 'update':\n if (phase === 'preMutation') {\n applicableRowPolicy = rowPolicies?.update?.preMutation;\n } else if (phase === 'postMutation') {\n applicableRowPolicy = rowPolicies?.update?.postMutation;\n }\n break;\n case 'delete':\n if (phase === 'preMutation') {\n applicableRowPolicy = rowPolicies?.delete;\n }\n break;\n }\n\n const cellPolicies = rules?.cell;\n const applicableCellPolicies: Policy[] = [];\n if (cellPolicies) {\n for (const [column, policy] of Object.entries(cellPolicies)) {\n if (action === 'update' && op.value[column] === undefined) {\n // If the cell is not being updated, we do not need to check\n // the cell rules.\n continue;\n }\n switch (action) {\n case 'insert':\n if (policy.insert && phase === 'postMutation') {\n applicableCellPolicies.push(policy.insert);\n }\n break;\n case 'update':\n if (phase === 'preMutation' && policy.update?.preMutation) {\n applicableCellPolicies.push(policy.update.preMutation);\n }\n if (phase === 'postMutation' && policy.update?.postMutation) {\n applicableCellPolicies.push(policy.update.postMutation);\n }\n break;\n case 'delete':\n if (policy.delete && phase === 'preMutation') {\n applicableCellPolicies.push(policy.delete);\n }\n break;\n }\n }\n }\n\n if (\n !(await this.#passesPolicyGroup(\n applicableRowPolicy,\n applicableCellPolicies,\n authData,\n rowQuery,\n ))\n ) {\n this.#lc.warn?.(\n `Permission check failed for ${JSON.stringify(\n op,\n )}, action ${action}, phase ${phase}, authData: ${JSON.stringify(\n authData,\n )}, rowPolicies: ${JSON.stringify(\n applicableRowPolicy,\n )}, cellPolicies: ${JSON.stringify(applicableCellPolicies)}`,\n );\n return false;\n }\n\n return true;\n }\n\n #getPreMutationRow(op: UpsertOp | UpdateOp | DeleteOp) {\n const {value} = op;\n\n const primaryKeyValues = this.#getPrimaryKey(op.tableName, value);\n\n const spec = this.#tableSpecs.get(op.tableName);\n if (!spec) {\n throw new Error(`Table ${op.tableName} not found`);\n }\n\n const conditions: SQLQuery[] = [];\n const values: PrimaryKeyValue[] = [];\n for (const pk in primaryKeyValues) {\n conditions.push(sql`${sql.ident(pk)}=?`);\n values.push(v.parse(primaryKeyValues[pk], primaryKeyValueSchema));\n }\n\n const ret = this.#statementRunner.get(\n compile(\n sql`SELECT ${sql.join(\n Object.keys(spec.zqlSpec).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(op.tableName)} WHERE ${sql.join(\n conditions,\n sql` AND `,\n )}`,\n ),\n ...values,\n );\n if (ret === undefined) {\n return ret;\n }\n return fromSQLiteTypes(spec.zqlSpec, ret, op.tableName);\n }\n\n #requirePreMutationRow(op: UpdateOp | DeleteOp) {\n const ret = this.#getPreMutationRow(op);\n assert(\n ret !== undefined,\n () => `Pre-mutation row not found for ${JSON.stringify(op.value)}`,\n );\n return ret;\n }\n\n async #passesPolicyGroup(\n applicableRowPolicy: Policy | undefined,\n applicableCellPolicies: Policy[],\n authData: JWTPayload | undefined,\n rowQuery: Query<string, Schema>,\n ) {\n if (!(await this.#passesPolicy(applicableRowPolicy, authData, rowQuery))) {\n return false;\n }\n\n for (const policy of applicableCellPolicies) {\n if (!(await this.#passesPolicy(policy, authData, rowQuery))) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Defaults to *false* if the policy is empty. At least one rule has to pass\n * for the policy to pass.\n */\n #passesPolicy(\n policy: Policy | undefined,\n authData: JWTPayload | undefined,\n rowQuery: Query<string, Schema>,\n ): MaybePromise<boolean> {\n if (policy === undefined) {\n return false;\n }\n if (policy.length === 0) {\n return false;\n }\n let rowQueryAst = asQueryInternals(rowQuery).ast;\n rowQueryAst = bindStaticParameters(\n {\n ...rowQueryAst,\n where: updateWhere(rowQueryAst.where, policy),\n },\n {\n authData: authData as Record<string, JSONValue>,\n preMutationRow: undefined,\n },\n );\n\n // call the compiler directly\n // run the sql against upstream.\n // remove the collecting into json? just need to know if a row comes back.\n\n const input = buildPipeline(rowQueryAst, this.#builderDelegate, 'query-id');\n try {\n const res = input.fetch({});\n for (const _ of res) {\n // if any row is returned at all, the\n // rule passes.\n return true;\n }\n } finally {\n input.destroy();\n }\n\n // no rows returned by any rules? The policy fails.\n return false;\n }\n}\n\nfunction updateWhere(where: Condition | undefined, policy: Policy) {\n assert(where, 'A where condition must exist for RowQuery');\n\n return simplifyCondition({\n type: 'and',\n conditions: [\n where,\n {\n type: 'or',\n conditions: policy.map(([action, rule]) => {\n assert(action, 'action must be defined in policy');\n return rule;\n }),\n },\n ],\n });\n}\n\ntype ActionOpMap = {\n insert: InsertOp;\n update: UpdateOp;\n delete: DeleteOp;\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/config/normalize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,qEAAqE;AACrE,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,GAAG,EAAE;QACH,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,MAAM,IAAI,oBAAoB,CAexC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,aAAa,CAAC,EAAE,MAAM,GACrB,oBAAoB,CAyEtB"}
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/config/normalize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,qEAAqE;AACrE,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,GAAG,EAAE;QACH,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AASD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,MAAM,IAAI,oBAAoB,CAexC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,aAAa,CAAC,EAAE,MAAM,GACrB,oBAAoB,CA8EtB"}
@@ -6,6 +6,10 @@ import { nanoid } from "nanoid";
6
6
  function isDevelopmentMode() {
7
7
  return process.env.NODE_ENV === "development";
8
8
  }
9
+ function isRunningInECS() {
10
+ return process.env.ECS_CONTAINER_METADATA_URI_V4 !== void 0;
11
+ }
12
+ var DEFAULT_ECS_KEEPALIVE_TIMEOUT_MS = 2e4;
9
13
  function assertNormalized(config) {
10
14
  assert(config.taskID, "missing --task-id");
11
15
  assert(config.changeStreamer.port, "missing --change-streamer-port");
@@ -59,6 +63,10 @@ function normalizeZeroConfig(lc, config, env, defaultTaskID) {
59
63
  config.cvr.db = config.upstream.db;
60
64
  env["ZERO_CVR_DB"] = config.upstream.db;
61
65
  }
66
+ if (!config.keepaliveTimeoutMs && isRunningInECS()) {
67
+ config.keepaliveTimeoutMs = DEFAULT_ECS_KEEPALIVE_TIMEOUT_MS;
68
+ env["ZERO_KEEPALIVE_TIMEOUT_MS"] = String(DEFAULT_ECS_KEEPALIVE_TIMEOUT_MS);
69
+ }
62
70
  lc.info?.(`runtime env: taskID=${config.taskID}, hostIP=${hostIP}`);
63
71
  return {
64
72
  ...config,
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.js","names":[],"sources":["../../../../../zero-cache/src/config/normalize.ts"],"sourcesContent":["import {availableParallelism} from 'node:os';\nimport type {LogContext} from '@rocicorp/logger';\nimport {nanoid} from 'nanoid';\nimport {assert, assertNotUndefined} from '../../../shared/src/asserts.ts';\nimport {getHostIp} from './network.ts';\nimport type {ZeroConfig} from './zero-config.ts';\n\n/** {@link ZeroConfig} with defaults set per option documentation. */\nexport type NormalizedZeroConfig = ZeroConfig & {\n taskID: string;\n changeStreamer: {\n port: number;\n address: string;\n };\n change: {\n db: string;\n };\n cvr: {\n db: string;\n };\n litestream: {\n port: number;\n };\n numSyncWorkers: number;\n};\n\nexport function isDevelopmentMode(): boolean {\n return process.env.NODE_ENV === 'development';\n}\n\nexport function assertNormalized(\n config: ZeroConfig,\n): asserts config is NormalizedZeroConfig {\n assert(config.taskID, 'missing --task-id');\n assert(config.changeStreamer.port, 'missing --change-streamer-port');\n assert(config.changeStreamer.address, 'missing --change-streamer-address');\n assert(config.litestream.port, 'missing --litestream-port');\n assert(config.change.db, 'missing --change-db');\n assert(config.cvr.db, 'missing --cvr-db');\n assertNotUndefined(config.numSyncWorkers, 'missing --num-sync-workers');\n\n if (!isDevelopmentMode()) {\n assert(\n config.adminPassword,\n 'missing --admin-password: required in production mode',\n );\n }\n}\n\n/**\n * Normalizes the parsed `config` by setting defaults from the environment\n * or from other options as documented. When defaults are applied, the\n * corresponding `env` variable is updated so that the settings are propagated\n * to spawned child workers. Child workers can then call\n * {@link assertNormalized} to verify that the expected defaults have been set.\n */\nexport function normalizeZeroConfig(\n lc: LogContext,\n config: ZeroConfig,\n env: NodeJS.ProcessEnv,\n defaultTaskID?: string,\n): NormalizedZeroConfig {\n if (!config.taskID) {\n const taskID = defaultTaskID ?? nanoid();\n config.taskID = taskID;\n env['ZERO_TASK_ID'] = taskID;\n }\n if (!config.changeStreamer.port) {\n const port = config.port + 1;\n config.changeStreamer.port = port;\n env['ZERO_CHANGE_STREAMER_PORT'] = String(port);\n }\n if (!config.litestream.port) {\n const port = config.port + 2;\n config.litestream.port = port;\n env['ZERO_LITESTREAM_PORT'] = String(port);\n }\n if (config.numSyncWorkers === undefined) {\n // Reserve 1 core for the replicator. The change-streamer is not CPU heavy.\n const numSyncers = Math.max(1, availableParallelism() - 1);\n config.numSyncWorkers = numSyncers;\n env['ZERO_NUM_SYNC_WORKERS'] = String(numSyncers);\n }\n\n const hostIP = getHostIp(\n lc,\n config.changeStreamer.discoveryInterfacePreferences,\n );\n if (!config.changeStreamer.address) {\n const {port} = config.changeStreamer;\n const address = `${hostIP}:${port}`;\n config.changeStreamer.address = address;\n env['ZERO_CHANGE_STREAMER_ADDRESS'] = address;\n }\n\n if (!config.change.db) {\n config.change.db = config.upstream.db;\n env['ZERO_CHANGE_DB'] = config.upstream.db;\n }\n\n if (!config.cvr.db) {\n config.cvr.db = config.upstream.db;\n env['ZERO_CVR_DB'] = config.upstream.db;\n }\n\n lc.info?.(`runtime env: taskID=${config.taskID}, hostIP=${hostIP}`);\n\n return {\n ...config,\n taskID: config.taskID,\n\n changeStreamer: {\n ...config.changeStreamer,\n port: config.changeStreamer.port,\n address: config.changeStreamer.address,\n },\n\n litestream: {\n ...config.litestream,\n port: config.litestream.port,\n },\n\n change: {\n ...config.change,\n db: config.change.db,\n },\n\n cvr: {\n ...config.cvr,\n db: config.cvr.db,\n },\n\n numSyncWorkers: config.numSyncWorkers,\n };\n}\n"],"mappings":";;;;;AA0BA,SAAgB,oBAA6B;AAC3C,QAAA,QAAA,IAAA,aAAgC;;AAGlC,SAAgB,iBACd,QACwC;AACxC,QAAO,OAAO,QAAQ,oBAAoB;AAC1C,QAAO,OAAO,eAAe,MAAM,iCAAiC;AACpE,QAAO,OAAO,eAAe,SAAS,oCAAoC;AAC1E,QAAO,OAAO,WAAW,MAAM,4BAA4B;AAC3D,QAAO,OAAO,OAAO,IAAI,sBAAsB;AAC/C,QAAO,OAAO,IAAI,IAAI,mBAAmB;AACzC,oBAAmB,OAAO,gBAAgB,6BAA6B;AAEvE,KAAI,CAAC,mBAAmB,CACtB,QACE,OAAO,eACP,wDACD;;;;;;;;;AAWL,SAAgB,oBACd,IACA,QACA,KACA,eACsB;AACtB,KAAI,CAAC,OAAO,QAAQ;EAClB,MAAM,SAAS,iBAAiB,QAAQ;AACxC,SAAO,SAAS;AAChB,MAAI,kBAAkB;;AAExB,KAAI,CAAC,OAAO,eAAe,MAAM;EAC/B,MAAM,OAAO,OAAO,OAAO;AAC3B,SAAO,eAAe,OAAO;AAC7B,MAAI,+BAA+B,OAAO,KAAK;;AAEjD,KAAI,CAAC,OAAO,WAAW,MAAM;EAC3B,MAAM,OAAO,OAAO,OAAO;AAC3B,SAAO,WAAW,OAAO;AACzB,MAAI,0BAA0B,OAAO,KAAK;;AAE5C,KAAI,OAAO,mBAAmB,KAAA,GAAW;EAEvC,MAAM,aAAa,KAAK,IAAI,GAAG,sBAAsB,GAAG,EAAE;AAC1D,SAAO,iBAAiB;AACxB,MAAI,2BAA2B,OAAO,WAAW;;CAGnD,MAAM,SAAS,UACb,IACA,OAAO,eAAe,8BACvB;AACD,KAAI,CAAC,OAAO,eAAe,SAAS;EAClC,MAAM,EAAC,SAAQ,OAAO;EACtB,MAAM,UAAU,GAAG,OAAO,GAAG;AAC7B,SAAO,eAAe,UAAU;AAChC,MAAI,kCAAkC;;AAGxC,KAAI,CAAC,OAAO,OAAO,IAAI;AACrB,SAAO,OAAO,KAAK,OAAO,SAAS;AACnC,MAAI,oBAAoB,OAAO,SAAS;;AAG1C,KAAI,CAAC,OAAO,IAAI,IAAI;AAClB,SAAO,IAAI,KAAK,OAAO,SAAS;AAChC,MAAI,iBAAiB,OAAO,SAAS;;AAGvC,IAAG,OAAO,uBAAuB,OAAO,OAAO,WAAW,SAAS;AAEnE,QAAO;EACL,GAAG;EACH,QAAQ,OAAO;EAEf,gBAAgB;GACd,GAAG,OAAO;GACV,MAAM,OAAO,eAAe;GAC5B,SAAS,OAAO,eAAe;GAChC;EAED,YAAY;GACV,GAAG,OAAO;GACV,MAAM,OAAO,WAAW;GACzB;EAED,QAAQ;GACN,GAAG,OAAO;GACV,IAAI,OAAO,OAAO;GACnB;EAED,KAAK;GACH,GAAG,OAAO;GACV,IAAI,OAAO,IAAI;GAChB;EAED,gBAAgB,OAAO;EACxB"}
1
+ {"version":3,"file":"normalize.js","names":[],"sources":["../../../../../zero-cache/src/config/normalize.ts"],"sourcesContent":["import {availableParallelism} from 'node:os';\nimport type {LogContext} from '@rocicorp/logger';\nimport {nanoid} from 'nanoid';\nimport {assert, assertNotUndefined} from '../../../shared/src/asserts.ts';\nimport {getHostIp} from './network.ts';\nimport type {ZeroConfig} from './zero-config.ts';\n\n/** {@link ZeroConfig} with defaults set per option documentation. */\nexport type NormalizedZeroConfig = ZeroConfig & {\n taskID: string;\n changeStreamer: {\n port: number;\n address: string;\n };\n change: {\n db: string;\n };\n cvr: {\n db: string;\n };\n litestream: {\n port: number;\n };\n numSyncWorkers: number;\n};\n\nexport function isDevelopmentMode(): boolean {\n return process.env.NODE_ENV === 'development';\n}\n\nfunction isRunningInECS(): boolean {\n // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-environment-variables.html\n return process.env.ECS_CONTAINER_METADATA_URI_V4 !== undefined;\n}\n\nconst DEFAULT_ECS_KEEPALIVE_TIMEOUT_MS = 20_000;\n\nexport function assertNormalized(\n config: ZeroConfig,\n): asserts config is NormalizedZeroConfig {\n assert(config.taskID, 'missing --task-id');\n assert(config.changeStreamer.port, 'missing --change-streamer-port');\n assert(config.changeStreamer.address, 'missing --change-streamer-address');\n assert(config.litestream.port, 'missing --litestream-port');\n assert(config.change.db, 'missing --change-db');\n assert(config.cvr.db, 'missing --cvr-db');\n assertNotUndefined(config.numSyncWorkers, 'missing --num-sync-workers');\n\n if (!isDevelopmentMode()) {\n assert(\n config.adminPassword,\n 'missing --admin-password: required in production mode',\n );\n }\n}\n\n/**\n * Normalizes the parsed `config` by setting defaults from the environment\n * or from other options as documented. When defaults are applied, the\n * corresponding `env` variable is updated so that the settings are propagated\n * to spawned child workers. Child workers can then call\n * {@link assertNormalized} to verify that the expected defaults have been set.\n */\nexport function normalizeZeroConfig(\n lc: LogContext,\n config: ZeroConfig,\n env: NodeJS.ProcessEnv,\n defaultTaskID?: string,\n): NormalizedZeroConfig {\n if (!config.taskID) {\n const taskID = defaultTaskID ?? nanoid();\n config.taskID = taskID;\n env['ZERO_TASK_ID'] = taskID;\n }\n if (!config.changeStreamer.port) {\n const port = config.port + 1;\n config.changeStreamer.port = port;\n env['ZERO_CHANGE_STREAMER_PORT'] = String(port);\n }\n if (!config.litestream.port) {\n const port = config.port + 2;\n config.litestream.port = port;\n env['ZERO_LITESTREAM_PORT'] = String(port);\n }\n if (config.numSyncWorkers === undefined) {\n // Reserve 1 core for the replicator. The change-streamer is not CPU heavy.\n const numSyncers = Math.max(1, availableParallelism() - 1);\n config.numSyncWorkers = numSyncers;\n env['ZERO_NUM_SYNC_WORKERS'] = String(numSyncers);\n }\n\n const hostIP = getHostIp(\n lc,\n config.changeStreamer.discoveryInterfacePreferences,\n );\n if (!config.changeStreamer.address) {\n const {port} = config.changeStreamer;\n const address = `${hostIP}:${port}`;\n config.changeStreamer.address = address;\n env['ZERO_CHANGE_STREAMER_ADDRESS'] = address;\n }\n\n if (!config.change.db) {\n config.change.db = config.upstream.db;\n env['ZERO_CHANGE_DB'] = config.upstream.db;\n }\n\n if (!config.cvr.db) {\n config.cvr.db = config.upstream.db;\n env['ZERO_CVR_DB'] = config.upstream.db;\n }\n\n if (!config.keepaliveTimeoutMs && isRunningInECS()) {\n config.keepaliveTimeoutMs = DEFAULT_ECS_KEEPALIVE_TIMEOUT_MS;\n env['ZERO_KEEPALIVE_TIMEOUT_MS'] = String(DEFAULT_ECS_KEEPALIVE_TIMEOUT_MS);\n }\n\n lc.info?.(`runtime env: taskID=${config.taskID}, hostIP=${hostIP}`);\n\n return {\n ...config,\n taskID: config.taskID,\n\n changeStreamer: {\n ...config.changeStreamer,\n port: config.changeStreamer.port,\n address: config.changeStreamer.address,\n },\n\n litestream: {\n ...config.litestream,\n port: config.litestream.port,\n },\n\n change: {\n ...config.change,\n db: config.change.db,\n },\n\n cvr: {\n ...config.cvr,\n db: config.cvr.db,\n },\n\n numSyncWorkers: config.numSyncWorkers,\n };\n}\n"],"mappings":";;;;;AA0BA,SAAgB,oBAA6B;AAC3C,QAAA,QAAA,IAAA,aAAgC;;AAGlC,SAAS,iBAA0B;AAEjC,QAAO,QAAQ,IAAI,kCAAkC,KAAA;;AAGvD,IAAM,mCAAmC;AAEzC,SAAgB,iBACd,QACwC;AACxC,QAAO,OAAO,QAAQ,oBAAoB;AAC1C,QAAO,OAAO,eAAe,MAAM,iCAAiC;AACpE,QAAO,OAAO,eAAe,SAAS,oCAAoC;AAC1E,QAAO,OAAO,WAAW,MAAM,4BAA4B;AAC3D,QAAO,OAAO,OAAO,IAAI,sBAAsB;AAC/C,QAAO,OAAO,IAAI,IAAI,mBAAmB;AACzC,oBAAmB,OAAO,gBAAgB,6BAA6B;AAEvE,KAAI,CAAC,mBAAmB,CACtB,QACE,OAAO,eACP,wDACD;;;;;;;;;AAWL,SAAgB,oBACd,IACA,QACA,KACA,eACsB;AACtB,KAAI,CAAC,OAAO,QAAQ;EAClB,MAAM,SAAS,iBAAiB,QAAQ;AACxC,SAAO,SAAS;AAChB,MAAI,kBAAkB;;AAExB,KAAI,CAAC,OAAO,eAAe,MAAM;EAC/B,MAAM,OAAO,OAAO,OAAO;AAC3B,SAAO,eAAe,OAAO;AAC7B,MAAI,+BAA+B,OAAO,KAAK;;AAEjD,KAAI,CAAC,OAAO,WAAW,MAAM;EAC3B,MAAM,OAAO,OAAO,OAAO;AAC3B,SAAO,WAAW,OAAO;AACzB,MAAI,0BAA0B,OAAO,KAAK;;AAE5C,KAAI,OAAO,mBAAmB,KAAA,GAAW;EAEvC,MAAM,aAAa,KAAK,IAAI,GAAG,sBAAsB,GAAG,EAAE;AAC1D,SAAO,iBAAiB;AACxB,MAAI,2BAA2B,OAAO,WAAW;;CAGnD,MAAM,SAAS,UACb,IACA,OAAO,eAAe,8BACvB;AACD,KAAI,CAAC,OAAO,eAAe,SAAS;EAClC,MAAM,EAAC,SAAQ,OAAO;EACtB,MAAM,UAAU,GAAG,OAAO,GAAG;AAC7B,SAAO,eAAe,UAAU;AAChC,MAAI,kCAAkC;;AAGxC,KAAI,CAAC,OAAO,OAAO,IAAI;AACrB,SAAO,OAAO,KAAK,OAAO,SAAS;AACnC,MAAI,oBAAoB,OAAO,SAAS;;AAG1C,KAAI,CAAC,OAAO,IAAI,IAAI;AAClB,SAAO,IAAI,KAAK,OAAO,SAAS;AAChC,MAAI,iBAAiB,OAAO,SAAS;;AAGvC,KAAI,CAAC,OAAO,sBAAsB,gBAAgB,EAAE;AAClD,SAAO,qBAAqB;AAC5B,MAAI,+BAA+B,OAAO,iCAAiC;;AAG7E,IAAG,OAAO,uBAAuB,OAAO,OAAO,WAAW,SAAS;AAEnE,QAAO;EACL,GAAG;EACH,QAAQ,OAAO;EAEf,gBAAgB;GACd,GAAG,OAAO;GACV,MAAM,OAAO,eAAe;GAC5B,SAAS,OAAO,eAAe;GAChC;EAED,YAAY;GACV,GAAG,OAAO;GACV,MAAM,OAAO,WAAW;GACzB;EAED,QAAQ;GACN,GAAG,OAAO;GACV,IAAI,OAAO,OAAO;GACnB;EAED,KAAK;GACH,GAAG,OAAO;GACV,IAAI,OAAO,IAAI;GAChB;EAED,gBAAgB,OAAO;EACxB"}
@@ -77,11 +77,11 @@ declare const authOptions: {
77
77
  deprecated: string[];
78
78
  };
79
79
  revalidateIntervalSeconds: {
80
- type: v.Optional<number>;
80
+ type: v.Type<number>;
81
81
  desc: string[];
82
82
  };
83
83
  retransformIntervalSeconds: {
84
- type: v.Optional<number>;
84
+ type: v.Type<number>;
85
85
  desc: string[];
86
86
  };
87
87
  };
@@ -336,11 +336,11 @@ export declare const zeroOptions: {
336
336
  deprecated: string[];
337
337
  };
338
338
  revalidateIntervalSeconds: {
339
- type: v.Optional<number>;
339
+ type: v.Type<number>;
340
340
  desc: string[];
341
341
  };
342
342
  retransformIntervalSeconds: {
343
- type: v.Optional<number>;
343
+ type: v.Type<number>;
344
344
  desc: string[];
345
345
  };
346
346
  };
@@ -348,6 +348,10 @@ export declare const zeroOptions: {
348
348
  type: v.Type<number>;
349
349
  desc: string[];
350
350
  };
351
+ keepaliveTimeoutMs: {
352
+ type: v.Optional<number>;
353
+ desc: string[];
354
+ };
351
355
  changeStreamer: {
352
356
  uri: {
353
357
  type: v.Optional<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"zero-config.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/config/zero-config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AAUnD,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAC,SAAS,EAAC,MAAM,kCAAkC,CAAC;AAEhE,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C,eAAO,MAAM,UAAU;;;;;;;;;CA+CtB,CAAC;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;CAwBxB,CAAC;AAEF,QAAA,MAAM,cAAc;;;;;;;;;CAmBnB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE3D,QAAA,MAAM,oBAAoB;;;;;;;;;CAczB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAE5D,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DhB,CAAC;AAuGF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAEpD,+DAA+D;AAC/D,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,UAAU,EACV,KAAK,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CACrD,CAAC;AAKF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;IAsDtB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAGlB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAuLhB,kBAAkB;;;;;;QASlB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAkZpB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgEnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAIpD,wBAAgB,aAAa,CAC3B,IAAI,GAAE,IAAI,CAAC,YAAY,EAAE,eAAe,CAAM,GAC7C,UAAU,CAaZ;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,GAAE,IAAI,CAAC,YAAY,EAAE,eAAe,CAAM,GAC7C,oBAAoB,CAItB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,GAAG,SAAS,GACpD,MAAM,CAER;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,eAAe,CAAC,EACnD,QAAQ,EAAE,MAAM,GAAG,SAAS,WAwC7B;AAYD,wBAAgB,kBAAkB,SAEjC"}
1
+ {"version":3,"file":"zero-config.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/config/zero-config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AAUnD,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAC,SAAS,EAAC,MAAM,kCAAkC,CAAC;AAEhE,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C,eAAO,MAAM,UAAU;;;;;;;;;CA+CtB,CAAC;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;CAwBxB,CAAC;AAEF,QAAA,MAAM,cAAc;;;;;;;;;CAmBnB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE3D,QAAA,MAAM,oBAAoB;;;;;;;;;CAczB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAE5D,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DhB,CAAC;AAuGF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAEpD,+DAA+D;AAC/D,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,UAAU,EACV,KAAK,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CACrD,CAAC;AAKF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;IAsDtB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAGlB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4MhB,kBAAkB;;;;;;QASlB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoZpB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgEnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAIpD,wBAAgB,aAAa,CAC3B,IAAI,GAAE,IAAI,CAAC,YAAY,EAAE,eAAe,CAAM,GAC7C,UAAU,CAaZ;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,GAAE,IAAI,CAAC,YAAY,EAAE,eAAe,CAAM,GAC7C,oBAAoB,CAItB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,GAAG,SAAS,GACpD,MAAM,CAER;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,eAAe,CAAC,EACnD,QAAQ,EAAE,MAAM,GAAG,SAAS,WAwC7B;AAYD,wBAAgB,kBAAkB,SAEjC"}
@@ -131,11 +131,11 @@ var authOptions = {
131
131
  deprecated: [`Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`]
132
132
  },
133
133
  revalidateIntervalSeconds: {
134
- type: valita_exports.number().optional(),
134
+ type: valita_exports.number().default(300),
135
135
  desc: [`The interval in seconds between periodic /query auth revalidation for validated connections.`, `If unset, periodic auth revalidation is disabled.`]
136
136
  },
137
137
  retransformIntervalSeconds: {
138
- type: valita_exports.number().optional(),
138
+ type: valita_exports.number().default(300),
139
139
  desc: [`The interval in seconds between periodic shared /query retransform work for a client group.`, `If unset, periodic shared retransform is disabled.`]
140
140
  }
141
141
  };
@@ -389,6 +389,26 @@ var zeroOptions = {
389
389
  type: valita_exports.number().default(4848),
390
390
  desc: [`The port for sync connections.`]
391
391
  },
392
+ keepaliveTimeoutMs: {
393
+ type: valita_exports.number().optional(),
394
+ desc: [
395
+ `The timeout since the last /keepalive request after which the server will initiate`,
396
+ `a graceful shutdown. This is a workaround for AWS Elastic Container Service, which`,
397
+ `otherwise provides no signal that a target has been deregistered (and should thus begin`,
398
+ `shutdown); the cessation of health checks at /keepalive is instead used as the signal to`,
399
+ `drain. (ECS later sends a SIGTERM before killing the server but only allows a 30-second`,
400
+ `timeout before sending SIGKILL).`,
401
+ ``,
402
+ `Other container runners explicitly send a SIGTERM followed by a configurable drain interval,`,
403
+ `in which case /keepalive logic is not necessary.`,
404
+ ``,
405
+ `When running the server in ECS, this timeout should be set to some multiple of the health`,
406
+ `check interval. If the option is unset, the keepalive timeout is disabled in non-ECS environments,`,
407
+ `and defaults to 20 seconds when run in ECS (determined by the presence of the`,
408
+ `{bold ECS_CONTAINER_METADATA_URI_V4} environment variable as per`,
409
+ `https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-environment-variables.html).`
410
+ ]
411
+ },
392
412
  changeStreamer: {
393
413
  uri: {
394
414
  type: valita_exports.string().optional(),
@@ -743,11 +763,13 @@ var zeroOptions = {
743
763
  ]
744
764
  },
745
765
  intervalHours: {
746
- type: valita_exports.number().default(24),
766
+ type: valita_exports.number().default(12),
747
767
  desc: [
748
- `The interval between shadow initial-sync runs, in hours. The first run`,
749
- `is additionally staggered by a random fraction of this interval so that`,
750
- `a fleet restart does not cause all tasks to canary simultaneously.`
768
+ `The interval between shadow initial-sync runs, in hours. The first`,
769
+ `run fires within [2/3, 1) of this interval after startup, so the`,
770
+ `canary completes at least once per task lifetime (the replication`,
771
+ `manager is restarted every ~24h) while still jittering so a fleet`,
772
+ `restart does not cause all tasks to canary simultaneously.`
751
773
  ]
752
774
  },
753
775
  sampleRate: {